AkiraZheng's Time.

驱动原理学习1:字符设备驱动

Word count: 1.3kReading time: 5 min
2025/09/08

一、字符设备驱动

1.1 ioctl

ioctl是用于设备控制的公共接口,可以实现用户态系统调用设备驱动程序进行通信。ioctl字符设备、块设备、网络设备等设备驱动程序中广泛使用。

1
2
3
4
5
6
7
8
9
10
11
12
13

+---------------+ +---------------+ +---------------+ +---------------------------+
| 用户态ioctl +--------->| 陷入内核传递 +------>| 内核查找 +------>| sys_ioctl()根据文件操作集 |
| | | 系统调用号54 | | 系统调用号54 | | 找到驱动程序mydev_ioctl |
+---------------+ +---------------+ +---------------+ +------------+--------------+
^ |
| |
| |
| v
| +---------------------------+
+------------------------------------------------------------------+ |
| 执行驱动程序mydev_ioctl |
+---------------------------+

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
2
3
4
 31             30           16 15            8 7             0
+---------------+--------------+---------------+---------------+
| dir | size | type | nr |
+---------------+--------------+---------------+---------------+

假设按照这4个字段来划分cmd参数,在宏定义时会定义_IOC_DIRSHIFT_IOC_TYPESHIFT_IOC_NRSHIFT_IOC_SIZESHIFT这4个移位值,然后通过移位操作来获取这4个字段的值。

通常,我们不直接使用ioctl函数,而是使用一些宏定义,如_IOC

1
2
3
4
5
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))

并利用_IOC衍生的接口_IO _IOR _IOW _IOWR 等来生成指定的ioctl命令:

1
2
3
4
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)	//定义不带参数的 ioctl 命令
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size))) //定义 带写参数的 ioctl 命令(copy_from_user)
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))) //定义 带读参数的ioctl命令(copy_to_user)
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))) //定义带读写参数的 ioctl 命令

2)sys_ioctl

ioctl会让用户态触发中断陷入内核,所以ioctl本身也有一个系统调用号__NR_ioctl,在<arch/arm64/include/asm/unistd32.h>中定义:

中断陷入内核态之后,会根据寄存器传递过来的系统调用号(54),执行系统调用表中的(54)操作,就是调用sys_ioctl()函数。

1
2
#define __NR_ioctl 54
__SYSCALL(__NR_ioctl, compat_sys_ioctl)

这里sys_ioctl函数的声明为:

1
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg);

实际上我们使用的时候用的是SYSCALL_DEFINE3

1
2
3
4
5
6
7
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
// 1. 根据fd找到对应的file结构体
// 2. 检查权限和参数有效性
// 3. 调用具体设备的ioctl操作方法
return do_vfs_ioctl(file, fd, cmd, 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 字符设备驱动示例
static long mydev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case MYDEV_CMD_READ:
// 处理读取请求
break;
case MYDEV_CMD_WRITE:
// 处理写入请求
break;
default:
return -ENOTTY; // 不支持的命令
}
return 0;
}

// 在file_operations中注册(注册到文件操作集中,供给sys_ioctl路由到这(通过`unlocked_ioctl`))
static const struct file_operations mydev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = mydev_ioctl,
// 其他操作方法...
};

执行里面的驱动函数就能实现完整的系统调用,最后再将结果返回给用户态。

二、

参考

[1] 一文搞懂内核块设备操作之ioctl系统调用过程

CATALOG
  1. 一、字符设备驱动
    1. 1.1 ioctl
    2. 1.2 用户态ioctl和驱动ioctl
      1. 1)用户态ioctl
      2. 2)sys_ioctl
      3. 3)驱动ioctl
  2. 二、
  3. 参考