一、ioctl的简介:

虽然在文件操作结构体"structfile_operations"中有很多对应的设备操作函数,但是有些命令是实在找不到对应的操作函数。如CD-ROM的驱动,想要一个弹出光驱的操作,这种操作并不是所有的字符设备都需要的,所以文件操作结构体也不会有对应的函数操作。


出于这样的原因,ioctl就有它的用处了————一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。所以,ioctl函数里面都实现了多个的对硬件的操作,通过应用层传入的命令来调用相应的操作。


来个图来说一下应用层与驱动函数的ioctl之间的联系:

iostat参数那个代表Io率高 ioctl参数说明_iostat参数那个代表Io率高

上面的图可以看出,fd通过内核后找到对应的inode和file结构体指针并传给驱动函数,而另外两个参数却没有修改(类型改了没什么关系)。


简单介绍一下函数:

int (*ioctl) (struct inode * node,struct file *filp, unsigned int cmd, unsigned long arg);

参数:

1)inode和file:ioctl的操作有可能是要修改文件的属性,或者访问硬件。要修改

文件属性的话,就要用到这两个结构体了,所以这里传来了它们的指针。

2)cmd:命令

3)arg:参数

返回值:

1)如果传入的非法命令,ioctl返回错误号-EINVAL。

2)内核中的驱动函数返回值都有一个默认的方法,只要是正数,内核就会傻乎乎的认为这是正确的返回,并把它传给应用层,如果是负值,内核就会认为它是错误号了。

Ioctl里面多个不同的命令,那就要看它函数的实现来决定返回值了。打个比方,如果ioctl里面有一个类似read的函数,那返回值也就可以像read一样返回。当然,不返回也是可以的。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------


       这里主要说一下cmd命令,因为我在写驱动的时候出错,定义的cmd命令无法调用,网上各种查资料,才解决。。。。。。

Linux内核一般会自动地过滤到一些不合法的cmd定义,比如你自己定义的1,2,
cmd为1,2没有type、没有number,没有direction,也没有size,Linux内核自动过滤掉你的ioctl请求,你的ioctl根本就没有到驱动ioctl上就被返回错误了。


二,ioctl参数cmd


一个cmd被分为了4个段,每一段都有各自的意义,cmd的定义在。注:但实际上中只是包含了,这说明了这是跟平台相关的,ARM的定义在,但这文件也是包含别的文件,千找万找,终于找到了。


在中,cmd拆分如下:

iostat参数那个代表Io率高 ioctl参数说明_应用层_02

解释一下四部分,全部都在和ioctl-number.txt这两个文档有说明。

1)幻数:说得再好听的名字也只不过是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不同的驱动的,像设备号申请的时候一样,内核有一个文档给出一些推荐的或者已经被使用的幻数。

/*Documentation/ioctl/ioctl-number.txt*/

164 'w' all CERN SCIdriver

165 'y' 00-1F packetbased user level communications

166

167 'z' 00-3F CAN buscard

168

169 'z' 40-7F CAN buscard

170

可以看到'x'是还没有人用的,我就拿这个当幻数!


2)序数:用这个数来给自己的命令编号,占8bit(_IOC_NRBITS),我的程序从1开始排序。


3)数据传输方向:占2bit(_IOC_DIRBITS)。如果涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层的角度来描述的。

1)_IOC_NONE:值为0,无数据传输。

2)_IOC_READ:值为1,从设备驱动读取数据。

3)_IOC_WRITE:值为2,往设备驱动写入数据。

4)_IOC_READ|_IOC_WRITE:双向数据传输。


4)数据大小:与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)。


强调一下,内核是要求按这样的方法把cmd分类,当然你也可以不这样干,这只是为了迎合内核的要求,让自己的程序看上去很正宗。上面我的程序没按要求照样运行。


既然内核这样定义cmd,就肯定有方法让用户方便定义:

_IO(type,nr) //没有参数的命令

_IOR(type,nr,size) //该命令是从驱动读取数据

_IOW(type,nr,size) //该命令是从驱动写入数据

_IOWR(type,nr,size) //双向数据传输

上面的命令已经定义了方向,我们要传的是幻数(type)、序号(nr)和大小(size)。在这里szie的参数只需要填参数的类型,如int,上面的命令就会帮你检测类型的正确然后赋值sizeof(int)。


有生成cmd的命令就必有拆分cmd的命令:

_IOC_DIR(cmd) //从命令中提取方向

_IOC_TYPE(cmd) //从命令中提取幻数

_IOC_NR(cmd) //从命令中提取序数

_IOC_SIZE(cmd) //从命令中提取数据大小


越讲就越复杂了,既然讲到这,随便就讲一下预定义命令。

预定义命令是由内核来识别并且实现相应的操作,换句话说,一旦你使用了这些命令,你压根也不要指望你的驱动程序能够收到,因为内核拿掉就把它处理掉了。


分为三类:

1)可用于任何文件的命令

2)只用于普通文件的命令

3)特定文件系统类型的命令


其实上面的我三类我也没搞懂,反正我自己随便编了几个数当命令都没出错,如果真的怕出错,那就不要用别人已经使用的幻数就行了。


讲了这么多,终于要上程序了,修改一下上一个程序,让它看起来比较有内涵。

/3rd_char/3rd_char_4/2nd

1)先改一下命令:

/*test_cmd.h*/

1 #ifndef _TEST_CMD_H

2 #define _TEST_CMD_H

3

4 #define TEST_MAGIC 'x' //定义幻数

5 #define TEST_MAX_NR 1 //定义命令的最大序数,只有一个命令当然是1

6

7 #define TEST_CLEAR _IO(TEST_MAGIC, 0)

8

9 #endif /*_TEST_CMD_H*/


2)既然这么辛苦改了cmd,在驱动函数当然要做一些参数检验:

/*test.c*/

122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, unsigned long arg)

123 {

124 int ret = 0;

125 struct _test_t *dev =filp->private_data;

126

127 /*既然这么费劲定义了命令,当然要检验命令是否有效*/

128 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL;

129 if(_IOC_NR(cmd)> TEST_MAX_NR) return - EINVAL;

130

131 switch(cmd){

132 case TEST_CLEAR:

133 memset(dev->kbuf,0, DEV_SIZE);

134 dev->cur_size =0;

135 filp->f_pos = 0;

136 ret = 0;

137 break;

138 default: /*命令错误时的处理*/

139 P_DEBUG("errorcmd!\n");

140 ret = - EINVAL;

141 break;

142 }

143

144 return ret;

145 }

每个参数的传入都会先检验一下幻数还有序数是否正确。


-----lecho---------20150915-------------这个幻数的使用看,经过本人测试是,正确的