ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。
ioctl 的定义
用户空间的ioctl是一个可变参数函数,系统调用原型:
/*
* fd: 用户程序打开设备时使用open函数返回的文件标示符
* cmd: 用户程序对设备的控制命令
*/
int ioctl(int fd, unsigned long cmd, ...);
驱动程序的ioctl原型是:
/*
* filep: 待操作的设备文件的file结构体指针
* cmd: 用户空间传递的命令
* arg: 用户空间的数据,可能是一个地址,或者一个数值,或者啥也没有,取决于用户空间的ioctl最后的可变参数
*/
long (*unlocked_ioctl) (struct file* filep, unsigned int cmd, unsigned long arg);
在驱动程序中实现的ioctl函数体内,实际上是可以通过一个switch{case}的结构,将每一个case对应一个命令码(cmd),做出一些相应的操作。简言之就是用户空间通过调用ioctl系统调用传递cmd参数到内核空间,通过cmd的一一对应来决定命令需要进行的操作是什么。
ioctl CMD的结构与操作宏
结构
- 设备类型(type):幻数,一个字母
- 序列号(number):代表改设备的第几个指令
- 方向(direction):如果命令涉及到数据的传输,则该字段定义数据的传输方向。可以使用的值包括_IOC_NONE(没有数据传输)、_IOC_READ、_IOC_WRITE、_IOC_READ|_IOC_WRITE(双向数据传输)。数据传输是从应用程序的角度看的,也就是说IOC_READ意味着从设备中读取数据,对应驱动程序应该是往用户空间写入数据
- 数据尺寸(size):所涉及到的用户数据的大小。
规则
命令编号有着确定的规则:命令号要在系统范围内唯一。否则可能会造成错误的匹配,进而完成意想不到的错误操作。为了避免这种错误,Linux内核约定了方法为驱动程序选择ioctl编号,首先应该看看include/asm/ioctl.h和Documentation/ioctl-number.txt这两个文件。ioctl-number.txt文件中罗列了内核所使用的幻数,在选择自己的幻数的时候要避免和内核冲突。
操作宏
内核<linux/ioctl.h>头文件中定义了一组构造命令编号的宏。
_IO(type, nr) // 构造无参数的命令编号,即没有数据传递的命令
_IOR(type, nr, datatype) // 构造从驱动程序中读取数据的命令编号
_IOW(type, nr, datatype) // 用于向驱动中写入数据的命令
_IOWR(type, nr, datatype) // 用于双向传输
type和nr位字段通过参数传入,size位字段通过对datatype参数取sizeof获得。
同样也定义了一组解开位字段的宏:
_IOC_DIR(cmd) // 提取方向
_IOC_TYPE(cmd) // 提取幻数
_IOC_NR(cmd) // 提取序数
_IOC_SIZE(cmd) // 提取数据大小
对于direction字段,也有定义对应的宏:
_IOC_NONE // 值为0,没有数据传输
_IOC_READ // 值为1,从设备驱动读取数据
_IOC_WRITE // 值为2,向设备驱动写入数据
_IOC_READ | _IOC_WRITE // 双向传输
CMD 的构建
创建幻数
首先要创建幻数,我们打开ioctl-number.txt文件,先看一个很奇怪的疑问。
‘c’ all linux/cm4000_cs.h conflict!
‘c’ 00-7F linux/comstats.h conflict!
‘c’ 00-7F linux/coda.h conflict!
冲突啊!!! 幻数的本意是为了避免在错误的设备中使用正确的命令,所以不是绝对不能冲突的东西。另外就是两个驱动不同时出现在内核中,也就不会出现问题。其实很好理解,内核那么多驱动程序,如果全部不冲突,也不好做到啊。但是要尽可能的不要冲突。
创建cmd
#define CDEV_IOC_MAGIC 'x'
#define CDEV_IOC_CLEAN _IO(CDEV_IOC_MAGIC, 0)
#define CDEV_IOC_WRITE _IOW(CDEV_IOC_MAGIC, 1, int)
#define CDEV_IOC_READ _IOR(CDEV_IOC_MAGIC, 2, int)
#define CDEV_IOC_WR _IOWR(CDEV_IOC_MAGIC, 3, charp)
ioctl 应用示例
填充unlocked_ioctl函数,可以按照如下步骤进行:
- 利用_IOC_TYPE检测幻数有效性
- 利用access_ok来检测arg是否符合要求
- 利用switch case结构,定义每个cmd的处理
驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define CDEV_IOC_MAGIC 'x'
#define CDEV_IOC_CLEAN _IO(CDEV_IOC_MAGIC, 0)
#define CDEV_IOC_WRITE _IOW(CDEV_IOC_MAGIC, 1, int)
#define CDEV_IOC_READ _IOR(CDEV_IOC_MAGIC, 2, int)
#define CDEV_IOC_WR _IOWR(CDEV_IOC_MAGIC, 3, int)
static unsigned int major = 222;
static unsigned int minor = 0;
static dev_t dev_no;
static struct class* cls = NULL;
static struct device* cls_dev = NULL;
static int k_value = 1314;
static long hello_ioctl(struct file* filep, unsigned int cmd, unsigned long arg) {
int result;
printk("hello_ioctl");
if (_IOC_TYPE(cmd) != CDEV_IOC_MAGIC) {
printk("magic num error!\n");
return -ENODEV;
}
if (!access_ok((void __user *)arg, _IOC_SIZE(cmd))) {
printk("size error!\n");
return -ENODEV;
}
switch (cmd) {
case CDEV_IOC_CLEAN: {
printk("== CDEV_IOC_CLEAN ==\n");
k_value = 1314;
} break;
case CDEV_IOC_WRITE: {
printk("== CDEV_IOC_WRITE ==\n");
result = copy_from_user(&k_value, (int*)arg, sizeof(int));
printk("== CDEV_IOC_WRITE == k_value: %d\n", k_value);
} break;
case CDEV_IOC_READ: {
printk("== CDEV_IOC_READ ==\n");
result = copy_to_user((int*)arg, &k_value, sizeof(int));
} break;
case CDEV_IOC_WR: {
printk("== CDEV_IOC_WR ==\n");
result = get_user(k_value, (int*)arg);
printk("== CDEV_IOC_WR == k_value: %d\n", k_value);
k_value = 957;
result = put_user(k_value, (int*)arg);
} break;
default: {
printk("unsupport cmd:0x%x\n", cmd);
return -EINVAL;
} break;
}
return 0;
}
static struct file_operations hello_ops = {
.unlocked_ioctl = hello_ioctl,
};
static int hello_init(void) {
int result = 0;
printk("hello init enter!");
dev_no = MKDEV(major, minor);
result = register_chrdev(major, "hello", &hello_ops);
if (result < 0) {
printk("register chrdev failed! result: %d\n", result);
return result;
}
cls = class_create(THIS_MODULE, "hello_cls");
if (IS_ERR(cls) != 0) {
printk("class create failed!");
result = PTR_ERR(cls);
goto err_1;
}
cls_dev = device_create(cls, NULL, dev_no, NULL, "hello_dev");
if (IS_ERR(cls_dev) != 0) {
printk("device create failed!");
result = PTR_ERR(cls_dev);
goto err_2;
}
return 0;
err_2:
class_destroy(cls);
err_1:
unregister_chrdev(major, "hello");
return result;
}
static void hello_exit(void) {
printk("hello exit enter!");
device_destroy(cls, dev_no);
class_destroy(cls);
unregister_chrdev(major, "hello");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
用户空间代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define CDEV_IOC_MAGIC 'x'
#define CDEV_IOC_CLEAN _IO(CDEV_IOC_MAGIC, 0)
#define CDEV_IOC_WRITE _IOW(CDEV_IOC_MAGIC, 1, int)
#define CDEV_IOC_READ _IOR(CDEV_IOC_MAGIC, 2, int)
#define CDEV_IOC_WR _IOWR(CDEV_IOC_MAGIC, 3, int)
int main(int argc, char const *argv[]) {
int value = 9999;
char buffer[32] = {0};
int fd = open("/dev/hello_dev", O_RDWR);
if (ioctl(fd, CDEV_IOC_CLEAN) < 0) {
printf("ioctl CDEV_IOC_CLEAN failed!\n");
return 0;
}
if (ioctl(fd, CDEV_IOC_READ, &value) < 0) {
printf("ioctl CDEV_IOC_READ failed!\n");
return 0;
}
printf("read k_value = %d\n", value); // 1314
value = 369;
if (ioctl(fd, CDEV_IOC_WRITE, &value) < 0) {
printf("ioctl CDEV_IOC_WRITE failed!\n");
return 0;
}
if (ioctl(fd, CDEV_IOC_READ, &value) < 0) {
printf("ioctl CDEV_IOC_READ failed!\n");
return 0;
}
printf("write end, read k_value = %d\n", value); // 369
value = 9090;
if (ioctl(fd, CDEV_IOC_WR, &value) < 0) {
printf("ioctl CDEV_IOC_WR failed!\n");
return 0;
}
printf("read+write k_value = %d\n", value);
close(fd);
return 0;
}
结果
利用arg做消息分发
可以将arg定义为结构体,然后利用arg结构体中的内容做消息分发。
struct ioctl_transfer {
int type;
int value;
};
#define CDEV_IOC_MAGIC 'x'
#define CDEV_MSGSIZE(N) ((((N) * (sizeof(struct ioctl_transfer))) < (1 << _IOC_SIZEBITS)) ? ((N) * (sizeof(struct ioctl_transfer))) : 0)
#define CDEV_IOCTL_MESSAGE(N) _IOWR(CDEV_IOC_MAGIC, 0, char[CDEV_MSGSIZE(N)])
示例中根据ioctl_transfer中type的不同,做不同处理,type为X2_MAGNIFICATION则将value乘以2,type为X10_MAGNIFICATION则乘以10,代码如下。
驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define CDEV_IOC_MAGIC 'x'
struct ioctl_transfer {
int type;
int value;
};
#define X2_MAGNIFICATION 0x00F1
#define X10_MAGNIFICATION 0x00F2
static unsigned int major = 222;
static unsigned int minor = 0;
static dev_t dev_no;
static struct class* cls = NULL;
static struct device* cls_dev = NULL;
static long hello_ioctl(struct file* filep, unsigned int cmd, unsigned long arg) {
int result;
struct ioctl_transfer arg_data;
printk("hello_ioctl cmd:%u\n", cmd);
if (_IOC_TYPE(cmd) != CDEV_IOC_MAGIC) {
printk("magic num error!\n");
return -ENODEV;
}
if (!access_ok((void __user *)arg, _IOC_SIZE(cmd))) {
printk("size error!\n");
return -ENODEV;
}
if (copy_from_user(&arg_data, (void __user *)arg, sizeof(struct ioctl_transfer))) {
printk("get arg data error!\n");
return -ENODEV;
}
printk("arg.type: 0x%x, arg.value: %d\n", arg_data.type, arg_data.value);
switch (arg_data.type) {
case X2_MAGNIFICATION: {
arg_data.value *= 2;
result = copy_to_user((void __user *)arg, &arg_data, sizeof(struct ioctl_transfer));
} break;
case X10_MAGNIFICATION: {
arg_data.value *= 10;
result = copy_to_user((void __user *)arg, &arg_data, sizeof(struct ioctl_transfer));
} break;
default: {
return -ENODEV;
} break;
}
return result;
}
static struct file_operations hello_ops = {
.unlocked_ioctl = hello_ioctl,
};
static int hello_init(void) {
int result = 0;
printk("hello init enter!");
dev_no = MKDEV(major, minor);
result = register_chrdev(major, "hello", &hello_ops);
if (result < 0) {
printk("register chrdev failed! result: %d\n", result);
return result;
}
cls = class_create(THIS_MODULE, "hello_cls");
if (IS_ERR(cls) != 0) {
printk("class create failed!");
result = PTR_ERR(cls);
goto err_1;
}
cls_dev = device_create(cls, NULL, dev_no, NULL, "hello_dev");
if (IS_ERR(cls_dev) != 0) {
printk("device create failed!");
result = PTR_ERR(cls_dev);
goto err_2;
}
return 0;
err_2:
class_destroy(cls);
err_1:
unregister_chrdev(major, "hello");
return result;
}
static void hello_exit(void) {
printk("hello exit enter!");
device_destroy(cls, dev_no);
class_destroy(cls);
unregister_chrdev(major, "hello");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
应用层代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
struct ioctl_transfer {
int type;
int value;
};
#define X2_MAGNIFICATION 0x00F1
#define X10_MAGNIFICATION 0x00F2
#define CDEV_IOC_MAGIC 'x'
#define CDEV_MSGSIZE(N) ((((N) * (sizeof(struct ioctl_transfer))) < (1 << _IOC_SIZEBITS)) ? ((N) * (sizeof(struct ioctl_transfer))) : 0)
#define CDEV_IOCTL_MESSAGE(N) _IOWR(CDEV_IOC_MAGIC, 0, char[CDEV_MSGSIZE(N)])
int main(int argc, char const *argv[]) {
int fd = open("/dev/hello_dev", O_RDWR);
static struct ioctl_transfer transfer1 = {
.type = X2_MAGNIFICATION,
.value = 9,
};
ioctl(fd, CDEV_IOCTL_MESSAGE(1), &transfer1);
printf("transfer1 type: 0x%x, value: %d\n", transfer1.type, transfer1.value);
static struct ioctl_transfer transfer2 = {
.type = X10_MAGNIFICATION,
.value = 30,
};
ioctl(fd, CDEV_IOCTL_MESSAGE(1), &transfer2);
printf("transfer2 type: 0x%x, value: %d\n", transfer2.type, transfer2.value);
close(fd);
return 0;
}
结果: