ioctl
是linux中一种除read和write之外的数据传递机制, 通信双方是应用层和内核层。
简单描述这个机制就是,
- 应用程序调用ioctl函数发送一个数字给内核层驱动程序
- 驱动程序接收到数字, 执行对应的操作。
难点:
- ioctl所发送的数字, 是有一定规则的, 必须符合这个规则, 驱动层才能正确解析这个数字指令。 这个数字也叫
指令码
- 应用层的ioctl是个变参函数, 但是变参并不意味着它可以传任意多个参数进去,它的意思是第三个参数
可传可不传
。
既然是机制, 那么ioctl的机制如下
// >>> 应用层应用程序 test.c
int fd = open("/dev/xxx", O_RDWR);
// 与驱动沟通方式一:
// read(fd, buf, bufsize);
// write(fd, buf, bufsize);
// 与驱动沟通方式二:
ioctl(fd,CMD1);
----------------------
// >>> ioctl.h 一个发挥通信协议作用的头文件
#define CMD1 _IO('r', 10)
----------------------
// >>> 内核层驱动程序 test_dev.c
// 这里是驱动中自定义的ioctl函数, 通过结构体fileoperations结构体建立的映射
long dev_ioctl(struct file *file, unsigned int cmd, unsigned long ptr)
{
switch (cmd){
case CMD1:
// do something ...
break;
}
}
关于指令码
- 指令码的构成
指令码是一个共32位的无符号整型数
direction [31:30] size [29:16] device type [15:8] number [7:0]
direction: 00--没有参数 10--读数据 01--写数据 11--读写双向
size: 如果应用层的ioctl传递了第三个参数, 那么这个段存储第三个参数的字节数取值范围0-4096
type: 一个任意的字符即可, 取值范围0-255
number: 用户自定义个一个数字, 用来构成唯一的指令码,取值范围0-255
- linux内核提供了
生成指令码
的宏函数, 以及解析指令码
的宏函数
// 生成指令码的宏函数
_IO(type, nr) // 生成一个无参数的指令码
_IOR(type, nr, size) // 生成一个读数据的指令码
_IOW(type, nr, size) // 生成一个写数据的指令码
_IOWR(type, nr, size) // 生成一个既要读又要写的指令码
// 解析指令码的宏函数
_IOC_DIR(nr) // 解析出指令码的读写方式direction
_IOC_SIZE(nr) // 解析出指令码中的size
_IOC_TYPE(nr) // 解析出指令码中的type字符
_IOC_NR(nr) // 解析出指令码中的用户自定义的number
关于ioctl的第三个参数
在man手册中,对第三个参数的描述是可以传递一个指针。但是在驱动层对这个参数并没有非常严格的限制。任何小于long类型的数据都可以通过第三个参数完成传递。也就是说, 我可以给第三个参数传递一个字符, 或者一个short型数据, 或者一个int型数据都是可以的。如果是指针, 那这个指针可以传递几乎任何数据类型, 只要在指令码的size段指明这个数据类型的大小即可。举例如下
传递char
// >>> 应用层应用程序 test.c
ioctl(fd,CMD1, data);
----------------------
// >>> ioctl.h 一个发挥通信协议作用的头文件
char data = 'x'; // 要传递的数据
#define CMD1 _IOR('r', 10, data)
---------------------
// >>> 内核层驱动程序 test_dev.c
long dev_ioctl(struct file *file, unsigned int cmd, unsigned long ptr)
{
switch (cmd){
case CMD1:
printk("received a char : %c\n", (char)ptr);
break;
}
}
传递int
// >>> 应用层应用程序 test.c
ioctl(fd,CMD1, data);
----------------------
// >>> ioctl.h 一个发挥通信协议作用的头文件
int data = 12345; // 要传递的数据
#define CMD1 _IOR('r', 10, data)
---------------------
// >>> 内核层驱动程序 test_dev.c
long dev_ioctl(struct file *file, unsigned int cmd, unsigned long ptr)
{
switch (cmd){
case CMD1:
printk("received a int: %d\n", (int)ptr);
break;
}
}
传递字符串
// >>> 应用层应用程序 test.c
ioctl(fd,CMD1, data);
----------------------
// >>> ioctl.h 一个发挥通信协议作用的头文件
char *data = "this is string data to transfer."; // 要传递的数据
#define CMD1 _IOR('r', 10, data)
---------------------
// >>> 内核层驱动程序 test_dev.c
long dev_ioctl(struct file *file, unsigned int cmd, unsigned long ptr)
{
switch (cmd){
case CMD1:
printk("received a string : %s\n", (char *)ptr);
break;
}
}
传递结构体
// >>> 应用层应用程序 test.c
ioctl(fd,CMD1, data);
----------------------
// >>> ioctl.h 一个发挥通信协议作用的头文件
typedef struct {
char name[20];
int age;
} stu_t;
stu_t data; // 要传递的数据
#define CMD1 _IOR('r', 10, data)
---------------------
// >>> 内核层驱动程序 test_dev.c
long dev_ioctl(struct file *file, unsigned int cmd, unsigned long ptr)
{
switch (cmd){
case CMD1:
memcpy(&data, (stu_t *)ptr, _IOC_SIZE(cmd));
printk("student name: %s age:%d\n", data.name, data.age);
break;
}
}