Linuxioctl的实现 linux ioctl 头文件

一、ioctl的简介:

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

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

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

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

简单介绍一下函数:

int(*ioctl)(structinode*node,structfile*filp,unsignedintcmd,unsignedlongarg);

参数:

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

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

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

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

返回值:

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

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

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

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

二、ioctl的cmd

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

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

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

1#ifndef_TEST_CMD_H

2#define_TEST_CMD_H

3

4#defineTEST_CLEAR0

5

6#endif

2)驱动实现ioctl:

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

122inttest_ioctl(structinode*node,structfile*filp,unsignedintcmd,unsignedlongarg)

123{

124intret=0;

125struct_test_t*dev=filp->private_data;

126

127switch(cmd){

128caseTEST_CLEAR:

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

130dev->cur_size=0;

131filp->f_pos=0;

132ret=0;

133break;

134default:

135P_DEBUG("errorcmd!n");

136ret=-EINVAL;

137break;

138}

139

140returnret;

141}

3)再来个应用程序:

1#include

2#include

3#include

4#include

5#include

6#include"test_cmd.h"

7

8intmain(void)

9{

10charbuf[20];

11intfd;

12intret;

13

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

15if(fd<0)

16{

17perror("open");

18return-1;

19}

20

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

22

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

24

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

26if(ret<0)

27{

28perror("read");

29}

30

31close(fd);

32return0;

33}

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

判断,一看就知道了。

4)验证一下:

[root:1st]#insmodtest.ko

major[253]minor[0]

hellokernel

[root:1st]#mknod/dev/testc2530

[root:1st]#./app

[test_write]write10bytes,cur_size:[10]

[test_write]kbufis[xiaobai]

read:Nosuchdeviceoraddress//哈哈!出错了!因为没数据读取。

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

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

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

三、ioctl中的cmd

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

在中,cmd拆分如下:

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

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

164'w'allCERNSCIdriver

165'y'00-1Fpacketbaseduserlevelcommunications

166

167'z'00-3FCANbuscard

168

169'z'40-7FCANbuscard

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#defineTEST_MAGIC'x'//定义幻数

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

6

7#defineTEST_CLEAR_IO(TEST_MAGIC,0)

8

9#endif

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

122inttest_ioctl(structinode*node,structfile*filp,unsignedintcmd,unsignedlongarg)

123{

124intret=0;

125struct_test_t*dev=filp->private_data;

126

127

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

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

130

131switch(cmd){

132caseTEST_CLEAR:

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

134dev->cur_size=0;

135filp->f_pos=0;

136ret=0;

137break;

138default:

139P_DEBUG("errorcmd!n");

140ret=-EINVAL;

141break;

142}

143

144returnret;

145}

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

3)应用程序的验证:

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

[root:2nd]#insmodtest.ko

major[253]minor[0]

hellokernel

[root:2nd]#mknod/dev/testc2530

[root:2nd]#./app

[test_write]write10bytes,cur_size:[10]

[test_write]kbufis[xiaobai]

read:Nosuchdeviceoraddress

五、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#defineTEST_MAGIC'x'//定义幻数

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

6

7#defineTEST_CLEAR_IO(TEST_MAGIC,1)

8#defineTEST_OFFSET_IO(TEST_MAGIC,2)

9

10#endif

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

原因有二:

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

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

2)更新test_ioctl

122inttest_ioctl(structinode*node,structfile*filp,unsignedintcmd,unsignedlongarg)

123{

124intret=0;

125struct_test_t*dev=filp->private_data;

126

127

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

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

130

131switch(cmd){

132caseTEST_CLEAR:

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

134dev->cur_size=0;

135filp->f_pos=0;

136ret=0;

137break;

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

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

140P_DEBUG("changeoffset!n");

141ret=0;

142break;

143default:

144P_DEBUG("errorcmd!n");

145ret=-EINVAL;

146break;

147}

148

149returnret;

150}

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

3)再改一下应用程序:

1#include

2#include

3#include

4#include

5#include

6

7#include"test_cmd.h"

8

9intmain(void)

10{

11charbuf[20];

12intfd;

13intret;

14

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

16if(fd<0)

17{

18perror("open");

19return-1;

20}

21

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

23

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

25

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

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

28if(ret<0)

29{

30perror("read");

31}

32

33close(fd);

34return0;

35}

4)验证一下

[root:3rd]#insmodtest.ko

major[253]minor[0]

hellokernel

[root:3rd]#mknod/dev/testc2530

[root:3rd]#./app

[test_write]write10bytes,cur_size:[10]

[test_write]kbufis[xiaobai]

[test_ioctl]changeoffset!//更改偏移量

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

bufis[xiaobai]

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

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

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

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

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

1)用的时候才检验。

Linuxioctl的实现 linux ioctl 头文件

2)一进来ioctl就检验。

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

1)先定义个命令

1#ifndef_TEST_CMD_H

2#define_TEST_CMD_H

3

4structioctl_data{

5unsignedintsize;

6charbuf[100];

7};

8

9#defineDEV_SIZE100

10

11#defineTEST_MAGIC'x'//定义幻数

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

13

14#defineTEST_CLEAR_IO(TEST_MAGIC,1)

15#defineTEST_OFFSET_IO(TEST_MAGIC,2)

16#defineTEST_KBUF_IO(TEST_MAGIC,3)

17

18#endif

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

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

2)来个实现函数:

122inttest_ioctl(structinode*node,structfile*filp,unsignedintcmd,unsignedlongarg)

123{

124intret=0;

125struct_test_t*dev=filp->private_data;

126structioctl_dataval;

127

128

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

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

131

132switch(cmd){

133caseTEST_CLEAR:

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

135dev->cur_size=0;

136filp->f_pos=0;

137ret=0;

138break;

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

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

141P_DEBUG("changeoffset!n");

142ret=0;

143break;

144caseTEST_KBUF://修改kbuf

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

146ret=-EFAULT;

147gotoRET;

148}

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

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

151dev->cur_size=val.size;

152filp->f_pos=0;

153ret=0;

154break;

155default:

156P_DEBUG("errorcmd!n");

157ret=-EINVAL;

158break;

159}

160

161RET:

162returnret;

163}

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

3)来个应用程序

9intmain(void)

10{

11charbuf[20];

12intfd;

13intret;

14

15structioctl_datamy_data={

16.size=10,

17.buf="123456789"

18};

19

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

21if(fd<0)

22{

23perror("open");

24return-1;

25}

26

27write(fd,"xiaobai",10);

28

29ioctl(fd,TEST_KBUF,&my_data);

30

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

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

33if(ret<0)

34{

35perror("read");

36}

37

38close(fd);

39return0;

40}

4)再来验证一下:

[root:4th]#./app

[test_write]write10bytes,cur_size:[10]

[test_write]kbufis[xiaobai]

[test_read]read10bytes,cur_size:[0]

bufis[123456789]//成功!

注:类似copy_xx_user的函数含有put_user、get_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

4structioctl_data{

5unsignedintsize;

6charbuf[100];

7};

8

9#defineDEV_SIZE100

10

11#defineTEST_MAGIC'x'//定义幻数

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

13

14#defineTEST_CLEAR_IO(TEST_MAGIC,1)

15#defineTEST_OFFSET_IO(TEST_MAGIC,2)

16#defineTEST_KBUF_IOW(TEST_MAGIC,3,structioctl_data)

17

18#endif

这里终于要用_IOW了!

2)实现ioctl

122inttest_ioctl(structinode*node,structfile*filp,unsignedintcmd,unsignedlongarg)

123{

124intret=0;

125struct_test_t*dev=filp->private_data;

126

127

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

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

130

131if(_IOC_DIR(cmd)&_IOC_READ)

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

133elseif(_IOC_DIR(cmd)&_IOC_WRITE)

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

135if(!ret)return-EFAULT;

136

137switch(cmd){

138caseTEST_CLEAR:

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

140dev->cur_size=0;

141filp->f_pos=0;

142ret=0;

143break;

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

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

146P_DEBUG("changeoffset!n");

147ret=0;

148break;

149caseTEST_KBUF://修改kbuf

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

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

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

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

154filp->f_pos=0;

155ret=0;

156break;

157default:

158P_DEBUG("errorcmd!n");

159ret=-EINVAL;

160break;

161}

162

163returnret;

164}

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

3)再来个应用程序:

9intmain(void)

10{

11charbuf[20];

12intfd;

13intret;

14

15structioctl_datamy_data={

16.size=10,

17.buf="123456789"

18};

19

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

21if(fd<0)

22{

23perror("open");

24return-1;

25}

26

27write(fd,"xiaobai",10);

28

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

30if(ret<0)

31{

32perror("ioctl");

33}

34

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

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

37if(ret<0)

38{

39perror("read");

40}

41

42close(fd);

43return0;

44}

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

[root:5th]#./app

[test_write]write10bytes,cur_size:[10]

[test_write]kbufis[xiaobai]

[test_read]read10bytes,cur_size:[0]

bufis[123456789]

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

修改应用程序一样代码:

29ret=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_ok。

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

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

七、总结:

这次讲了ioctl的实现:

1)命令是怎么定义。

2)参数怎么传递。

  

爱华网本文地址 » http://www.413yy.cn/a/25101013/149196.html

更多阅读

怎么挑选好的文玩核桃狮子头 怎样挑选文玩核桃

怎么挑选好的文玩核桃狮子头——简介文玩核桃就是老年人经常在手里转动的野山核桃。这种野山核桃没有榨油的经济价值,但被老年人利用起来揉手,能起到健身和保健作用。核桃经过数十年的揉搓,会变的又红又亮,成为老年人所珍爱的玩物,所以古

收藏夹的网址保存在哪个文件夹里 收藏夹的网址怎么导出

收藏夹的网址保存在哪个文件夹里——简介本例主要讲解怎么在电脑上找到收藏夹从而对其进行备份,电脑使用熟练的朋友可以直接看方法2(快速找到收藏夹的位置)。收藏夹的网址保存在哪个文件夹里——工具/原料电脑收藏夹的网址保存在哪个

如何安装下载的ISO系统镜像文件? iso镜像系统如何安装

如何安装下载的ISO系统镜像文件?——简介网上下载的系统镜像文件,如果是ISO格式的,我们不用U盘也不要刻录成光盘就可以直接安装系统,用什么方法安装呢?如何安装下载的ISO系统镜像文件?——工具/原料硬件:电脑如何安装下载的ISO系统镜像文

Jsp中分页功能的实现 jsp 实现分页查询

分页查询功能一直是web编程中常用的技术,如何实现可重复使用而又简单的分页技术呢,下面的代码可以提供一些参考,实现用户列表的分页显示,当其它数据需分页显示时,可以复用其中的分页对象(SplitPage.java),然后提供实现dao接口的类.

声明:《Linuxioctl的实现 linux ioctl 头文件》为网友我活得很好分享!如侵犯到您的合法权益请联系我们删除