ioctl的驱动接口一般是作用在一些标准接口无法实现的功能。如和主控芯片链接的很多外设ic,主控对这些芯片的功能设置以及状态的获取等。所以 ioctl 接口可以看成是系统给我们进行功能扩展的的专用接口。
系统调用接口原型:
int ioctl(int d, int request, ...);这个函数是一个可变参数函数,最少需要2个参数
参数: d: 是文件描述符号 request: 通常是 cmd 。dongjieko ...:可变参数,可以有,也可以没有,根据 request 情况而定。 示例: 一个命令: 所有灯开 -- 只需要一个cmd ,不需要其他参数 一个命令:指定灯开 -- 需要一个cmd ,还需要知道操作哪个灯的, 这种情况下就需要第3个参数。 返回值: 成功:通常是返回 0, 如果是非标准的cmd,返回是用户自定义的正数。 如,可以使用这个接口实现llseek功能。 失败:返回 的是-1;
驱动程序 ioctl 接口原型: 3.0以上的内核,(2。6内核是 ioctl) long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg); file: 文件描述通过VFS转换而来 cmd:就是应用程序传递下来的 request 参数 arg: 对应于应用程序传递下第三个参数(变参);
如以下的驱动实例:
//ioctl(fd,cmd,arg) long chrdev_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long arg) { switch ( cmd ) { case 0 : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); break; case 1 : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); break; case 2 : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); break; case 3 : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); break; case 4 : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); break; default: printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); return - EINVAL; } return arg; }比如:应用程序的代码:
for(i=0;i<6;i++) { int ret ; ret = ioctl(fd,i,10+i); //args 从10开始 sleep(1); printf("\nret = %d\n",ret); }安装模块后执行应用程序输出: [root@/home]# ./app [ 8698.345000] line:102,cmd:0,arg:10 ret = 10 line:105,cmd:1,arg:11 ret = 11 //此处应该是12,但是没有执行到。 ret = -1 line:111,cmd:3,arg:13 ret = 13 line:114,cmd:4,arg:14 ret = 14 line:117,cmd:5,arg:15 ret = -1
结果: 1。验证以上所说的参数对应关系 2。验证以上所的返回值 3。当cmd的值为2时,驱动程序没有执行!
重点分析一下第3个现象: ioctl接口比较特殊,不像其他接口通过fd直接找到驱动, ioclt 接口的cmd值在内核中有规范。不是随便可以使用一个数字就可以了。有特定的编码规则。 当cmd值和系统一些预定义命令相同时,代码所执行不会是我们的驱动代码中的ioctl,而是被拦截了,去执行相同的预定义命令去了。 上面的2这个值就属于这种现象。。。 从2。6内核到现在,cmd为2的值不能使用的。 要想安全使用ioctl接口,必须使用内核规定的定义方法来定义cmd值。
内核文档 Ioctl-decoding.txt (documentation\ioctl) 有说明:
bit含义31-3000-no parameters: uses _IO macro系统调用 ioctl ,当cmd值的31:30为00时候,应该 ioctl(fd,cmd),不能有3个参数,否则[可能]和其他命令冲突。
10 - read: _IOR代表想通过ioctl从驱动中读取数据回来,存放在 ioctl(fd,cmd,arg)中的argr所代表的内存空间中(arg应该是一个可写内存地址)。
01 - write: _IOW代表想通过ioctl从向驱动中写入数据,数据存放在 ioctl(fd,cmd,arg)中的arg所指向内存空间中(arg应该是一个可读内存地址)。
11 - read/write: _IOWR代表想通过ioctl从向驱动中写入数据,数据存放在 ioctl(fd,cmd,arg)中的arg所指向内存空间中(arg应该是一个可读内存地址)。
29-16就是参数大小 14位,最多只能传递16K数据
当 cmd 31-30 不为0时候表示 调用方法 ioctl(fd,cmd,arg) ,表示有数据传递。
15-8驱动 魔数/幻数,范围00~FF用来标识一个驱动,原则上讲一个驱动有惟一值,只要这个值不同,就不会和其他命令冲突。
内核中已经占用大部分的魔数。原则可以由用户定义。内核文档 Ioctl-number.txt (documentation\ioctl)告诉数字已经被使用了。
7-0function 命令功能 范围:0~255
其实这个才是真正的命令码。像前面的0,1,2,3
示例: 定义一个关灯命令: #define LED_X_OFF 1<<30 | 1<<16 | 'A' <<8 | 0 定义一个开灯命令: #define LED_X_ON 1<<30 | 1<<16 | 'A' <<8 | 1 定义一个全部灯关命令: #define LED_ALL_OFF 0<<30 | 0<<16 | 'A' <<8 | 2 定义一个全部开灯命令: #define LED_ALL_ON 0<<30 | 0<<16 | 'A' << 8 | 3 定义一个读指定灯状态命令: #define LED_X_STATUS 2<<30 | 1<<16 | 'A' <<8 | 4
这样太麻烦了,内核已经提供了相应的宏合成命令: /* used to create numbers */ _IO(type,nr) :定义一个没有参数的命令 _IOR(type,nr,size) :定义一个读方向的命令 _IOW(type,nr,size) :定义一个写方向的命令 _IOWR(type,nr,size) :定义一个数据双向的命令 type:就是魔数 nr:功能码 size:参数大小,其实应该传递参数类型。(非指针)
也提供反向分解代码: /* used to decode ioctl numbers.. */ #define _IOC_DIR(nr) 取出方向值 #define _IOC_TYPE(nr) 取魔数 #define _IOC_NR(nr) 取出功能码 #define _IOC_SIZE(nr) 取出大小
所在上面的定义可以修改: 示例: 定义一个关灯命令: #define LED_MAGI 'A' #define LED_MAX 4 #define LED_X_OFF _IOW(LED_MAGI,0,char) // 1<<30 | 1<<16 | 'A' <<8 | 0 定义一个开灯命令: #define LED_X_ON _IOW(LED_MAGI,1,char) // 1<<30 | 1<<16 | 'A' <<8 | 1 定义一个全部灯关命令: #define LED_ALL_OFF _IO(LED_MAGI,2) //0<<30 | 0<<16 | 'A' <<8 | 2 定义一个全部开灯命令: #define LED_ALL_ON _IO(LED_MAGI,3) // 0<<30 | 0<<16 | 'A' << 8 | 3 定义一个读指定灯状态命令: #define LEDS_STATUS _IOR(LED_MAGI,4,int) // 2<<30 | 1<<16 | 'A' <<8 | 4 //每字节存放一个灯状态, 驱动中有4个灯。
以上内容单独定义在一个.h文件中,然后把drv,app放在在相同目录下。 drv,app都包含相同的头文件 就可以了。
应用程序使用:
int main() { .... char led_nr; int state; //关指定灯 led_nr =2; ioctl(fd,LED_X_OFF,&lednr); //关所有灯 ioctl(fd,LED_ALL_OFF); //读灯状态 ioctl(fd,LEDS_STATUS,&state); }驱动代码:
//ioctl(fd,cmd,arg) long chrdev_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long arg) { char *pdata; int count = _IOC_SIZE(cmd) ; //取得复制数量 //出于效率还要做一个判断 if(_IOC_TYPE(cmd) != LED_MAGI) return -1; if(_IOC_NR(cmd) > LED_MAX) return -1; switch ( cmd ) { case LED_X_OFF : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); pdata = kmalloc( _IOC_SIZE(cmd)); //分配缓冲区 if(_IOC_SIZE(cmd) > 4) count = 4; copy_from_user(pdata, (const void __user *)arg, count); for(i=0;i<count;i++) { rGPM4DAT |= (1 << (0 + pdata[i])); /*灭 */ } kfree(pdata); case LED_X_ON : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); if(_IOC_SIZE(cmd) > 4) count = 4; copy_from_user(pdata, (const void __user *)arg, count); for(i=0;i<count;i++) { rGPM4DAT &= ~ (1 << (0 + pdata[i])); /* 亮 */ } break; case LED_ALL_OFF : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); //for 可以 rGPM4DAT |= (1 << (0 + 0); /* 灭 */ rGPM4DAT |= (1 << (0 + 1); /* 灭 */ rGPM4DAT |= (1 << (0 + 2); /* 灭 */ rGPM4DAT |= (1 << (0 + 3); /* 灭 */ break; case LED_ALL_ON: printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); //for 可以 rGPM4DAT &= ~ (1 << (0 + 0); /* 灭 */ rGPM4DAT &= ~ (1 << (0 + 1); /* 灭 */ rGPM4DAT &= ~ (1 << (0 + 2); /* 灭 */ rGPM4DAT &= ~ (1 << (0 + 3); /* 灭 */ break; case LEDS_STATUS : printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); if(_IOC_SIZE(cmd) > 4) count = 4; pdata = kmalloc( count); //分配缓冲区 for(i=0;i< count;i++) { pdata[i] = !( rGPM4DAT & 1<<(0 + i) ); } copy_to_user( arg, pdata, _IOC_SIZE(cmd)); kfree(pdata); break; default: printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg); return - EINVAL; } return 0; }