
本文概述
ioctl是Linux字符设备驱动中最常用的控制接口,用户空间通过它向驱动发送控制命令和传递参数。现代Linux内核使用unlocked_ioctl替代了传统的ioctl接口,不再持有大内核锁。ioctl命令码通过_IO、_IOR、_IOW、_IOWR四个宏定义,包含方向、大小、类型、序号四个字段。驱动实现中通过switch case分发命令,使用copy_from_user和copy_to_user在用户空间和内核空间之间拷贝数据。实现ioctl需要注意参数合法性检查、并发保护、错误码返回和64位兼容等问题。
核心要点速览
现代驱动使用unlocked_ioctl,不持有大内核锁,需自己加锁
命令码用_IO/_IOR/_IOW/_IOWR宏定义,四字段:方向+大小+类型+序号
用户空间数据必须用copy_from_user/copy_to_user安全拷贝
返回值:成功返回0或正数,失败返回负的错误码
64位内核要考虑32位用户空间兼容,实现compat_ioctl
一、面试官考察点
面试官问ioctl,考察的是Linux驱动开发的基本功。这个问题看起来简单,但深浅差别很大。新手可能只会说"用ioctl函数,switch case处理命令",有经验的开发者会谈及unlocked_ioctl和传统ioctl的区别、命令码定义规范、参数拷贝的安全性、并发控制、错误处理、compat兼容等更深入的内容。
二、回答思路
1. ioctl是什么,什么时候用
ioctl全称是input/output control,是设备驱动中用来做控制操作的系统调用。read和write主要用来传输数据,而ioctl主要用来做配置、控制、状态查询这类操作。比如设置串口波特率、读写GPIO方向、控制LED亮度、查询设备状态这些,用ioctl就比较合适。用read/write也能实现,但语义上不太对,而且参数传递不方便。
ioctl vs sysfs vs proc
除了ioctl,Linux驱动还有其他和用户空间交互的方式,比如sysfs属性文件、proc文件、netlink等。ioctl适合命令式的操作,一次调用完成一个动作;sysfs适合单值的属性读写,每个文件一个属性;proc适合调试信息输出。实际选型看具体场景,简单的控制用sysfs更优雅,复杂的多参数操作用ioctl更方便。
2. 命令码怎么定义
ioctl命令码是一个32位的整数,不是随便编的,有固定的格式。内核提供了四个宏来帮我们构造命令码:
| 宏 | 含义 | 参数 | 使用场景 |
|---|---|---|---|
| _IO(type, nr) | 无参数命令 | 类型+序号 | 纯控制命令,比如复位、启动、停止 |
| _IOR(type, nr, size) | 读命令(从驱动到用户) | 类型+序号+数据类型 | 从驱动读取状态、参数、配置等 |
| _IOW(type, nr, size) | 写命令(从用户到驱动) | 类型+序号+数据类型 | 向驱动下发配置、参数、控制数据 |
| _IOWR(type, nr, size) | 双向命令 | 类型+序号+数据类型 | 既有输入又有输出,比如某些io操作 |
命令码里的type字段叫幻数,一般用一个字符表示,每个驱动的type要不一样,防止不同驱动的命令码撞车。内核文档里有一个正在使用的幻数列表,自己写驱动的时候要选一个没被占用的。nr字段就是命令序号,从0或者1开始编号就行。
3. 驱动端实现unlocked_ioctl
驱动的ioctl接口在file_operations结构体里注册。注意现在内核用的是unlocked_ioctl,不是老的ioctl。unlocked_ioctl的意思是调用的时候不持有大内核锁BKL,这样多核系统的性能更好。但代价就是驱动自己要处理并发问题,该加锁的地方要加锁。
4. 用户空间怎么调用
用户空间用ioctl系统调用来访问驱动的ioctl接口。函数原型是int ioctl(int fd, unsigned long request, ...),第三个参数是可变参数,传一个指针或者一个整数都可以。
5. 实现ioctl的注意事项
注意一:不能直接访问用户空间指针
用户空间传过来的地址不能直接解引用,必须用copy_from_user和copy_to_user来拷贝数据。这两个函数会检查地址是否合法,如果地址无效会返回剩余未拷贝的字节数。直接访问用户空间指针,轻则oops重则安全漏洞。同样,也不能直接用__user标记的指针来读写,那只是给静态检查工具看的。
注意二:返回正确的错误码
ioctl的返回值有约定:成功返回0或者非负整数(比如读取的字节数),失败返回负的错误码。常见的错误码有:-ENOTTY表示不支持的命令,-EFAULT表示用户空间地址无效,-EINVAL表示参数无效,-ENOMEM表示内存不足,-EBUSY表示设备忙。错误码要选准确,方便用户空间的程序判断问题出在哪。
注意三:并发保护
因为unlocked_ioctl不持有大内核锁,多个进程可以同时调用同一个驱动的ioctl。如果ioctl操作了共享的设备资源,必须自己加锁保护。常用的锁有自旋锁、互斥锁等。简单的寄存器操作可以用自旋锁,可能休眠的操作用互斥锁。加锁的范围要合适,不能太大也不能太小。
注意四:64位兼容问题
如果驱动跑在64位内核上,还要考虑32位用户程序的调用。因为32位和64位的指针大小不一样,结构体的对齐和大小可能不同,导致命令码里的size字段不一致。这时候需要实现compat_ioctl接口,在file_operations里注册.compat_ioctl函数指针。compat_ioctl的作用是把32位的命令和参数转换成64位的格式,然后调用正常的处理逻辑。如果命令参数都是基本类型而且大小一样,也可以用generic compat层的默认处理。
三、追问环节
追问1:为什么不能直接访问用户空间的指针?
有几个原因。第一,用户空间的地址可能无效,比如传了一个野指针,直接访问会导致内核oops。第二,用户空间的内存可能被换出,访问的时候会触发缺页异常,虽然内核一般能处理,但在某些上下文(比如中断上下文、持有自旋锁时)是不能休眠的,这时候就会出问题。第三,这是安全边界,用户空间的数据是不可信的,必须经过校验才能使用。copy_from_user和copy_to_user除了做数据拷贝,还会检查地址的合法性和可访问性,如果有问题返回错误而不是直接崩溃。这是Linux驱动编程的基本安全准则。
追问2:unlocked_ioctl为什么比老的ioctl好?
老的ioctl调用时会持有大内核锁BKL,BKL是一个全局的自旋锁,整个内核只有一把。这意味着任何时候只能有一个CPU在执行ioctl,其他CPU都得等着,多核处理器的性能完全发挥不出来。unlocked_ioctl把锁的粒度下放到了驱动层面,每个驱动自己决定怎么加锁。如果驱动的ioctl操作不需要全局同步,就可以不加锁或者加更细粒度的锁,这样多个CPU可以同时执行不同的ioctl,并发性能大大提升。这是Linux内核从巨锁向细粒度锁演进的一个重要步骤。现在内核里BKL已经完全移除了。
追问3:ioctl和read/write比有什么优缺点?
ioctl的优点是灵活,一个命令码可以定义各种各样的操作,参数可以是任意结构体,不受read/write那种数据流模型的限制。对于控制类、配置类的操作,ioctl的语义更清晰。缺点也很明显:第一,ioctl不是统一的接口,每个驱动自己定义命令码,应用程序要知道具体的命令才能用,不像read/write是标准的;第二,ioctl不好做权限控制和审计,因为命令码是自定义的;第三,从设计模式的角度,ioctl容易变成一个大杂烩,什么功能都往里塞,代码可读性差。现在内核社区其实更推荐用sysfs来做设备控制,一个属性一个文件,更清晰也更安全。但对于一些复杂的、多参数的、原子性的操作,ioctl还是不可替代的。
追问4:ioctl里可以休眠吗?
unlocked_ioctl是在进程上下文调用的,所以是可以休眠的。也就是说,可以在ioctl里面用互斥锁、等待队列、kmalloc的GFP_KERNEL标志这些可能休眠的操作。但要注意几个点:第一,如果你自己加了自旋锁,在持有自旋锁的时候不能休眠;第二,如果是在等待硬件操作完成,最好用等待队列而不是死循环延时;第三,休眠的时候设备状态可能变化,唤醒后要重新检查条件。另外,如果ioctl的操作可能会被信号打断,应该用可中断的等待函数,并返回-ERESTARTSYS让系统重新调用。
四、失分表达
"直接在ioctl里把用户指针强转成结构体指针用就行"
这是非常危险的错误,也是面试的送命题。直接访问用户空间指针轻则oops重则安全漏洞,必须用copy_from_user/copy_to_user。
"ioctl命令码随便编个数字就行,只要不重复"
不了解命令码的规范结构,不知道要用_IO系列宏定义。这会导致方向检查失效、大小校验失效,埋下安全隐患。
"ioctl和unlocked_ioctl就是名字不一样,功能一样"
不知道BKL和并发的区别,说明对内核的并发模型和历史演变缺乏了解。
五、准备动作
写一个完整的字符设备驱动例子
从模块入口出口、注册设备、file_operations到ioctl实现,完整写一遍。能跑起来最好,跑不起来至少代码要完整。
弄清copy_from_user的原理和返回值
为什么要用它、它做了什么检查、返回值是什么含义,这些细节经常被追问。
了解几种用户态和内核态交互方式的对比
ioctl、sysfs、proc、netlink、字符设备read/write各有什么优缺点、适用场景是什么。
六、核心关键词
unlocked_ioctl 命令码定义 copy_from_user 用户空间交互 字符设备驱动 并发保护 compat_ioctl 错误码返回

扫码关注




























