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-30 | 00-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-0 | function 命令功能 范围: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 4)
count = 4;
copy_from_user(pdata, (const void __user *)arg, count);
for(i=0;i 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;
}