IOCTL函数的实现
一、ioctl的简介:
虽然在文件操作结构体"struct file_operations"中有很多对应的设备操作函数,但是有些命令是实在找不到对应的操作函数。如CD-ROM的驱动,想要一个弹出光驱的操作,这种操作并不是所有的字符设备都需要的,所以文件操作结构体也不会有对应的函数操作。
出于这样的原因,ioctl就有它的用处了————一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。所以,ioctl函数里面都实现了多个的对硬件的操作,通过应用层传入的命令来调用相应的操作。
来个图来说一下应用层与驱动函数的ioctl之间的联系:
上面的图可以看出,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一样返回。
当然,不返回也是可以的。
二、ioctl的cmd
说白了,cmd就是一个数,如果应用层传来的数值在驱动中有对应的操作,这样就就可以了。
来个最简单的ioctl实现:3rd_char_4/1st
1)要先定义个命令,就用一个简单的0,来个命令的头文件,驱动和应用函数都要包含这个头文件:
/*test_cmd.h*/
1 #ifndef _TEST_CMD_H
2 #define _TEST_CMD_H
3
4 #define TEST_CLEAR 0
5
6 #endif /*_TEST_CMD_H*/
2)驱动实现ioctl:
命令TEST_CLEAR的操作就是清空驱动中的kbuf。
122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg)
123 {
124 int ret = 0;
125 struct _test_t *dev = filp->private_data;
126
127 switch(cmd){
128 case TEST_CLEAR:
129 memset(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("error cmd!\n");
136 ret = - EINVAL;
137 break;
138 }
139
140 return ret;
141 }
3)再来个应用程序:
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <sys/ioctl.h>
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返回出错,我修改了驱动的read、write函数的开始时的第一个
判断,一看就知道了。
4)验证一下:
[root: 1st]# insmod test.ko
major[253] minor[0]
hello kernel
[root: 1st]# mknod /dev/test c 253 0
[root: 1st]# ./app
<kernel>[test_write]write 10 bytes, cur_size:[10]
<kernel>[test_write]kbuf is [xiao bai]
read: No such device or address //哈哈!出错了!因为没数据读取。
按照上面的方法来定义一个命令是完全可以的,但内核开发人员发现这样有点不对劲。
如果有两个不同的设备,但它们的ioctl的cmd却一样的,哪天有谁不小心打开错了,并且调用ioctl,这样就完蛋了。因为这个文件里面同样有cmd对应实现。
为了防止这样的事情发生,内核对cmd又有了新的定义,规定了cmd都应该不一样。
三、ioctl中的cmd
一个cmd被分为了4个段,每一段都有各自的意义,cmd的定义在<Linux/ioctl.h>。注:但实际上<linux/ioctl.h>中只是包含了<asm/ioctl.h>,这说明了这是跟平台相关的,ARM的定义在<arch/arm/include/asm/ioctl.h>,但这文件也是包含别的文件<asm-generic/ioctl.h>,千找万找,终于找到了。
在<asm-generic/ioctl.h>中,cmd拆分如下:
解释一下四部分,全部都在<asm-generic/ioctl.h>和ioctl-number.txt这两个文档有说明。
1)幻数:说得再好听的名字也只不过是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不同的驱动的,像设备号申请的时候一样,内核有一个文档给出一些推荐的或者已经被使用的幻数。
/*Documentation/ioctl/ioctl-number.txt*/
164 'w' all CERN SCI driver
165 'y' 00-1F packet based user level communications
166 <mailto:zapman@interlan.NET>
167 'z' 00-3F CAN bus card
168 <mailto:hdstich@connectu.ulm.circular.de>
169 'z' 40-7F CAN bus card
170 <mailto:oe@port.de>
可以看到'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("error cmd!\n");
140 ret = - EINVAL;
141 break;
142 }
143
144 return ret;
145 }
每个参数的传入都会先检验一下幻数还有序数是否正确。
3)应用程序的验证:
结果跟上一个完全一样,因为命令的操作没有修改
[root: 2nd]# insmod test.ko
major[253] minor[0]
hello kernel
[root: 2nd]# mknod /dev/test c 253 0
[root: 2nd]# ./app
<kernel>[test_write]write 10 bytes, cur_size:[10]
<kernel>[test_write]kbuf is [xiao bai]
read: No such device or address
五、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 #define TEST_MAX_NR 2 //定义命令的最大序数
6
7 #define TEST_CLEAR _IO(TEST_MAGIC, 1)
8 #define TEST_OFFSET _IO(TEST_MAGIC, 2)
9
10 #endif /*_TEST_CMD_H*/
这里有人会问了,明明你是要传入参数,为什么不用_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 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 case TEST_OFFSET: //根据传入的参数更改偏移量
139 filp->f_pos += (int)arg;
140 P_DEBUG("change offset!\n");
141 ret = 0;
142 break;
143 default: /*命令错误时的处理*/
144 P_DEBUG("error cmd!\n");
145 ret = - EINVAL;
146 break;
147 }
148
149 return ret;
150 }
TSET_OFFSET命令就是根据传参更改偏移量,不过这里要注意一个问题,那就是参数的类型,驱动函数必须要知道从应用传来的参数是什么类型,不然就没法使用。在这个函数里,从应用层传来的参数是int,因此在驱动中也得用int。
3)再改一下应用程序:
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <sys/ioctl.h>
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("<app> 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]# insmod test.ko
major[253] minor[0]
hello kernel
[root: 3rd]# mknod /dev/test c 253 0
[root: 3rd]# ./app
<kernel>[test_write]write 10 bytes, cur_size:[10]
<kernel>[test_write]kbuf is [xiao bai]
<kernel>[test_ioctl]change offset! //更改偏移量
<kernel>[test_read]read 10 bytes, cur_size:[0] //没错误,成功读取!
<app> buf is [xiao bai]
上面的传参很简单把,接下来说一下以指针传参。
考虑到参数不可能永远只是一个正数这么简单,如果要传多一点的东西,譬如是结构体,那就得用上指针了。
六、ioctl中的arg之指针传参。
一讲到从应用程序传来的指针,就得想起我邪恶的传入了非法指针的例子。所以,驱动程序中任何与应用层打交道的指针,都得先检验指针的安全性。
说到这检验又有两种方法:
1)用的时候才检验。
2)一进来ioctl就检验。
copy_from_user函数介绍:
函数负责在用户空间和内核空间传递数据
static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n) { if (access_ok(VERIFY_READ, from, n)) n = __arch_copy_from_user(to, from, n); else /* security hole - plug it */ memzero(to, n); return n; }
先看函数的三个参数:*to是内核空间的指针,*from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。
这个函数从结构上来分析,其实都可以分为两个部分:
- 首先检查用户空间的地址指针是否有效;
- 调用__arch_copy_from_user函数。
先说用的时候检验,说白了就是用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 #define TEST_MAX_NR 3 //定义命令的最大序数
13
14 #define TEST_CLEAR _IO(TEST_MAGIC, 1)
15 #define TEST_OFFSET _IO(TEST_MAGIC, 2)
16 #define TEST_KBUF _IO(TEST_MAGIC, 3)
17
18 #endif /*_TEST_CMD_H*/
这里有定义多了一个函数,虽然这个命令是涉及到了指针的传参,但我还是_IOW,还是那一句,现在还不需要用上。
该命令的操作是传进一个结构体指针,驱动根据结构体的内容修改kbuf和cur_size和偏移量。
2)来个实现函数:
122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg)
123 {
124 int ret = 0;
125 struct _test_t *dev = filp->private_data;
126 struct ioctl_data val;
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:
134 memset(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("change offset!\n");
142 ret = 0;
143 break;
144 case TEST_KBUF: //修改kbuf
145 if(copy_from_user(&val, (struct ioctl_data *)arg, sizeof(struct ioctl_data))){
146 ret = - EFAULT;
147 goto RET;
148 }
149 memset(dev->kbuf, 0, DEV_SIZE);
150 memcpy(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("error cmd!\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("<app> 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
<kernel>[test_write]write 10 bytes, cur_size:[10]
<kernel>[test_write]kbuf is [xiao bai]
<kernel>[test_read]read 10 bytes, cur_size:[0]
<app> buf is [123456789] //成功!
注:类似copy_xx_user的函数含有put_user、get_user等,我就不细说了。
七、总结:
这次讲了ioctl的实现:
1)命令是怎么定义。
2)参数怎么传递。
原文地址:
http://blog.csdn.net/styyzxjq2009/article/details/8023501
IOCTL函数的实现相关推荐
- ioctl()函数详解
我这里说的ioctl函数是指驱动程序里的,因为我不知道还有没有别的场合用到了它,所以就规定了我们讨论的范围.写这篇文章是因为我前一阵子被ioctl给搞混了,这几天才弄明白它,于是在这里清理一下头脑. ...
- 设备控制接口(ioctl 函数)
[转]Linux设备控制接口 序言 设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管理和控制设备的接口.我们前面的"Hello World"驱动程序已经可以提 ...
- 第三章:ioctl 函数详解
ioctl 函数 功能 : 是各种i/o操作的杂务箱 , 其他 i/o 函数所不能进行的操作 均可以由该函数来进行 终端I/O是 ioctl函数的 最大使用方面 , 使用ioctl进行终端I/O的 ...
- ioctl函数_Linux字符设备驱动之实现ioctl文件操作
在前面的文章里面已经写了如何实现read.write操作,但是对于有些驱动仅仅靠read和write是不够的,比如对于一个led驱动,我们用户想控制led进行全亮.全灭.流水灯等等操作,那么显然使用i ...
- Linux驱动设计ioctl函数的cmd参数不能为2
Linux驱动程序设计的时候偶然发现的ioctl()函数的cmd参数不能为2,如果为2,ioctl()函数返回-1,网上说就是这样的,正常,不知道为什么,stack overflow上有一个外国学友的 ...
- 获取网络接口信息——ioctl()函数与结构体struct ifreq、 struct ifconf
http://blog.csdn.net/windeal3203/article/details/39320605 Linux 下 可以使用ioctl()函数 以及 结构体 struct ifreq ...
- Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析
解析完 open.close.read.write 四个函数后,终于到我们的 ioctl() 函数了 一. 什么是ioctl ioctl是设备驱动程序中对设备的I/O通道进行管理的函数.所谓对I/O通 ...
- Linux绘图函数与驱动,Linux中与驱动相关的ioctl函数
一: ioctl函数的作用 ioctl用于向设备发控制和配置命令 ,有些命令也需要读写一些数据,但这些数据是不能用read/write读写的,称为Out-of-band数据.也就是说,read/ ...
- ioctl 函数 参数 详解
2019独角兽企业重金招聘Python工程师标准>>> ioctl 函数 参数 详解 2009-04-24 11:55 ioctl函数 本函数影响由fd参数引用的一个打开的文件. # ...
- 主题:ioctl函数详细说明
ioctl 函数 本函数影响由fd 参数引用的一个打开的文件. #include<unistd.h> int ioctl( int fd, int request, .../* void ...
最新文章
- MySql 扩展存储引擎
- micropython安装ros_ROS2与STM32入门教程-microROS的linux版本
- mysql 查看trige_mysql查看所有存储过程,函数,视图,触发器,表
- 中科院遗传所钱文峰组发表新冠病毒源于自然界的科学证据
- char*,wchar_t*,CString和BSTR之间的转换
- 几张动态图弄懂递归,二叉树,二分查找简短算法
- 图像处理、语音处理的应用及前沿技术_自主知识产权的2微米级3D视觉相机应用于高精度和高反光物体的缺陷检测...
- 彩超中ri是什么意思_胎儿b超ri是什么意思
- Linux输入子系统浅析
- Simple-RNN with Keras
- 怎样卸载干净Oracle
- 快乐又刺激的点名小程序:滚动点名+BGM
- 【人工智能算法】算法基础之K均值聚类算法
- jsp:include和%@include file=%有什么区别
- SCI论文投稿信(Cover Letter)的写法及模版(转
- 目标检测 YOLOv5 - 卷积层和BN层的融合
- linux查看cpu是否支持虚拟化,如何知道CPU是否支持虚拟化技术(VT)
- Linux cma内存的使用
- 论文结论的写作类型和注意事项
- 金三银四的 Vue 面试准备
热门文章
- 误差的基本性质与处理matlab实验,基于matlab的误差数据处理实验报告.doc
- 陕西计算机在职研究生院校排名,陕西在职研究生哪个学校好上
- 百度指数和股票的相关性
- 家庭局域网_第二篇如何让电信IPTV与路由器同时上网(组建自己家庭局域网专题)...
- 获取本机局域网IP地址
- Android 对ListView和RecyclerView的两个BaseAdapter封装分享
- ims应用服务器,IMS业务应用服务器部署方式分析.PDF
- selenium 自动化测试 Chrome 大于 63 版本 不能重定向问题解决办法
- 银行卡卡号归属地汇总
- 安卓逆向学习 之 KGB Messenger的writeup(1)