来公司实习看代码的时候经常会看到IOCTL这个函数,以前没见过,也问过mentor一回,但一直没搞懂,今天就拿出一点时间查了一下。
首先看一下ioctl的函数原型:
int ioctl(int fd, int cmd, [int *argdx, int *argcx])
返回值:成功为0,出错为-1。
第一个参数fd:是用户打开的文件描述符。
第二个参数cmd:是用户对这个文件描述符对应的设备的控制命令,这个参数是理解ioctl的关键。
剩下的参数是配合第二个参数cmd使用的,如果你是自己在内核中定义了自己的ioctl函数,那么这些剩下的参数就是内核ioctl的入参了,这样你就可以扩展出自己的功能。举个例子,我们项目中内核有很多不同类型的数据需要向用户空间传,这时候如果每种类型定义一个文件的话太多了,所以我们在cmd后面增加了一个参数,用来区别我们要读取的参数是哪个类型的数据,内核中的ioctl函数根据这个参数来进行相应的配置就行了,然后read函数中再根据不同的配置定位不同的内存。
现在讲cmd,这个主要来自于网上对别人博客的参考。
cmd的大小是32位,这32位被分成了4部分,结构入下:
|区别读写|数据大小|魔 数|区别序号|
|31 31|29 16|15 08|07 00|
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
bit29~bit16 14位为 "数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit15~bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。
如果每个cmd都要一个一个算着位然后生成,肯定是件费力不讨好的事情,聪明的程序员肯定都不愿意这么干,所以内核定义了四个函数用来生产cmd。这四个函数是:
_IO、_IOR、_IOW、_IOWR。
我们来看_IO,其余的相似。
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr
),0)
type其实对应的是cmd中的魔数,用来标识命令的类型,注意不同的命令可以使用相同的魔数,那么魔数相同系统怎么区分两条命令呢,这时就要用第二个参数nr了,这个参数对应cmd中的区别序号,这两个参数一起来标识一条命令。这两个参数都是8位。
下面来看_IOC:
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
可以看出来,这个宏主要是对_IOC的几个参数进行移位,具体移多少位呢?
看看下面就知道了。
#define _IOC_NRSHIFT 0 //序数字段在整个字段中的位移,0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //幻数字段的位移,8
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //大小字段的位移,16
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //方向字段的位移,30
#define _IOC_NRBITS 8 //序数(number)字段的字位宽度,8bits
#define _IOC_TYPEBITS 8 //幻数(type)字段的字位宽度,8bits
#define _IOC_SIZEBITS 14 //大小(size)字段的字位宽度,14bits
#define _IOC_DIRBITS 2 //方向(direction)字段的字位宽度,2bits
所以,
(dir) << _IOC_DIRSHIFT)
表是 dir 往左移 30 位,即移到 bit31~bit30 两位上,得到方向(读写)的属性;
(size) << _IOC_SIZESHIFT)
位左移 16 位得到“数据大小”区;
(type) << _IOC_TYPESHIFT)
左移 8位得到"魔数区" ;
(nr) << _IOC_NRSHIFT)
左移 0 位( bit7~bit0) 。
这样就得到了各个部分的值。
我们只要利用这个宏,并且对相应的参数赋值,就可以构造出一个cmd来了。、
现在我们来构造一个:
#define MY_MAGIC
0xdf
#define MY_CMD _IOW(MY_MAGIC, 2 , int)
于是有命令MY_CMD各组成字段(dir,size,type,nr)分别为:01(=_IOC_WRITE
),00 0000 0000 0100(=sizeof(unsigned int)),1101 1111(=MY_CMD_MAGIC),0000 0000(=0)。用十六进制表示即0x4004df00。这个32 bits的"整数"就是该命令的编号(LDD3原文是command number),也就是命令MY_CMD在系统中的"身份证号码"了!