博客主页:PannLZ
系列专栏:《Linux系统之路》
欢迎关注:点赞收藏✍️留言
典型的Linux系统包含大约350个系统调用(syscalls),但是其中只有少数几个链接到文件操作。有时设备可能需要实现系统调用未提供的特定命令,特别是与文件和设备文件相关的命令。在这种情况下,解决方案是使用输入/输出控制(ioctl),这种方法可以扩展与设备相关的系统调用列表(实际上是命令)。用它可以向设备发送特殊命令(重置、关机、配置等)。如果驱动程序没有定义这个方法,则内核将向ioctl()
系统调用返回-ENOTTY错误。
为了有效和安全起见,ioctl命令
需要由系统唯一的号码来标识。系统内ioctl号码的唯一性可以防止把正确的命令发送到错误的设备,或者将错误的参数传递给正确的命令(给出重复的ioctl号码)。
Linux提供了4个帮助宏来创建ioctl标识符,选用哪个取决于是否有数据传输和传输的方向。它们各自的原型如下:
_IO(MAGIC, SEQ_NO)
_IOW(MAGIC, SEQ_NO, TYPE)
_IOR(MAGIC, SEQ_NO, TYPE)
_IORW(MAGIC, SEQ_NO, TYPE)
_IO:ioctl不需要数据传输。
_IOW:ioctl需要写入参数(copy_from_user或get_user)。
_IOR:ioctl需要读取参数(copy_to_user或put_user)。
_IOWR:ioctl需要写入和读取参数。
参数的含义(按照传递顺序)如下:
(1)8位编码数字(0~255),称为魔数。
(2)序列号或命令ID,也是8位。
(3)数据类型(如果有的话),这会通知内核要复制的数据长度。
内核源码的
Documentation/ioctl/ioctl-decoding.txt
中会详细说明,现有的ioctl
在Documentation/ioctl/ioctl-number.txt中列出,需要创建ioctl命令时,这是一个很好的开始。
应该在专用头文件中生成自己的ioctl编号,这不是强制性的,但建议这样做,因为这个头文件在用户空间中也可以使用。换句话说,应该复制ioctl头文件,以便在内核中有一个,在用户空间中也有一个,该文件可以包含在用户应用程序中。现在以一个真实的例子来生成ioctl编号:
eep_ioctl.h:
#ifndef PACKT_IOCTL_H
#define PACKT_IOCTL_H
/*
* 需要为驱动选择一个数字,以及每个命令的序列号
*/
#define EEP_MAGIC 'E'
#define ERASE_SEQ_NO 0x01
#define RENAME_SEQ_NO 0x02
#define ClEAR_BYTE_SEQ_NO 0x03
#define GET_SIZE 0x04
/*
* 分区名必须是最大32字节
*/
#define MAX_PART_NAME 32
/*
* 定义ioctl编号
*/
#define EEP_ERASE _IO(EEP_MAGIC, ERASE_SEQ_NO)
#define EEP_RENAME_PART _IOW(EEP_MAGIC,RENAME_SEQ_NO, unsigned long)
#define EEP_GET_SIZE _IOR(EEP_MAGIC, GET_SIZE,int *)
#endif
看一下ioctl的原型,如下所示:
long ioctl(struct file *f, unsigned int cmd,unsigned long arg);
使用switch … case语句,在调用未定义的ioctl命令时返回-ENOTTY错误:
/*
* 用户空间代码还需要包括定义ioctls的头文件,这里是eep_iocl.h
*/
#include "eep_ioctl.h"
static long eep_ioctl(struct file *f, unsignedint cmd, unsigned long arg)
{
int part;
char *buf = NULL;
int size = 1300;
switch(cmd){
case EEP_ERASE:
erase_eepreom();
break;
case EEP_RENAME_PART:
buf = kmalloc(MAX_PART_NAME,GFP_KERNEL);
copy_from_user(buf, (char *)arg,MAX_PART_NAME);
rename_part(buf);
break;
case EEP_GET_SIZE:
copy_to_user((int*)arg, &size,sizeof(int));
break;
default:
return -ENOTTY;
}
return 0;
}
如果认为ioctl命令需要多个参数,则应该把这些参数集合到结构中,把该结构的指针传递给ioctl。
现在,在用户空间中,必须使用与驱动程序代码中相同的ioctl头文件:
//my_main.c
#include
#include
#include
#include
#include "eep_ioctl.h" /* our ioctl header file*/
int main()
{
int size = 0;
int fd;
char *new_name = "lorem_ipsum"; /* 不超过MAX_PART_NAME */
fd = open("/dev/eep-mem1", O_RDWR);
if (fd == -1){
printf("Error while opening theeeprom\n");
return -1;
}
ioctl(fd, EEP_ERASE); /* 调用ioctl来擦除分区*/
ioctl(fd, EEP_GET_SIZE, &size); /* 调用ioctl获取分区大小 */
ioctl(fd, EEP_RENAME_PART, new_name); /*调用ioctl来重命名分区 */
close(fd);
return 0;
}