Linux设备驱动 | 系统IO之ioctl函数详解
作者:世至其美
原文地址:https://hqber.com
1. 定义:
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。
在用户空间, ioctl 系统调用的原型:
int ioctl(int fd, unsigned long cmd, ...);
ioctl 驱动方法有和用户空间版本不同的原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg);
2. 定义命令编号规则(cmd)
定义 ioctl 命令号使用 4 个位字段(<linux ioctl.h>)。
- type 幻数,只是选择一个号码(在参考了 ioctl-number.txt 之后)并且使用它在整个驱动 中,这个成员是 8 位宽(_IOC_TYPEBITS)。
- number 序数(顺序编号), 它是 8 位(_IOC_NRBITS)宽。
- direction 数据传送的方向,数据传送是从应用程序的观点来看待的。
- _IOC_NONE:没有数据传输
- _IOC_READ:从设备读数据
- _IOC_WRITE:从设备写数据
- _IOC_READ | _IOC_WRITE:双向传输数据
- size 用户数据的大小,与体系结构有关。常常是 13 或者14 位,你通过宏 _IOC_SIZEBITS 中找到它的值。size字段不是必须的,内核不会检查这个字段,正确使用size可帮助检测用户空间程序的错误并使你实现向后兼容。
2.1 构造命令编号
<linux ioctl.h> 中包含的<asm ioctl.h>中定义宏来构造命令编号。
- _IO(type,nr):构造无参数的命令编号
- _IOR(type, nre,datatype):构造给从驱动中读数据的命令编号
- _IOW(type,nr,datatype):构造给从驱动中写数据的命令编号
- _IOWR(type,nr,datatype):构造给从驱动中读写数据的命令编号
tips:type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到。
2.2 解码命令编号
可通过命令编号进行解码各个字段
- _IOC_DIR(nr)
- _IOC_TYPE(nr)
- _IOC_NR(nr)
- _IOC_SIZE(nr)
2.3 预定义的命令
可用于任何文件(普通、设备、FIFO和套接字) 的命令
只用于普通文件发出的命令
特定于文件系统类型的命令
3. arg参数
如果它是一个整数,可以直接使用。如果它是一个指针, 我们必须确保用户地址是有效的。试图读取一个没验证过的用户提供的指针可能导致不正确的行为, 一个内核 oops、系统崩溃、或者安全问题。驱动的责任是对每一个它使用的用户空间地址进行正确的检查, 如果它是无效的,则返回一个错误。
地址校验(不传送数据)由函数 access_ok实现,它定义在 <asm uaccess.h>:
int access_ok(int type, const void *addr, unsigned long size);
4. 返回值
- ioctl 的实现常常是一个 switch 语句, 基于命令号. 但是当命令号没有匹配一个有效的,一般返回值为:
- ENIVAL("Invalid argument"):命令参数确实不是一个有效的
5. 单个数据
5.1 传输单个数据
- put_user(datum, ptr) 检查来确保这个进程能够写入给定的内存地址。它在成功时返回 0,并且在错误时返回 -EFAULT。
- ___put_user(datum, ptr) 进行更少的检查(它不调用 access_ok),但是仍然能够失败如果被指向的内存对用户是不可写的。
5.2 接收单个数据
- get_user(local, ptr) 应当只用在已经使用 access_ok 校验过的地址,从用户空间接收单个数据,获取的值存储于本地变量 local。
- __get_user(local, ptr) 进行更少的检查(它不调用 access_ok),从用户空间接收单个数据,获取的值存储于本地变量 local。
注意:传输/接收多个数据到用户空间可使用copy_to_user和copy_from_user函数。
6. 权能和受限
权能就是对权限进行管理分配,全部的权能操作在<linux capability.h>可找到,包含系统能够理解的所有权能,在不修改内核源代码情况下,驱动工程师和系统管理员就无法定义新的权能。内核专门为许可管理使用权能并导出了两个系统调用capget和capset。重点关注如下权能:
- CAP_DAC_OVERRIDE:越过文件或目录的访问限制(数据访问控制或者 DAC)的能力。
- CAP_NET_ADMIN:进行网络管理任务的能力, 包括那些能够影响网络接口的。
- CAP_SYS_MODULE:载入或卸载内核模块的能力。
- CAP_SYS_RAWIO:进行 "裸“ I/O 操作的能力,例子包括存取设备端口或者直接和 USB 设备通讯。
- CAP_SYS_ADMIN:截获的能力, 提供访问许多系统管理操作的途径。
- CAP_SYS_TTY_CONFIG:进行 tty 配置任务的能力。
设备驱动程序应该检查调用进程是否有合适的权能,如果不进行检查,将影响系统稳定性和安全性。
作者:世至其美
原文地址:https://hqber.com