- ioctl 请求码
- ioctl 请求码的宏
- 常用的 ioctl 宏
- 示例
- 示例 2
- 示例 3 传递结构体字符串参数
- 注意事项
在内核中,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");
MODULE_DESCRIPTION("A simple character device driver with ioctl support");
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>// 定义设备类型和 ioctl 命令
#define MY_DEVICE_TYPE 'M'
#define MY_IOCTL_SET_PARAM _IOW(MY_DEVICE_TYPE, 0, int)int main() {int fd;int param = 42;// 打开设备文件fd = open("/dev/my_device", O_RDWR);if (fd < 0) {perror("open");return -1;}// 调用 ioctl 命令if (ioctl(fd, MY_IOCTL_SET_PARAM, ¶m) < 0) {perror("ioctl");close(fd);return -1;}// 关闭设备文件close(fd);return 0;
}
示例 2
假设我们有一个字符设备驱动程序,我们希望用户空间程序能够通过 ioctl 命令来获取和设置设备的缓冲区大小。
#include <linux/module.h> // 包含模块相关的宏和函数
#include <linux/init.h> // 包含模块初始化和退出相关的宏
#include <linux/fs.h> // 包含文件系统相关的函数和结构体
#include <linux/cdev.h> // 包含字符设备相关的函数和结构体
#include <linux/uaccess.h> // 包含用户空间和内核空间数据传输的函数
#include <linux/device.h> // 包含设备类和设备实例相关的函数
#include <linux/ioctl.h> // 包含 ioctl 相关的宏和函数// 定义设备号
static dev_t dev_num;// 定义字符设备结构体
static struct cdev chr_dev;// 定义一个设备缓冲区
static char buffer[1024];// 定义缓冲区的当前大小
static int buffer_size = 0;// 定义设备类
static struct class *chr_dev_class;// 定义设备实例
static struct device *chr_dev_device;// 模块许可证
MODULE_LICENSE("GPL");// 模块作者
MODULE_AUTHOR("gopher");// 模块描述
MODULE_DESCRIPTION("A simple character device driver with ioctl support");// 定义 ioctl 命令
#define CHR_DEV_IOCTL_BASE 'c'
#define CHR_DEV_IOCTL_CLEAR_BUFFER _IO(CHR_DEV_IOCTL_BASE, 0) // 清空缓冲区命令
#define CHR_DEV_IOCTL_GET_BUFFER_SIZE _IOR(CHR_DEV_IOCTL_BASE, 1, int) // 获取缓冲区大小命令
#define CHR_DEV_IOCTL_SET_BUFFER_SIZE _IOW(CHR_DEV_IOCTL_BASE, 2, int) // 设置缓冲区大小命令// 打开设备文件时的回调函数
static int chr_dev_open(struct inode *inode, struct file *file) {printk(KERN_INFO "chr_dev: Device opened\n"); // 打印设备被打开的信息return 0; // 返回0表示成功
}// 关闭设备文件时的回调函数
static int chr_dev_release(struct inode *inode, struct file *file) {printk(KERN_INFO "chr_dev: Device closed\n"); // 打印设备被关闭的信息return 0; // 返回0表示成功
}// 从设备读取数据时的回调函数
static ssize_t chr_dev_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) {int ret;if (*ppos >= buffer_size) // 如果请求的偏移量超过缓冲区大小,则返回0表示EOFreturn 0;ret = copy_to_user(user_buf, buffer + *ppos, count); // 将数据从内核空间复制到用户空间if (ret) // 如果复制失败,返回错误return -EFAULT;printk(KERN_INFO "chr_dev: Read %zu bytes from device\n", count); // 打印读取的数据字节数*ppos += count; // 更新文件位置指针return count; // 返回读取的字节数
}// 向设备写入数据时的回调函数
static ssize_t chr_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) {int ret;if (count > sizeof(buffer) - buffer_size) // 如果写入的数据超出缓冲区大小,则返回无空间错误return -ENOSPC;ret = copy_from_user(buffer + buffer_size, user_buf, count); // 将数据从用户空间复制到内核空间if (ret) // 如果复制失败,返回错误return -EFAULT;printk(KERN_INFO "chr_dev: Wrote %zu bytes to device\n", count); // 打印写入的数据字节数buffer_size += count; // 更新缓冲区大小return count; // 返回写入的字节数
}// ioctl 回调函数
static long chr_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {int ret = 0;int size;switch (cmd) {case CHR_DEV_IOCTL_CLEAR_BUFFER: // 清空缓冲区命令处理memset(buffer, 0, sizeof(buffer)); // 将缓冲区清零buffer_size = 0; // 重置缓冲区大小printk(KERN_INFO "chr_dev: Buffer cleared\n");break;case CHR_DEV_IOCTL_GET_BUFFER_SIZE: // 获取缓冲区大小命令处理size = buffer_size; // 获取当前缓冲区大小if (copy_to_user((int __user *)arg, &size, sizeof(size))) // 将缓冲区大小复制到用户空间return -EFAULT;printk(KERN_INFO "chr_dev: Buffer size retrieved: %d\n", size);break;case CHR_DEV_IOCTL_SET_BUFFER_SIZE: // 设置缓冲区大小命令处理if (copy_from_user(&size, (int __user *)arg, sizeof(size))) // 从用户空间获取新的缓冲区大小return -EFAULT;if (size < 0 || size > sizeof(buffer)) // 检查新的缓冲区大小是否有效return -EINVAL;buffer_size = size; // 更新缓冲区大小printk(KERN_INFO "chr_dev: Buffer size set to: %d\n", size);break;default:return -EINVAL; // 返回无效命令错误}return ret; // 返回0表示处理成功
}// 文件操作结构体
static struct file_operations chr_dev_fops = {.owner = THIS_MODULE,.open = chr_dev_open, // 打开设备的回调.release = chr_dev_release, // 关闭设备的回调.read = chr_dev_read, // 读取设备的回调.write = chr_dev_write, // 写入设备的回调.unlocked_ioctl = chr_dev_ioctl, // 添加 ioctl 回调函数
};// 模块初始化函数
static int __init chr_dev_init(void) {int ret;// 分配设备号ret = alloc_chrdev_region(&dev_num, 0, 1, "chr_dev");if (ret < 0) {printk(KERN_ERR "chr_dev: Failed to allocate device number\n");return ret; // 返回错误}// 初始化字符设备cdev_init(&chr_dev, &chr_dev_fops); // 初始化字符设备结构chr_dev.owner = THIS_MODULE; // 设置设备的所有者// 添加字符设备ret = cdev_add(&chr_dev, dev_num, 1);if (ret < 0) {printk(KERN_ERR "chr_dev: Failed to add character device\n");unregister_chrdev_region(dev_num, 1); // 注销设备号return ret; // 返回错误}// 创建设备类chr_dev_class = class_create("chr_dev_class");if (IS_ERR(chr_dev_class)) {printk(KERN_ERR "chr_dev: Failed to create device class\n");cdev_del(&chr_dev); // 删除字符设备unregister_chrdev_region(dev_num, 1); // 注销设备号return PTR_ERR(chr_dev_class); // 返回错误}// 创建设备实例chr_dev_device = device_create(chr_dev_class, NULL, dev_num, NULL, "chr_dev");if (IS_ERR(chr_dev_device)) {printk(KERN_ERR "chr_dev: Failed to create device\n");class_destroy(chr_dev_class); // 销毁设备类cdev_del(&chr_dev); // 删除字符设备unregister_chrdev_region(dev_num, 1); // 注销设备号return PTR_ERR(chr_dev_device); // 返回错误}printk(KERN_INFO "chr_dev: Device initialized\n");printk(KERN_INFO "chr_dev: Major number: %d\n", MAJOR(dev_num)); // 打印主设备号printk(KERN_INFO "chr_dev: Minor number: %d\n", MINOR(dev_num)); // 打印次设备号return 0; // 返回0表示成功
}// 模块退出函数
static void __exit chr_dev_exit(void) {// 删除设备实例device_destroy(chr_dev_class, dev_num);// 删除设备类class_destroy(chr_dev_class);// 删除字符设备cdev_del(&chr_dev);// 释放设备号unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "chr_dev: Device removed\n"); // 打印设备被移除的信息
}// 注册模块初始化函数
module_init(chr_dev_init);// 注册模块退出函数
module_exit(chr_dev_exit);
test.c 文件内容如下:
#include <stdio.h> // 包含基本输入输出函数的头文件
#include <fcntl.h> // 包含文件控制的头文件
#include <sys/ioctl.h> // 包含 ioctl 函数和宏定义的头文件// 定义 ioctl 命令的基准字符
#define CHR_DEV_IOCTL_BASE 'c'// 定义 ioctl 命令:清空缓冲区
#define CHR_DEV_IOCTL_CLEAR_BUFFER _IO(CHR_DEV_IOCTL_BASE, 0)
// 定义 ioctl 命令:获取缓冲区大小
#define CHR_DEV_IOCTL_GET_BUFFER_SIZE _IOR(CHR_DEV_IOCTL_BASE, 1, int)
// 定义 ioctl 命令:设置缓冲区大小
#define CHR_DEV_IOCTL_SET_BUFFER_SIZE _IOW(CHR_DEV_IOCTL_BASE, 2, int)int main() {// 打开设备文件,如果失败则返回-1int fd = open("/dev/chr_dev", O_RDWR);if (fd < 0) {perror("open"); // 打印打开设备时的错误信息return -1;}// 清空缓冲区,通过 ioctl 调用设备的清空缓冲区命令if (ioctl(fd, CHR_DEV_IOCTL_CLEAR_BUFFER) < 0) {perror("ioctl clear buffer"); // 打印清空缓冲区时的错误信息close(fd); // 关闭设备文件描述符return -1;}// 获取缓冲区大小的准备int size; // 存储缓冲区大小的变量if (ioctl(fd, CHR_DEV_IOCTL_GET_BUFFER_SIZE, &size) < 0) {perror("ioctl get buffer size"); // 打印获取缓冲区大小时的错误信息close(fd); // 关闭设备文件描述符return -1;}printf("Buffer size: %d\n", size); // 打印获取到的缓冲区大小// 设置新的缓冲区大小size = 512; // 指定要设置的新缓冲区大小if (ioctl(fd, CHR_DEV_IOCTL_SET_BUFFER_SIZE, &size) < 0) {perror("ioctl set buffer size"); // 打印设置缓冲区大小时的错误信息close(fd); // 关闭设备文件描述符return -1;}// 关闭设备文件描述符close(fd);return 0; // 程序正常结束返回0
}
查看内核
[ 4924.302297] chr_dev: Device initialized
[ 4924.302302] chr_dev: Major number: 240
[ 4924.302303] chr_dev: Minor number: 0
[ 4992.722315] chr_dev: Device opened
[ 4992.722321] chr_dev: Buffer cleared
[ 4992.722335] chr_dev: Buffer size retrieved: 0
[ 4992.722382] chr_dev: Buffer size set to: 512
[ 4992.722401] chr_dev: Device closed
示例 3 传递结构体字符串参数
#include <linux/module.h> // 包含模块相关的宏和函数
#include <linux/init.h> // 包含模块初始化和退出相关的宏
#include <linux/fs.h> // 包含文件系统相关的函数和结构体
#include <linux/cdev.h> // 包含字符设备相关的函数和结构体
#include <linux/uaccess.h> // 包含用户空间和内核空间数据传输的函数
#include <linux/device.h> // 包含设备类和设备实例相关的函数
#include <linux/ioctl.h> // 包含 ioctl 相关的宏和函数// 设备号全局变量
static dev_t dev_num;// 字符设备结构体
static struct cdev chr_dev;// 设备类
static struct class *chr_dev_class;// 设备实例
static struct device *chr_dev_device;// 模块许可证
MODULE_LICENSE("GPL");// 模块作者
MODULE_AUTHOR("gopher");// 模块描述
MODULE_DESCRIPTION("A simple character device driver with ioctl support");// 定义 ioctl 命令
#define CHR_DEV_IOCTL_BASE 'c'
#define CHR_DEV_IOCTL_SET_STRING _IOW(CHR_DEV_IOCTL_BASE, 0, char *) // 设置字符串命令
#define CHR_DEV_IOCTL_SET_STRUCT _IOW(CHR_DEV_IOCTL_BASE, 1, struct my_struct *) // 设置结构体命令// 定义结构体
struct my_struct {int id; // ID字段char name[64]; // 名称字段,最多64个字符
};// 打开设备文件时的回调函数
static int chr_dev_open(struct inode *inode, struct file *file) {printk(KERN_INFO "chr_dev: Device opened\n"); // 打印设备被打开的日志return 0; // 返回0表示成功
}// 关闭设备文件时的回调函数
static int chr_dev_release(struct inode *inode, struct file *file) {printk(KERN_INFO "chr_dev: Device closed\n"); // 打印设备被关闭的日志return 0; // 返回0表示成功
}// ioctl 回调函数
static long chr_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {int ret = 0;char str[1024]; // 用于接收字符串的内核空间缓冲区struct my_struct my_data; // 定义结构体变量以接收数据switch (cmd) {case CHR_DEV_IOCTL_SET_STRING: // 处理设置字符串命令// 从用户空间复制字符串到内核空间if (copy_from_user(str, (char __user *)arg, sizeof(str)))return -EFAULT; // 如果复制失败,返回错误printk(KERN_INFO "chr_dev: Received string: %s\n", str); // 打印接收的字符串break;case CHR_DEV_IOCTL_SET_STRUCT: // 处理设置结构体命令// 从用户空间复制结构体到内核空间if (copy_from_user(&my_data, (struct my_struct __user *)arg, sizeof(struct my_struct)))return -EFAULT; // 如果复制失败,返回错误printk(KERN_INFO "chr_dev: Received struct - id: %d, name: %s\n", my_data.id, my_data.name); // 打印接收的结构体数据break;default:return -EINVAL; // 如果命令无效,返回错误}return ret; // 返回0表示处理成功
}// 文件操作结构体
static struct file_operations chr_dev_fops = {.owner = THIS_MODULE,.open = chr_dev_open, // 打开设备的回调.release = chr_dev_release, // 关闭设备的回调.unlocked_ioctl = chr_dev_ioctl, // 添加 ioctl 回调函数
};// 模块初始化函数
static int __init chr_dev_init(void) {int ret;// 分配设备号ret = alloc_chrdev_region(&dev_num, 0, 1, "chr_dev");if (ret < 0) {printk(KERN_ERR "chr_dev: Failed to allocate device number\n");return ret; // 返回错误}// 初始化字符设备cdev_init(&chr_dev, &chr_dev_fops); // 将文件操作结构体与字符设备关联chr_dev.owner = THIS_MODULE; // 设置设备的所有者// 添加字符设备ret = cdev_add(&chr_dev, dev_num, 1);if (ret < 0) {printk(KERN_ERR "chr_dev: Failed to add character device\n");unregister_chrdev_region(dev_num, 1); // 注销设备号return ret; // 返回错误}// 创建设备类chr_dev_class = class_create("chr_dev_class");if (IS_ERR(chr_dev_class)) {printk(KERN_ERR "chr_dev: Failed to create device class\n");cdev_del(&chr_dev); // 删除字符设备unregister_chrdev_region(dev_num, 1); // 注销设备号return PTR_ERR(chr_dev_class); // 返回错误}// 创建设备实例chr_dev_device = device_create(chr_dev_class, NULL, dev_num, NULL, "chr_dev");if (IS_ERR(chr_dev_device)) {printk(KERN_ERR "chr_dev: Failed to create device\n");class_destroy(chr_dev_class); // 销毁设备类cdev_del(&chr_dev); // 删除字符设备unregister_chrdev_region(dev_num, 1); // 注销设备号return PTR_ERR(chr_dev_device); // 返回错误}printk(KERN_INFO "chr_dev: Device driver initialized\n"); // 打印设备驱动初始化成功的信息return 0; // 返回0表示成功
}// 模块退出函数
static void __exit chr_dev_exit(void) {// 删除设备实例device_destroy(chr_dev_class, dev_num);// 删除设备类class_destroy(chr_dev_class);// 删除字符设备cdev_del(&chr_dev);// 注销设备号unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "chr_dev: Device driver exited\n"); // 打印设备驱动退出的信息
}// 注册模块初始化函数
module_init(chr_dev_init);// 注册模块退出函数
module_exit(chr_dev_exit);
#include <stdio.h> // 包含标准输入输出函数
#include <stdlib.h> // 包含标准库函数
#include <fcntl.h> // 包含文件控制操作的头文件
#include <sys/ioctl.h> // 包含 ioctl 函数和宏定义的头文件
#include <string.h> // 包含字符串操作的函数
#include <errno.h> // 包含 errno 变量的定义// 定义 ioctl 命令
#define CHR_DEV_IOCTL_BASE 'c' // 定义 ioctl 命令基准
#define CHR_DEV_IOCTL_SET_STRING _IOW(CHR_DEV_IOCTL_BASE, 0, char *) // 定义设置字符串的 ioctl 命令
#define CHR_DEV_IOCTL_SET_STRUCT _IOW(CHR_DEV_IOCTL_BASE, 1, struct my_struct *) // 定义设置结构体的 ioctl 命令// 定义结构体
struct my_struct {int id; // 结构体中的 ID 字段char name[64]; // 结构体中的名称字段,最多64个字符
};int main() {int fd; // 文件描述符char *str = "Hello from user space!"; // 定义要传递的字符串struct my_struct my_data = {1, "User Struct"}; // 初始化要传递的结构体// 打开设备文件,获取文件描述符fd = open("/dev/chr_dev", O_RDWR);if (fd < 0) { // 检查文件打开是否成功perror("Failed to open the device"); // 打印失败信息return errno; // 返回错误代码}// 使用 ioctl 传递字符串到设备if (ioctl(fd, CHR_DEV_IOCTL_SET_STRING, str) < 0) { // 调用 ioctl 设置字符串perror("Failed to set string via ioctl"); // 打印失败信息close(fd); // 关闭设备文件描述符return errno; // 返回错误代码}// 使用 ioctl 传递结构体到设备if (ioctl(fd, CHR_DEV_IOCTL_SET_STRUCT, &my_data) < 0) { // 调用 ioctl 设置结构体perror("Failed to set struct via ioctl"); // 打印失败信息close(fd); // 关闭设备文件描述符return errno; // 返回错误代码}// 关闭设备文件描述符close(fd);return 0; // 程序正常结束返回0
}
[ 6474.643614] chr_dev: Device driver initialized
[ 6495.790475] chr_dev: Device opened
[ 6495.790482] chr_dev: Received string: Hello from user space!
[ 6495.790484] chr_dev: Received struct - id: 1, name: User Struct
[ 6495.790540] chr_dev: Device closed
注意事项
ioctl 是一个非常底层的接口,通常用于特定的设备驱动程序。对于大多数应用程序,使用更高级的接口(如 read、write、mmap 等)可能更为合适。
ioctl 的请求码必须在内核和用户空间之间保持一致,否则会导致未定义的行为。
在使用 ioctl 时,务必处理好错误情况,并确保传递的数据是有效的。