一、字符设备驱动
1.1 ioctl
ioctl
是用于设备控制的公共接口,可以实现用户态系统调用、设备驱动程序进行通信。ioctl
在字符设备、块设备、网络设备等设备驱动程序中广泛使用。
1 |
|
1.2 用户态ioctl和驱动ioctl
系统调用过程中,实际上就是处于用户空间的用户态ioctl对应上特定内核空间中ioctl的过程
但是用户态没有访问内核空间的权限,因此需要系统先通过SWI(Software Interrupt)
的方式从用户态陷入内核态,并利用sys_ioctl
通过文件操作集找到对应的驱动ioctl,三者共同构成了Linux
I/O控制的核心机制。
1)用户态ioctl
在Linux中,glibc/标准库中(/usr/include/sys
)封装着为用户程序提供的统一ioctl
接口,在<sys/ioctl.h>中定义了ioctl函数:
1 | extern int ioctl (int __fd, unsigned long int __request, ...) __THROW; |
fd
:设备文件描述符。表示要操作的设备对象。request
:也常将该参数写作cmd
,表示对设备进行控制的命令,设备驱动将根据cmd参数执行对应的操作- 该参数定义了用户与驱动的“协议”,虽然可以是任意值,但是Linux中还是提供了统一格式,将32位的int型数据划分成4个位段,来保证参数的唯一性。
...
:可选参数,表示对设备进行控制的命令参数,可以是整数、指针等类型,用于传递给驱动程序。
下面讲解一下
request
/cmd
参数中的4个位段信息: -
dir
-2bit:表示数据传输方向(四种) -
_IOC_NONE
:无数据传输 -
_IOC_READ
:数据从内核空间(设备)读取到用户空间 -
_IOC_WRITE
:数据从用户空间写入到内核空间(设备) -
_IOC_READ|_IOC_WRITE
:数据在用户空间和内核空间之间双向传输
-
type
-8bit:设备类型标识符,用于区分不同设备。也成为幻数/魔数
-
nr(number)
-8bit:命令序号,用于区分同一设备的不同命令,可以从0~255之间进行编号
-
size
-14bit:表示用户传入的用户数据...
部分参数的数据类型和长度,单位是字节
- 系统并不强制使用这个位字段,因此内核不会检查该字段。
1 | 31 30 16 15 8 7 0 |
假设按照这4个字段来划分cmd
参数,在宏定义时会定义_IOC_DIRSHIFT
、_IOC_TYPESHIFT
、_IOC_NRSHIFT
、_IOC_SIZESHIFT
这4个移位值,然后通过移位操作来获取这4个字段的值。
通常,我们不直接使用ioctl
函数,而是使用一些宏定义,如_IOC
:
1 |
并利用_IOC
衍生的接口_IO
_IOR
_IOW
_IOWR
等来生成指定的ioctl命令:
1 |
2)sys_ioctl
ioctl
会让用户态触发中断陷入内核,所以ioctl
本身也有一个系统调用号__NR_ioctl
,在<arch/arm64/include/asm/unistd32.h>中定义:
中断陷入内核态之后,会根据寄存器传递过来的系统调用号(54),执行系统调用表中的(54)操作,就是调用sys_ioctl()函数。
1 |
|
这里sys_ioctl
函数的声明为:
1 | asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg); |
实际上我们使用的时候用的是SYSCALL_DEFINE3
1 | SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) |
在SYSCALL_DEFINE3
中执行do_vfs_ioctl
,深挖到该接口里面会发现它将对应的驱动程序的ioctl加入到了文件操作集中,由此路由到对应的驱动ioctl函数中执行对应的操作
3)驱动ioctl
假设我们某个中断的目标是路由到led_ioctl
驱动程序中,定义该驱动函数为:
1 | static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
在驱动程序中用switch对该设备的cmd
号进行解析,并执行对应的操作
1 | // 字符设备驱动示例 |
执行里面的驱动函数就能实现完整的系统调用,最后再将结果返回给用户态。