1. 什么是ioctl
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:
int ioctl(int fd, ind cmd, …);
其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。
2.
3. 预定义命令
有一些ioctl命令是由内核识别的,当这些命令用于自己的设备时,他们会在我们自己的文件操作被调用之前被解码. 因此, 如果你选择一个ioctl命令编号和系统预定义的相同时,你永远不会看到该命令的请求,而且因为ioctl 号之间的冲突,应用程序的行为将无法预测。预定义命令分为 3 类:
(1)用于任何文件(常规, 设备, FIFO和socket) 的命令
(2)只用于常规文件的命令
(3)特定于文件系统类型的命令
下列 ioctl 命令是预定义给任何文件,包括设备特定文件:
FIOCLEX :设置 close-on-exec 标志(File IOctl Close on EXec)。
FIONCLEX :清除 close-no-exec 标志(File IOctl Not CLose on EXec)。
FIOQSIZE :这个命令返回一个文件或者目录的大小; 当用作一个设备文件, 但是, 它返回一个 ENOTTY 错误。
FIONBIO:"File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一节中描述)。
4. cmd命令构成。
在Linux核心中是这样定义一个命令码的:
――――――――――――――――――――――――――――――――――――――
| 设备类型 | 序列号 | 方向 | 数据尺寸 |
|----------|--------|------|----------|
| 8 bit | 8 bit | 2 bit| 8~14 bit |
|----------|--------|------|----------|
设备类型 : '0'~'9','a'~'z','A'~'Z',补充:实际上只要8位的数都是可以的。
又称为 幻数,魔数
在内核文档Ioctl-number.txt (documentation\ioctl)有详细的描述,哪些数已经被使用了。
序列号 : 就是你驱动中命令的序号,从0开始,也可以从其他数开始。
方向 :
以用户空间为参照:
ioctl(fd,cmd); 没有arg参数,不存在数据方向
ioctl(fd,cmd,arg); arg是一个值或地址--->写入内核驱动中---写方向
ioctl(fd,cmd,arg); arg是地址 --->内核驱动中修改arg的值---读方向
ioctl(fd,cmd,arg); arg是地址 --->内核驱动中取arg的内容,然后修改arg的值---读写方向
int arg = 10;
ioctl(fd,cmd,&arg);
驱动中可以取得arg的值来使用,也可以修改arg的值,这种情况属于数据双向。
手工构造,例子:
cmd0: (‘k’ << 24 | 0 << 16 | 1 << 14 | 4 << 0)
cmd1: (‘k’ << 24 | 1 << 16 | 2 << 14 | 4 << 0)
是否可以自动构造??????
数据尺寸 :arg 大小。
这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
1、定义命令:
内核提供了一些宏来帮助定义命令:
//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送
定义命令例子:
#define MEM_IOC_MAGIC 'm' //定义类型
#define MEM_IOC _IO(MEM_IOC_MAGIC, 0)
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,1,int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC,2, int)
同时为发方便验证命令合法性,内核也实现了相应的域提取宏,如下:
_IOC_NR(cmd), _IOC_TYPE(cmd), _IOC_SIZE(cmd), _IOC_DIR(cmd) 这几个宏用来取得 cmd 命令中的域,其作用如下:
_IOC_NR(cmd) : 读取基数域值 (bit0~ bit7)
_IOC_TYPE(cmd): 读取魔数域值 (bit8 ~ bit15)
_IOC_SIZE(cmd): 读取数据大小域值 (bit16 ~ bit29)
_IOC_DIR(cmd) : 获取读写属性域值 (bit30 ~ bit31)
2、实现命令:
定义好了命令,下一步就是要实现ioctl函数了,ioctl的实现包括三个技术环节:
1)返回值;
ioctl函数的实现是根据命令执行的一个switch语句,但是,当命令不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(非法参数);
2)参数使用;
用户使用 int ioctl(int fd,unsinged long cmd,...) 时,...就是要传递的参数;
再通过 int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)中的arg传递;
如果arg是一个整数,可以直接使用;
如果是指针,我们必须确保这个用户地址是有效的,因此,使用之前需要进行正确检查。
内部有检查的,不需要检测的:
copy_from_user
copy_to_user
get_user
put_user
需要检测的函数:
__get_user
__put_user
检测函数access_ok():
static inline int access_ok(int type, const void *addr, unsigned long size)
/*
*type :是VERIFY_READ 或者VERIFY_WRITE用来表明是读用户内存还是写用户内存;
*addr:是要操作的用户内存地址;
*size:是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数就等于sizeof(int);
*返回值:Access_ok返回一个布尔值:1,是成功(存取没问题);0,是失败,ioctl返回-EFAULT;
*/
3)命令操作;
switch(cmd)
{
case:
... ...
}
5 .程序范例
作业:实现标准ioctl接口,控制led,读取led状态,移动led文件指针。
//复位文件指针; ---
//移动文件指针; arg
//查询当前led文件指针 arg
//设置当前led文件指针 arg
//点亮当前led ---
//熄灭当前led ---
//根据当前arg参数设置当前led状态 arg
//全部开led ---
//全部关led ---
//示例用户空间和内核空间同时传递数据的情况 arg
ioctl系统调用
最新推荐文章于 2024-08-04 20:19:55 发布