linux驱动程序ioctl函数用法

一、 什么是ioctl
    ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:
int ioctl(int fd, ind cmd, …);
    其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。
    ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。

二、 ioctl的必要性
    如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码(cmd)告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

三、 ioctl如何实现
    这是一个很麻烦的问题,我是能省则省。要说清楚它,没有四五千字是不行的,所以我这里是不可能把它说得非常清楚了,不过如果读者对用户程序是怎么和驱动程序联系起来感兴趣的话,可以看我前一阵子写的《write的奥秘》。读者只要把write换成ioctl,就知道用户程序的ioctl是怎么和驱动程序中的ioctl实现联系在一起的了。我这里说一个大概思路,因为我觉得《Linux设备驱动程序》这本书已经说的非常清楚了,但是得花一些时间来看。
    在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情。因为设备都是特定的,这里也没法说。关键在于怎样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。所以在Linux核心中是这样定义一个命令码的:
____________________________________
| 设备类型(幻数) | 序列号 | 方向 |数据尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|

    这样一来,一个命令就变成了一个整数形式的命令码;但是命令码非常的不直观,所以Linux Kernel中提供了一些宏。这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

    这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的宏,文件里给这些宏做了完整的定义。这里我只多说一个地方,那就是"幻数"。"幻数"是一个字母,数据长度也是8,用一个特定的字母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。 更多的说了也没用,读者还是看一看源代码吧,推荐各位阅读《Linux 设备驱动程序》所带源代码中的short一例,因为它比较短小,功能比较简单,可以看明白ioctl的功能和细节。

四、 cmd参数如何得出
    这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。要透彻理解,只能是通过阅读源代码,我这篇文章实际上只是一个引子。cmd参数的组织还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,因为驱动程序中最难的是对中断的理解

五、 小结
    ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的I/O控制都是通过这一部分的代码实现的。

***********************************************************************************************************************

一、ioctl的简介:

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


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


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

linux驱动程序ioctl函数用法(转)

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


简单介绍一下函数:

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

参数:

1)inodefileioctl的操作有可能是要修改文件的属性,或者访问硬件。要修改

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

2)cmd:命令,接下来要长篇大论地说。

3)arg:参数,接下来也要长篇大论。

返回值:

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

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

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

当然,不返回也是可以的。


二、ioctlcmd


说白了,cmd就是一个数,如果应用层传来的数值在驱动中有对应的操作,这样就就可以了。


来个最简单的ioctl实现:3rd_char_4/1st


1)要先定义个命令,就用一个简单的0,来个命令的头文件,驱动和应用函数都要包含这个头文件

 

1 #ifndef _TEST_CMD_H

2 #define _TEST_CMD_H

3

4 #define TEST_CLEAR 0

5

6 #endif

 

2)驱动实现ioctl

命令TEST_CLEAR的操作就是清空驱动中的kbuf

122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)

123 {

124 int ret = 0;

125 struct _test_t *dev =filp->private_data;

126

127 switch(cmd){

128 case TEST_CLEAR:

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

130 dev->cur_size =0;

131 filp->f_pos =0;

132 ret = 0;

133 break;

134 default:

135 P_DEBUG("errorcmd!\n");

136 ret = - EINVAL;

137 break;

138 }

139

140 return ret;

141 }


3)再来个应用程序:

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include "test_cmd.h"

7

8 int main(void)

9 {

10 char buf[20];

11 int fd;

12 int ret;

13

14 fd = open("/dev/test",O_RDWR);

15 if(fd <0)

16 {

17 perror("open");

18 return -1;

19 }

20

21 write(fd, "xiao bai", 10);//1先写入

22

23 ioctl(fd, TEST_CLEAR);//2再清空

24

25 ret = read(fd, buf, 10);//3再验证

26 if(ret <0)

27 {

28 perror("read");

29 }

30

31 close(fd);

32 return 0;

33 }

注:这里为了read返回出错,我修改了驱动的readwrite函数的开始时的第一个

判断,一看就知道了。


4)验证一下:

[root: 1st]# insmodtest.ko

major[253] minor[0]

hello kernel

[root: 1st]# mknod /dev/test c 2530

[root: 1st]# ./app

[test_write]write 10 bytes,cur_size:[10]

[test_write]kbuf is [xiaobai]

read: No such device or address//哈哈!出错了!因为没数据读取。


按照上面的方法来定义一个命令是完全可以的,但内核开发人员发现这样有点不对劲。

如果有两个不同的设备,但它们的ioctlcmd却一样的,哪天有谁不小心打开错了,并且调用ioctl,这样就完蛋了。因为这个文件里面同样有cmd对应实现。

为了防止这样的事情发生,内核对cmd又有了新的定义,规定了cmd都应该不一样。


三、ioctl中的cmd


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


在中,cmd拆分如下:

linux驱动程序ioctl函数用法(转)

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

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

 

164 'w' all CERN SCIdriver

165 'y' 00-1F packet based userlevel 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)先改一下命令:

 

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


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

 

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

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:

133memset(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 }

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


3)应用程序的验证

结果跟上一个完全一样,因为命令的操作没有修改

[root: 2nd]# insmodtest.ko

major[253] minor[0]

hello kernel

[root: 2nd]# mknod /dev/test c 2530

[root: 2nd]# ./app

[test_write]write 10 bytes,cur_size:[10]

[test_write]kbuf is [xiaobai]

read: No such device oraddress


五、ioctl中的arg之整数传参。



上面讲的例子都没有使用ioctl的传参。这里先要说一下ioctl传参的方式。


应用层的ioctl的第三个参数是"...",这个跟printf"..."可不一样,printf中是意味这你可以传任意个数的参数,而ioctl最多也只能传一个,"..."的意思是让内核不要检查这个参数的类型。也就是说,从用户层可以传入任何参数,只要你传入的个数是1.


一般会有两种的传参方法:

1)整数,那可是省力又省心,直接使用就可以了。

2)指针,通过指针的就传什么类型都可以了,当然用起来就比较烦。


先说简单的,使用整数作为参数:

例子,实现个命令,通过传入参数更改偏移量,虽然llseek已经实现,这里只是想验证一下正数传参的方法。


1)先加个命令:

1 #ifndef _TEST_CMD_H

2 #define _TEST_CMD_H

3

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

5 #defineTEST_MAX_NR 2 //定义命令的最大序数

6

7 #define TEST_CLEAR_IO(TEST_MAGIC, 1)

8 #define TEST_OFFSET_IO(TEST_MAGIC, 2)

9

10 #endif

这里有人会问了,明明你是要传入参数,为什么不用_IOW而用_IO定义命令呢?

原因有二:

1)因为定义数据的传输方向是为了好让驱动的函数验证数据的安全性,而一般指针才需要检验安全性,因为有人会恶意传参(回想一下copy_to_user)

2)个人喜好,方便我写程序介绍另一种传参方法,说白了命令也只是一个数,只要不要跟预定义命令冲突就可以了。


2)更新test_ioctl

122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)

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:

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

134 dev->cur_size =0;

135 filp->f_pos =0;

136 ret = 0;

137 break;

138 case TEST_OFFSET://根据传入的参数更改偏移量

139 filp->f_pos +=(int)arg;

140 P_DEBUG("changeoffset!\n");

141 ret = 0;

142 break;

143 default:

144 P_DEBUG("errorcmd!\n");

145 ret = - EINVAL;

146 break;

147 }

148

149 return ret;

150 }

TSET_OFFSET命令就是根据传参更改偏移量,不过这里要注意一个问题,那就是参数的类型,驱动函数必须要知道从应用传来的参数是什么类型,不然就没法使用。在这个函数里,从应用层传来的参数是int,因此在驱动中也得用int


3)再改一下应用程序:

1 #include

2 #include

3 #include

4 #include

5 #include

6

7 #include "test_cmd.h"

8

9 int main(void)

10 {

11 char buf[20];

12 int fd;

13 int ret;

14

15 fd = open("/dev/test",O_RDWR);

16 if(fd <0)

17 {

18 perror("open");

19 return -1;

20 }

21

22 write(fd, "xiao bai", 10);//先写入

23

24 ioctl(fd, TEST_OFFSET, -10);//再改偏移量

25

26 ret = read(fd, buf, 10);//再读数据

27 printf(" buf is [%s]\n",buf);

28 if(ret <0)

29 {

30 perror("read");

31 }

32

33 close(fd);

34 return 0;

35 }


4)验证一下

[root: 3rd]# insmodtest.ko

major[253] minor[0]

hello kernel

[root: 3rd]# mknod /dev/test c 2530

[root: 3rd]# ./app

[test_write]write 10 bytes,cur_size:[10]

[test_write]kbuf is [xiaobai]

[test_ioctl]change offset!//更改偏移量

[test_read]read 10 bytes,cur_size:[0] //没错误,成功读取!

buf is [xiao bai]


上面的传参很简单把,接下来说一下以指针传参。

考虑到参数不可能永远只是一个正数这么简单,如果要传多一点的东西,譬如是结构体,那就得用上指针了。


六、ioctl中的arg之指针传参。


一讲到从应用程序传来的指针,就得想起我邪恶的传入了非法指针的例子。所以,驱动程序中任何与应用层打交道的指针,都得先检验指针的安全性。


说到这检验又有两种方法:

1)用的时候才检验。

2)一进来ioctl就检验。


先说用的时候检验,说白了就是用copy_xx_user系列函数,下面实现一下:

1)先定义个命令

1 #ifndef _TEST_CMD_H

2 #define _TEST_CMD_H

3

4 struct ioctl_data{

5 unsigned int size;

6 char buf[100];

7 };

8

9 #define DEV_SIZE 100

10

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

12 #defineTEST_MAX_NR 3 //定义命令的最大序数

13

14 #define TEST_CLEAR_IO(TEST_MAGIC, 1)

15 #define TEST_OFFSET_IO(TEST_MAGIC, 2)

16 #defineTEST_KBUF _IO(TEST_MAGIC, 3)

17

18 #endif

这里有定义多了一个函数,虽然这个命令是涉及到了指针的传参,但我还是_IOW,还是那一句,现在还不需要用上。

该命令的操作是传进一个结构体指针,驱动根据结构体的内容修改kbufcur_size和偏移量。


2)来个实现函数:

122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)

123 {

124 int ret = 0;

125 struct _test_t *dev =filp->private_data;

126 struct ioctl_dataval;

127

128

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

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

131

132 switch(cmd){

133 case TEST_CLEAR:

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

135 dev->cur_size =0;

136 filp->f_pos =0;

137 ret = 0;

138 break;

139 case TEST_OFFSET://根据传入的参数更改偏移量

140 filp->f_pos +=(int)arg;

141 P_DEBUG("changeoffset!\n");

142 ret = 0;

143 break;

144 case TEST_KBUF://修改kbuf

145 if(copy_from_user(&val, (structioctl_data *)arg, sizeof(struct ioctl_data))){

146 ret = - EFAULT;

147 goto RET;

148 }

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

150memcpy(dev->kbuf, val.buf, val.size);

151 dev->cur_size =val.size;

152 filp->f_pos =0;

153 ret = 0;

154 break;

155 default:

156 P_DEBUG("errorcmd!\n");

157 ret = - EINVAL;

158 break;

159 }

160

161 RET:

162 return ret;

163 }

145行,因为指针是从用户程序传来,所以必须检查安全性。


3)来个应用程序

9 int main(void)

10 {

11 char buf[20];

12 int fd;

13 int ret;

14

15 struct ioctl_data my_data={

16 .size = 10,

17 .buf = "123456789"

18 };

19

20 fd = open("/dev/test",O_RDWR);

21 if(fd <0)

22 {

23 perror("open");

24 return -1;

25 }

26

27 write(fd, "xiao bai",10);

28

29 ioctl(fd, TEST_KBUF,&my_data);

30

31 ret = read(fd, buf,10);

32 printf(" buf is [%s]\n",buf);

33 if(ret <0)

34 {

35 perror("read");

36 }

37

38 close(fd);

39 return 0;

40 }


4)再来验证一下:

[root: 4th]# ./app

[test_write]write 10 bytes,cur_size:[10]

[test_write]kbuf is [xiaobai]

[test_read]read 10 bytes,cur_size:[0]

buf is [123456789]//成功!

注:类似copy_xx_user的函数含有put_userget_user等,我就不细说了。

 

下面说第二种方法:进入ioctl后使用access_ok检测。

声明一下:下面的验证方法是不正确的。如果不想看下去的话,今天的内容已经讲完了。


先说一下access_ok的使用

access_ok(type, addr,size)

使用:检测地址的安全性

参数:

type:用于指定数据传输的方向,VERIFY_READ表示要读取应用层数据,VERIFT_WRITE表示要往应用层写如数据。注意:这里和IORIOW的方向相反。如果既读取又写入,那就使用VERIFY_WRITE

addr:用户空间的地址

size:数据的大小

返回值:

成功返回1,失败返回0


既然知道怎么用,就直接来程序了:

1)定义命令

1 #ifndef _TEST_CMD_H

2 #define _TEST_CMD_H

3

4 struct ioctl_data{

5 unsigned int size;

6 char buf[100];

7 };

8

9 #define DEV_SIZE 100

10

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

12 #defineTEST_MAX_NR 3 //定义命令的最大序数

13

14 #define TEST_CLEAR_IO(TEST_MAGIC, 1)

15 #define TEST_OFFSET_IO(TEST_MAGIC, 2)

16 #defineTEST_KBUF _IOW(TEST_MAGIC, 3, struct ioctl_data)

17

18 #endif

这里终于要用_IOW了!


2)实现ioctl

122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)

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

131if(_IOC_DIR(cmd) & _IOC_READ)

132 ret =access_ok(VERIFY_WRITE, (void __user *)arg,_IOC_SIZE(cmd));

133 elseif(_IOC_DIR(cmd) & _IOC_WRITE)

134 ret =access_ok(VERIFY_READ, (void __user *)arg,_IOC_SIZE(cmd));

135 if(!ret)return - EFAULT;

136

137 switch(cmd){

138 case TEST_CLEAR:

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

140 dev->cur_size =0;

141 filp->f_pos =0;

142 ret = 0;

143 break;

144 case TEST_OFFSET://根据传入的参数更改偏移量

145 filp->f_pos +=(int)arg;

146 P_DEBUG("changeoffset!\n");

147 ret = 0;

148 break;

149 case TEST_KBUF://修改kbuf

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

151memcpy(dev->kbuf, ((struct ioctl_data*)arg)->buf,

152 ((structioctl_data *)arg)->size);

153 dev->cur_size =((struct ioctl_data *)arg)->size;

154 filp->f_pos =0;

155 ret = 0;

156 break;

157 default:

158 P_DEBUG("errorcmd!\n");

159 ret = - EINVAL;

160 break;

161 }

162

163 return ret;

164 }

上面并没有用copy_to_user,而是通过access_ok来检测。


3)再来个应用程序:

9 int main(void)

10 {

11 char buf[20];

12 int fd;

13 int ret;

14

15 struct ioctl_data my_data={

16 .size = 10,

17 .buf = "123456789"

18 };

19

20 fd = open("/dev/test",O_RDWR);

21 if(fd <0)

22 {

23 perror("open");

24 return -1;

25 }

26

27 write(fd, "xiao bai",10);

28

29 ret = ioctl(fd, TEST_KBUF,&my_data);

30 if(ret <0)

31 {

32 perror("ioctl");

33 }

34

35 ret = read(fd, buf,10);

36 printf(" buf is [%s]\n",buf);

37 if(ret <0)

38 {

39 perror("read");

40 }

41

42 close(fd);

43 return 0;

44 }


4)验证一下:效果和上一个一样

[root: 5th]# ./app

[test_write]write 10 bytes,cur_size:[10]

[test_write]kbuf is [xiaobai]

[test_read]read 10 bytes,cur_size:[0]

buf is [123456789]


下面就要如正题了,这个驱动是有问题的,那就是验证安全性完全不起作用!当我传入非法指针时,驱动同样会输出,不信可以自己传个邪恶地址(void*)0进去试一下。


修改应用程序一样代码:

29 ret = ioctl(fd, TEST_KBUF,&my_data);


上面是我做的错误实现,我本来想验证,只要经过access_ok检验,数据就会安全,没想到经过access_ok检验之后照样会出错。

但是,copy_to_user同样是先调用access_ok再调用memcpy,它却没出错。这个我事情我现在都没搞明白,如果谁知道了麻烦指点一下。


我查了设备驱动第三版,在144页有这样的说法:

1.access_ok并没有做完的所有的内存检查,

2.大多数的驱动代码都不是用access_ok的,后面的内存管理会讲述。


在这里书本上有这样的约定:(都是我自己的理解)

1.传入指针需要检查安全性。memcpy函数尽量不要在内核中使用。

2.copy_to_user,copy_from_user,get_user,put_user函数会再拷贝数据前检测指针的安全性(检查用户空间的地址),不需要access_okcopy_to_user,copy_from_user函数用于一块数据,get_user,put_user函数用于传递单个数据(1、2、4及8个字节)

3.如果在ioctl函数开头使用了accsee_ok检验数据,接下来的代码可以使用__put_user__get_user这些不需要检测的函数(书上有例子)


虽然还有写东西还没搞懂,但个人觉得,如果使用个access_ok要这么麻烦的话,那我就不用好了,以后我就使用copy_xx_user函数,省力又省心。




七、总结:


这次讲了ioctl的实现:

1)命令是怎么定义。

2)参数怎么传递。


=======================================================


  • 10
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Linux设备驱动程序是一种软件模块,它允许操作系统与硬件设备进行通信。设备驱动程序的设计需要遵循一定的规范和流程,包括以下几个步骤: 1. 确定设备类型:设备可以是字符设备、块设备或网络设备等。每种设备类型都有不同的驱动程序接口和操作方法。 2. 编写设备驱动程序:设备驱动程序是一个内核模块,它包含了设备的初始化、读写操作、中断处理等函数驱动程序需要遵循Linux内核的编程规范和API。 3. 注册设备驱动程序驱动程序需要在内核中注册,以便操作系统能够识别和加载它。注册过程包括分配设备号、初始化设备结构体、注册字符设备或块设备等。 4. 测试设备驱动程序:测试设备驱动程序需要使用一些工具和技术,例如ioctl命令、proc文件系统、sysfs文件系统等。测试过程需要验证设备的正确性、性能和稳定性。 以下是一个简单的字符设备驱动程序的例子: ```c #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #define DEVICE_NAME "mydevice" #define BUF_LEN 1024 static int Major; static char msg[BUF_LEN]; static char *msg_ptr; static int device_open(struct inode *inode, struct file *file) { msg_ptr = msg; try_module_get(THIS_MODULE); return 0; } static int device_release(struct inode *inode, struct file *file) { module_put(THIS_MODULE); return 0; } static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) { int bytes_read = 0; if (*msg_ptr == 0) return 0; while (length && *msg_ptr) { put_user(*(msg_ptr++), buffer++); length--; bytes_read++; } return bytes_read; } static ssize_t device_write(struct file *filp, const char *buffer, size_t length, loff_t *offset) { int i; for (i = 0; i < length && i < BUF_LEN; i++) get_user(msg[i], buffer + i); msg_ptr = msg; return i; } static struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release }; static int __init init_module(void) { Major = register_chrdev(0, DEVICE_NAME, &fops); if (Major < 0) { printk(KERN_ALERT "Registering char device failed with %d\n", Major); return Major; } printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major); printk(KERN_INFO "the driver, create a dev file with\n"); printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major); return 0; } static void __exit cleanup_module(void) { unregister_chrdev(Major, DEVICE_NAME); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple example Linux module."); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值