在 Linux 设备驱动开发中,ioctl
(Input/Output Control)是一种重要的控制接口,允许用户空间程序通过自定义命令与内核驱动交互,实现设备配置、状态查询、特殊操作等功能。以下是 ioctl
的完整实现指南。
1. ioctl
的核心概念
(1) 作用
- 用户空间与驱动的交互:通过自定义命令实现设备控制(如设置波特率、读取寄存器值)。
- 灵活的参数传递:支持传递整型、结构体等复杂数据类型。
(2) 用户空间接口
用户程序通过系统调用 ioctl
发送命令和参数:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long cmd, ... /* void *arg */);
(3) 内核驱动接口
在 file_operations
中实现 unlocked_ioctl
方法:
long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);
2. ioctl
命令码的构造
Linux 内核定义了命令码的构造规则,需使用宏 _IO
, _IOR
, _IOW
, _IOWR
生成唯一的命令码。
宏 | 含义 | 参数方向 |
---|---|---|
_IO(type, nr) | 无参数命令 | 无数据传递 |
_IOR(type, nr, datatype) | 读数据命令 | 驱动 → 用户空间 |
_IOW(type, nr, datatype) | 写数据命令 | 用户空间 → 驱动 |
_IOWR(type, nr, datatype) | 双向读写命令 | 用户空间 ↔ 驱动 |
- 参数说明:
type
:幻数(Magic Number),8 位宽,用于区分命令组(如'k'
表示自定义驱动)。nr
:命令序号(0~255),同一幻数下唯一。datatype
:数据类型的字节大小(如int
对应sizeof(int)
)。
示例:定义命令码
// 定义幻数(需唯一)
#define MYDEV_IOC_MAGIC 'k'
// 定义命令
#define MYDEV_GET_STATUS _IOR(MYDEV_IOC_MAGIC, 0, int)
#define MYDEV_SET_MODE _IOW(MYDEV_IOC_MAGIC, 1, int)
#define MYDEV_READ_DATA _IOWR(MYDEV_IOC_MAGIC, 2, struct mydev_data)
3. 驱动中的 ioctl
实现
(1) 基本框架
static long mydev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
struct mydev_private *priv = filp->private_data;
void __user *argp = (void __user *)arg;
switch (cmd) {
case MYDEV_GET_STATUS:
// 从驱动读取状态到用户空间
return mydev_get_status(priv, argp);
case MYDEV_SET_MODE:
// 从用户空间设置模式到驱动
return mydev_set_mode(priv, argp);
case MYDEV_READ_DATA:
// 双向数据传递
return mydev_read_data(priv, argp);
default:
return -ENOTTY; // 无效命令
}
}
(2) 参数处理示例
简单整型参数
case MYDEV_SET_MODE: {
int mode;
if (copy_from_user(&mode, argp, sizeof(int)))
return -EFAULT;
priv->mode = mode;
return 0;
}
结构体参数
struct mydev_data {
int id;
char buf[32];
};
case MYDEV_READ_DATA: {
struct mydev_data data;
if (copy_from_user(&data, argp, sizeof(data)))
return -EFAULT;
// 处理数据(例如根据 id 填充 buf)
snprintf(data.buf, sizeof(data.buf), "Data for ID %d", data.id);
// 将结果拷贝回用户空间
if (copy_to_user(argp, &data, sizeof(data)))
return -EFAULT;
return 0;
}
4. 用户空间调用示例
(1) 基础调用
#include <sys/ioctl.h>
int main() {
int fd = open("/dev/mydevice", O_RDWR);
int status;
// 获取设备状态
if (ioctl(fd, MYDEV_GET_STATUS, &status) < 0) {
perror("ioctl failed");
return -1;
}
printf("Device status: 0x%x\n", status);
close(fd);
return 0;
}
(2) 结构体参数调用
struct mydev_data data = { .id = 123 };
if (ioctl(fd, MYDEV_READ_DATA, &data) < 0) {
perror("ioctl failed");
return -1;
}
printf("Received data: %s\n", data.buf);
5. 安全性关键点
(1) 用户指针验证
-
access_ok
检查:确保用户空间地址合法。if (!access_ok(VERIFY_READ, argp, sizeof(int)) || !access_ok(VERIFY_WRITE, argp, sizeof(int))) { return -EFAULT; }
-
copy_from_user
/copy_to_user
:始终使用安全拷贝函数。
(2) 命令权限控制
- 结合
CAP_SYS_ADMIN
等权能限制敏感命令:if (!capable(CAP_SYS_ADMIN)) return -EPERM;
(3) 防止整数溢出
校验用户传入的缓冲区大小:
if (data.size > MAX_BUFFER_SIZE)
return -EINVAL;
6. 高级用法
(1) 兼容 32/64 位用户空间
- 使用
compat_ioctl
处理 32 位程序与 64 位内核的兼容性:#ifdef CONFIG_COMPAT static long mydev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { // 转换 32 位参数到 64 位格式 return mydev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); } #endif static struct file_operations mydev_fops = { .unlocked_ioctl = mydev_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = mydev_compat_ioctl, #endif };
(2) 可变长度数据传递
通过指针和大小参数动态处理数据:
struct variable_data {
size_t size;
void __user *data;
};
case MYDEV_VARIABLE_DATA: {
struct variable_data vdata;
if (copy_from_user(&vdata, argp, sizeof(vdata)))
return -EFAULT;
char *buf = kmalloc(vdata.size, GFP_KERNEL);
if (copy_from_user(buf, vdata.data, vdata.size)) {
kfree(buf);
return -EFAULT;
}
// 处理数据...
kfree(buf);
return 0;
}
7. 调试与错误处理
(1) 错误码列表
-ENOTTY
:无效命令。-EFAULT
:用户空间地址非法。-EINVAL
:参数无效。-EPERM
:权限不足。
(2) 内核日志追踪
在 ioctl
中添加调试日志:
pr_debug("Received command 0x%x, arg=0x%lx\n", cmd, arg);
(3) 用户空间测试工具
使用 strace
跟踪 ioctl
调用:
strace -e trace=ioctl ./user_app
总结
- 命令码构造:使用
_IO
,_IOR
,_IOW
,_IOWR
宏确保唯一性。 - 参数处理:通过
copy_from_user
/copy_to_user
安全拷贝数据。 - 安全验证:检查用户地址合法性和命令权限。
- 兼容性:支持 32/64 位混合环境。
合理设计 ioctl
接口,可显著提升驱动的灵活性和安全性,是设备控制交互的核心手段。
参考: