ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制。
如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码(cmd)告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
二、ioctl相关函数原型介绍- 在应用程序中
int ioctl(int fd, unsigned long cmd, ...)
-
fd:文件操作符
-
cmd:交互协议,设备驱动将根据 cmd 执行对应操作
-
…:可变参数 arg
-
在驱动程序中
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
unlocked_ioctl,顾名思义,应该在无大内核锁(BKL)的情况下调用;compat_ioctl,compat 全称 compatible(兼容的),主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,也是在无大内核锁的情况下调用。在字符设备驱动开发中,一般情况下只要实现 unlocked_ioctl 函数即可。
三、ioctl 用户与驱动之间的协议前文提到 ioctl 方法第二个参数 cmd 为用户与驱动的 “协议”,理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:
- dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据。
- type(device type),是个0-0xff的数或者一个字符,占8bit。这个数是用来区分不同的驱动的,像设备号一样,内核有一个文档(
Documentation/ioctl/ioctl-number.txt
)给出一些推荐的或者已经被使用的幻数。 - nr(number),命令编号/序数,8 bit,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增。
- size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,只需要填参数的类型,如int,函数就会帮你检测类型的正确性然后赋值sizeof(int)。
为了方便我们会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令,函数原型如下所示:
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
_IO: 定义不带参数的 ioctl 命令
_IOW: 定义带写参数的 ioctl 命令(copy_from_user)
_IOR: 定义带读参数的ioctl命令(copy_to_user)
_IOWR: 定义带读写参数的 ioctl 命令
四、下面举例说明ioctl相关函数怎么使用(伪代码)
- 驱动程序
#define CLOSE_CMD _IO(0XEF, 1) //关闭命令
#define OPEN_CMD _IO(0XEF, 2) //打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) //设置周期
static const struct file_operations timerdev_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_ioctl,
.release = timer_release,
};
static long timer_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case CLOSE_CMD:
break;
case OPEN_CMD:
break;
case SETPERIOD_CMD:
break;
}
return ret;
}
- 应用程序
#define CLOSE_CMD _IO(0XEF, 1) //关闭命令
#define OPEN_CMD _IO(0XEF, 2) //打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) //设置周期
/* 循环读取 */
while(1) {
printf("Input CMD:");
ret = scanf("%d", &cmd);
if(ret !=1 ) {
gets(str); /* 防止卡死 */
}
if(cmd == 1) { /* 关闭 */
ioctl(fd, CLOSE_CMD, &arg);
} else if(cmd == 2) { /* 打开 */
ioctl(fd, OPEN_CMD, &arg);
} else if(cmd == 3) { /* 设置周期 */
printf("Input Timer period:");
ret = scanf("%d", &arg);
if(ret !=1 ) {
gets(str);
}
ioctl(fd, SETPERIOD_CMD, &arg);
}
}
当我们在应用程序调用ioctl函数的时候,和我们之前说过的open、write等函数一样,它是通过一个file_operations 类型的结构体实现与驱动程序中的timer_ioctl函数联系上的。我们在应用程序中给ioctl函数传递cmd等参数,此时cmd能够通过unlocked_ioctl 传递给驱动程序中的timer_ioctl函数,该函数接收到cmd等参数之后就可以进行一系列的操作。具体执行什么操作,是由我们自己决定的,也即看我们想要这些cmd分别代表什么意思。
参考文章:
linux 内核 - ioctl 函数详解
linux ioctl()详解