在内核中,ioctl(input/output control)是一个系统调用,用于设备驱动程序和用户空间程序之间的通信。它允许用户空间程序向设备驱动程序发送命令,以执行特定的操作或获取设备的状态信息。ioctl 是一个非常灵活的接口,因为它可以根据设备类型和需求定义不同的命令。
ioctl 的基本结构
ioctl 系统调用的原型如下:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
- fd:文件描述符,指向设备驱动程序的打开文件。
- request:ioctl 命令,由设备驱动程序定义。
- …:可变参数,用于传递额外的数据
ioctl 请求码
请求码通常由以下几个部分组成:
| 31 - 30 | 29 - 16 | 15 - 8 | 7 - 0 |
| 方向 | 类型 | 序号 | 大小 |
方向(Direction):2 位,用于指示数据传输的方向。
类型(Type):14 位,用于标识设备或驱动程序的类型。
序号(Number):8 位,用于标识具体的操作。
大小(Size):8 位,用于指示数据的大小。
方向(Direction)
方向字段占用 2 位,用于指示数据传输的方向。常见的方向值如下:
_IOC_NONE(0):没有数据传输。
_IOC_WRITE(1):数据从用户空间写入内核空间。
_IOC_READ(2):数据从内核空间读取到用户空间。
_IOC_READ | _IOC_WRITE(3):双向数据传输。
类型(Type)
类型字段占用 14 位,用于标识设备或驱动程序的类型。通常是一个唯一的标识符,例如一个字符常量。
序号(Number)
序号字段占用 8 位,用于标识具体的操作。通常是一个整数。
大小(Size)
大小字段占用 8 位,用于指示数据的大小。通常是数据结构的大小。
ioctl 请求码的宏
在 Linux 内核中,请求码通常使用宏来定义,例如:
#define _IOC(dir, type, nr, size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
_IOC_DIRSHIFT、_IOC_TYPESHIFT、_IOC_NRSHIFT、_IOC_SIZESHIFT 是一些预定义的位移常量。
常用的 ioctl 宏
_IO(type, nr):用于定义一个没有数据传输的 ioctl 命令。
_IOR(type, nr, size):用于定义一个从内核读取数据的 ioctl 命令。
_IOW(type, nr, size):用于定义一个向内核写入数据的 ioctl 命令。
_IOWR(type, nr, size):用于定义一个双向数据传输的 ioctl 命令。
_IOC_DIR(request):提取请求码中的方向(Direction)。
_IOC_TYPE(request):提取请求码中的类型(Type)。
_IOC_NR(request):提取请求码中的序号(Number)。
_IOC_SIZE(request):提取请求码中的大小(Size)。
示例
假设我们有一个字符设备驱动程序,我们希望用户空间程序能够通过 ioctl 命令来设置设备的某个参数。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
// 定义设备类型和 ioctl 命令
#define MY_DEVICE_TYPE 'M'
#define MY_IOCTL_SET_PARAM _IOW(MY_DEVICE_TYPE, 0, int)
// 设备打开函数
static int my_device_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "my_device: open\n");
return 0;
}
// 设备释放函数
static int my_device_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "my_device: release\n");
return 0;
}
// ioctl 处理函数
static long my_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
int param;
switch (cmd) {
case MY_IOCTL_SET_PARAM:
// 从用户空间复制参数到内核空间
// 注意:copy_from_user() 函数返回 0 表示成功,非 0 表示失败
if (copy_from_user(¶m, (int __user *)arg, _IOC_SIZE(int))) {
return -EFAULT;
}
printk(KERN_INFO "my_device: set param to %d\n", param);
break;
default:
return -EINVAL; // 无效的命令
}
return 0;
}
// 文件操作结构体
static struct file_operations my_device_fops = {
.owner = THIS_MODULE,
.open = my_device_open,
.release = my_device_release,
.unlocked_ioctl = my_device_ioctl,
};
// 设备号和字符设备结构体
static dev_t my_device_dev;
static struct cdev my_device_cdev;
// 模块初始化函数
static int __init my_device_init(void) {
int ret;
// 分配设备号
ret = alloc_chrdev_region(&my_device_dev, 0, 1, "my_device");
if (ret < 0) {
printk(KERN_ERR "my_device: failed to allocate device number\n");
return ret;
}
// 初始化字符设备
cdev_init(&my_device_cdev, &my_device_fops);
my_device_cdev.owner = THIS_MODULE;
// 添加字符设备
ret = cdev_add(&my_device_cdev, my_device_dev, 1);
if (ret < 0) {
printk(KERN_ERR "my_device: failed to add character device\n");
unregister_chrdev_region(my_device_dev, 1);
return ret;
}
printk(KERN_INFO "my_device: module loaded\n");
return 0;
}
// 模块退出函数
static void __exit my_device_exit(void) {
// 删除字符设备
cdev_del(&my_device_cdev);
// 注销设备号
unregister_chrdev_region(my_device_dev, 1);
printk(KERN_INFO "my_device: module unloaded\n");
}
// 模块初始化和退出函数的注册
module_init(my_device_init);
module_exit(my_device_exit);
// 模块许可证、作者和描述信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");