内核中对底层设备操作完全可以通过read、write接口来实现,在linux 2.2之前都是没有ioctl接口的,2.4以后才引入ioctl接口。
典故(据说),以前在操作软盘时,需要弹出光盘时命令为eject,可以通过write写这个字符串来传输这个指令,但是此时,如果要往软盘中写入”eject”字符串时就出现了歧义的问题。虽然可以通过附加一些代码绕过这个问题,但是不是严谨的开发者的习惯。所以出现了iotcl接口。
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
/*
inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选。
*/
这个里面主要的就是这个cmd,驱动开发人员定义好这个cmd,在接口内部就是根据cmd做的一个分支处理。
在Linux核心中是这样定义一个命令码的:
魔数:只是一个数字,位宽为8位。在驱动中直接定义数字,大多是一字符的形式定义的,不过这个也是在8位位宽的数字范围内。具体可以参考内核源码中的文档ioctl-number.txt.
序号:就是这个type中的序号,比如有些字符在内核中已经用了,但是对应的序号并没有用完。自己定义时可以继续跟着在后面定义。
方向:数据传送的方向,如果这个特殊的命令涉及数据传送. 可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (数据在2个方向被传送). 数据传送是从应用程序的观点来看待的; _IOC_READ 意思是从设备读, 因此设备必须写到用户空间. 注意这个成员是一个位掩码, 因此 _IOC_READ 和 _IOC_WRITE 可使用一个逻辑 AND 操作来抽取。
数据尺寸:就是传输数据的大小。
最终,这个命令其实就是一个特殊的数字,在整个内核中保证和其他驱动所使用的命令不相同就行了。所以,可以自己随便写一个特殊的数字。但是为了保持规范,就有了这个标准。
ioctl命令详解
这里常用的几个
//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送宏
在ioctl函数实现中可以用以下一下两个宏函数来协助进行参数判断
_IOC_TYPE(cmd)
用来判断cmd的类型,就是跟定义的魔术进行比对,看是否属于次设备的ioctl的命令类型。
_IOC_NR(cmd)
用来判断命令的序号。防止出现定义的序号外的命令。
_IOC_DIR(cmd)
用来判断命令的读写方向。
_IOC_SIZE(cmd)
用来判断命令中参数的大小。
在具体实现中,用于传输数据的接口函数有四个,这几个接口里面都已经对参数的可读写操作性做了检测,所以不用再调用access_ok来检测了。
copy_from_user
copy_to_user
get_user
put_user
需要检测的接口
__get_user
__put_user
检测函数access_ok。
示例:
命令定义:
#define FREG_IOCTL_MAGIC 'f'
#define FREG_IOCTL_S_INDEX 0x20
#define FREG_IOCTL_NUM 2
/* 定义IOCTL的命令 */
#define FREG_IOCTL_READ _IOR(FREG_IOCTL_MAGIC, FREG_IOCTL_S_INDEX, int)
#define FREG_IOCTL_WRITE _IOW(FREG_IOCTL_MAGIC, FREG_IOCTL_S_INDEX + 1, int)
驱动中ioctl接口实现:
static long freg_ioctl(struct file* filp, unsigned int cmd, unsigned long val)
{
long ret = 0;
struct fake_reg_dev* dev = filp->private_data;
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
/* 检测命令的有效性 */
if (_IOC_TYPE(cmd) != FREG_IOCTL_MAGIC) {
printk(KERN_ERR"[%s]ioctl cmd err.\r\n", __func__);
ret = -EINVAL;
goto out;
}
if (_IOC_NR(cmd) < FREG_IOCTL_S_INDEX || _IOC_NR(cmd) >= (FREG_IOCTL_S_INDEX + FREG_IOCTL_NUM)) {
printk(KERN_ERR"[%s] ioctl cmd index err.\r\n", __func__);
ret = -EINVAL;
goto out;
}
switch (cmd) {
case FREG_IOCTL_WRITE:
printk(KERN_INFO"ioctl_write.\r\n");
if (copy_from_user(&(dev->val), (void*)&val, _IOC_SIZE(cmd))) {
printk(KERN_ERR"[%s]copy from user err.\r\n", __func__);
ret = -EFAULT;
goto out;
}
break;
case FREG_IOCTL_READ:
printk(KERN_INFO"ioctl_read.\r\n");
if (copy_to_user((void*)&val, &(dev->val), _IOC_SIZE(cmd))) {
printk(KERN_ERR"[%s]copy from user err.\r\n", __func__);
ret = -EFAULT;
goto out;
}
break;
default:
ret = -EINVAL;
break;
}
out:
up(&(dev->sem));
return ret;
}
应用程序中使用:
fd = open("/dev/freg", O_RDWR);
ioctl(fd, FREG_IOCTL_READ, &val);