一,函数原型
用户空间的ioctl函数原型
int ioctl(int fd, unsigned long cmd, ...);驱动程序里的ioctl函数原型
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
二,实现ioctl函数的方法
1,定义命令:
定义 ioctl 命令号的正确方法使用 4 个位段, 它们有下列的含义. 这个列表中介绍的新符号定义在 <linux/ioctl.h>.type
魔数. 只是选择一个数(在参考了 ioctl-number.txt之后)并且使用它在整个驱动中. 这个成员是 8 位宽(_IOC_TYPEBITS).
number
序(顺序)号. 它是 8 位(_IOC_NRBITS)宽.
direction
数据传送的方向,如果这个特殊的命令涉及数据传送. 可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (数据在2个方向被传送). 数据传送是从应用程序的观点来看待的; _IOC_READ 意思是从设备读, 因此设备必须写到用户空间. 注意这个成员是一个位掩码, 因此 _IOC_READ 和 _IOC_WRITE 可使用一个逻辑 AND 操作来抽取.
size
涉及到的用户数据的大小. 这个成员的宽度是依赖体系的, 但是常常是 13 或者 14 位.
头文件 <asm/ioctl.h>, 它包含在 <linux/ioctl.h> 中, 定义宏来帮助建立命令号, 如下:
_IO(type,nr) (给没有参数的命令)
_IOR(type, nre, datatype) (给从驱动中读数据的)
_IOW(type,nr,datatype) (给写数据)
_IOWR(type,nr,datatype)(给双向传送).
type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到.
这个头文件还定义宏, 可被用在你的驱动中来解码这个号: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), 和 _IOC_SIZE(nr).
例子
/* 定义幻数 */
#define MEMDEV_IOC_MAGIC 'k'
/* 定义命令 */
#define MEMDEV_IOCPRINT _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)
2,ioctl函数实现
(1)返回值
当命令号不能匹配switch语句中设备所支持的命令时候,应该返回-EINVAL(2)参数的使用
如果arg参数是一个整数则可直接使用,但是如果是一个指针,则可能需要检查用户空间是否可读或可写
不需要检测的函数:
copy_from_user ,copy_to_user,put_user,get_user
需要检测的函数
__put_user,__get_user
注意以下函数参数方向:put_user(datum, ptr),__put_user(datum, ptr)这些宏定义写 datum 到指针ptr用户空间
get_user(local, ptr),__get_user(local, ptr)这些宏定义用来从ptr用户空间接收单个数据给local。
int access_ok(int type, const void *addr, unsigned long size);
第一个参数应当是 VERIFY_READ 或者 VERIFY_WRITE, 依据这个要进行的动作是否是读用户空间内存区或者写它. addr 参数 持有一个用户空间地址, size 是一个字节量成功返回布尔值1,失败返回0,如果返回失败,则ioctl函数应当返回-EFAULT
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
(3)命令实现
switch(cmd)
{
/* 打印当前设备信息 */
case MEMDEV_IOCPRINT:
printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
break;
/* 获取参数 */
case MEMDEV_IOCGETDATA:
ioarg = 1101;
ret = __put_user(ioarg, (int *)arg);
break;
/* 设置参数 */
case MEMDEV_IOCSETDATA:
ret = __get_user(ioarg, (int *)arg);
printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
break;
default:
return -EINVAL;
}