4. Linux设备驱动模型
在板级联调中,很多“驱动加载了但设备没起来”“probe 不执行”“/dev 下没有节点”的问题,根源并不在驱动代码本身,而在于没有理清 Linux 设备驱动模型的对象关系。Linux 2.6 之后引入的统一设备模型,用 kobject、kset 等底层机制,把总线、设备、驱动、类、sysfs、udev、设备树串成了一条可观察、可匹配、可自动化的链路。理解这条链路,是后续调试 I2C 传感器、SPI 屏、GPIO 按键、平台设备的基础。
一、核心对象与职责
设备模型的核心思想是:用统一的树状结构描述系统中所有设备,并让“设备”和“驱动”通过“总线”完成匹配。各对象职责如下:
- 总线(bus):匹配规则的持有者,维护设备链表和驱动链表。当新设备或新驱动注册时,总线负责调用 match 判断能否配对,配对成功后调用驱动的 probe。
- 设备(device):描述一个具体的硬件实例,如 I2C 地址 0x68 的 RTC、GPIO 编号 12 的按键。它携带资源信息:地址、中断号、时钟、GPIO、寄存器基址等。
- 驱动(driver):描述“如何操作一类设备”的代码,包含 probe、remove、suspend、resume 以及 id_table 或 of_match_table。
- 类(class):从“功能视角”对设备分组,如 input、tty、net、rtc、leds。类决定 udev 在 /dev 下创建什么名字的节点。
- sysfs:把内核对象以文件树形式导出到 /sys,是用户空间观察和配置设备的窗口。
- udev:用户空间守护进程,监听内核 uevent,根据规则在 /dev 下动态创建/删除设备节点、设置权限和符号链接。
- 设备树(Device Tree):ARM 等平台上用 DTS/DTB 描述硬件拓扑与资源,内核解析后生成 platform_device、i2c_client 等设备对象。
一句话串联:设备树描述硬件 → 内核生成 device → 总线 match 到 driver → probe 初始化 → class 注册 → sysfs 暴露 → udev 收到 uevent → /dev 出现节点。
二、匹配与 probe 的完整流程
以 I2C 温度传感器为例,联调时最关心的是“为什么 probe 没被调用”。完整流程如下:
- 设备树中定义节点,compatible 为
"vendor,tmp102",reg 为<0x48>,并挂在对应 I2C 控制器下。 - 内核启动解析 DTB,为每个 I2C 子节点创建 i2c_client,并注册到 i2c 总线。
- 驱动模块加载时注册 i2c_driver,其 of_match_table 中同样声明
"vendor,tmp102"。 - i2c 总线对设备链表和驱动链表逐一 match,compatible 或 id 一致即配对成功。
- 配对后调用驱动的 probe,在 probe 中申请资源、注册 hwmon 或 iio 设备、创建 class 属性。
- class 注册触发 uevent,udev 在 /dev 或 /sys/class/hwmon 下生成对应节点与属性文件。
# 观察匹配与 probe 是否发生
dmesg | grep -i tmp102
# 查看设备是否挂到总线
ls /sys/bus/i2c/devices/
# 查看驱动是否注册
ls /sys/bus/i2c/drivers/
# 查看设备树节点是否被解析
ls /proc/device-tree/ | grep i2c
# 查看 class 与 udev 结果
ls /sys/class/hwmon/
ls -l /dev/input/
三、sysfs、udev 与设备树的分工
三者常被混淆,实际分工非常清晰:设备树是“硬件描述源”,sysfs 是“内核对象视图”,udev 是“用户空间节点管理器”。设备树只在启动时被解析一次,sysfs 随对象生命周期动态增删,udev 则根据 sysfs 暴露的 uevent 和属性做规则化处理。
| 对象 | 所在空间 | 主要作用 | 联调观察点 |
|---|---|---|---|
| 设备树 DTB | 内核启动阶段 | 描述硬件资源与连接关系 | /proc/device-tree、dmesg 解析日志 |
| bus | 内核 | 维护设备/驱动链表并 match | /sys/bus/*/devices、drivers |
| device | 内核 | 表示硬件实例及资源 | /sys/devices/ 下对应节点 |
| driver | 内核 | 实现 probe/remove 操作 | lsmod、/sys/bus/*/drivers |
| class | 内核 | 按功能分组,决定节点命名 | /sys/class/ 下目录 |
| sysfs | 内核→用户 | 导出对象属性为文件 | /sys 下 cat/echo 读写 |
| udev | 用户空间 | 动态创建 /dev 节点与权限 | /dev、udevadm info/monitor |
四、板级联调中的典型误区与复盘
误区一:只改驱动,不看设备树。 compatible 拼写不一致、reg 地址写错、节点挂错父总线,都会导致 match 失败,probe 永远不执行。联调第一步应是核对 DTS 与驱动 of_match_table。
误区二:以为 /dev 节点是驱动创建的。 驱动通常只注册 class 和 device_create,真正在 /dev 下生成节点的是 udev。若根文件系统没有 udev 或 mdev,节点不会自动出现,需要手动 mknod 或改用 devtmpfs。
复盘方法: 遇到设备不工作时,按“设备树→总线→驱动→class→sysfs→udev→/dev”顺序逐级排查。先用 dmesg 看 probe 是否被调用,再用 /sys/bus 确认匹配关系,最后用 udevadm monitor 观察 uevent 是否到达用户空间。这条链路一旦打通,绝大多数板级驱动问题都能快速定位。
掌握设备模型后,后续调试 platform 设备、I2C/SPI 从设备、GPIO 子系统时,就能从“猜”转变为“按对象关系定位”,这也是板级联调效率提升的关键一步。