Linux下的ioctl()函数
一、什么是ioctl
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。
用户程序所作的只是通过命令码(cmd
)告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
二、 ioctl如何实现
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情。因为设备都是特定的,这里也没法说。
三、ioctl参数
1、用户空间,ioctl的调用具有如下原型:int ioctl(int fd, unsigned long cmd, ...);
其中fd
是用户程序打开设备时使用open函数返回的文件标示符,cmd
是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。
2、驱动空间,ioctl方法的原型如下:int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg);
1)inode
和file
:ioctl的操作有可能是要修改文件的属性,或者访问硬件。要修改文件属性的话,就要用到这两个结构体了,所以这里传来了它们的指针。
2)cmd
:控制命令。
3)arg
:补充参数。
四、cmd参数
命令码(cmd
)是唯一联系用户程序命令和驱动程序支持的途径。 命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。所以在Linux核心中是这样定义一个命令码的:
cmd的大小为 32位,共分 4 个域:
设备类型(幻数) | 序列号 | 方向 | 数据大小 |
---|---|---|---|
8 bit | 8 bit | 2 bit | 14 bit |
---------- | -------- | ------- | ----------- |
bit31~bit30:2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
bit29~bit15:14位为 “数据大小” 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit20~bit08:8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00:8位为 “区别序号” 区,是区分命令的命令顺序序号。
像命令码中的 “区分读写区” 里的值可能是 _IOC_NONE (0值)表示无数据传输,_IOC_READ (读), _IOC_WRITE (写) , _IOC_READ|_IOC_WRITE (双向)。
这样一来,就产生了一个整数形式的命令码;但是命令码非常的不直观,所以Linux Kernel中提供了一些宏。这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。
内核定义了 _IO() , _IOR() , IOW() 和 _IOWR() 这 4 个宏来辅助生成上面的 cmd 。
这几个宏的使用格式为:
_IO (魔数,基数);
_IOR(魔数,基数,变量型)
_IOW(魔数,基数,变量型)
_IOWR (魔数,基数,变量型)
魔数 (magic number)
魔数范围为 0~255 。通常,用英文字符 "A" ~ "Z" 或者 "a" ~ "z" 来表示。设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理。魔数是拒绝误使用的初步辅助状态。设备驱动 程序可以通过 _IOC_TYPE (cmd) 来获取魔数。不同的设备驱动程序最好设置不同的魔数,但并不是要求绝对,也是可以使用其他设备驱动程序已用过的魔数。
基(序列号)数
基数用于区别各种命令。通常,从 0开始递增,相同设备驱动程序上可以重复使用该值。例如,读取和写入命令中使用了相同的基数,设备驱动程序也能分辨出来,原因在于设备驱动程序区分命令时 使用 switch ,且直接使用命令变量 cmd值。创建命令的宏生成的值由多个域组合而成,所以即使是相同的基数,也会判断为不同的命令。设备驱动程序想要从命令中获取该基数,就使用下面的宏:
_IOC_NR (cmd)
通常,switch 中的 case 值使用的是命令的本身。
变量型
变量型使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型,原因是在使用宏创建命令,已经包含了 sizeof() 编译命令。比如 _IOR() 宏的定义是:
引用
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
而 _IOC_TYPECHECK() 的定义正是:
引用
#define _IOC_TYPECHECK(t) (sizeof(t))
设备驱动程序想要从传送的命令获取相应的值,就要使用下列宏函数:
_IOC_SIZE(cmd)