4. 设备与驱动注册机制
Linux 设备模型的核心思想是“用统一的数据结构描述硬件与驱动,并让总线负责撮合”。在 2.6 到 6.x 的演进中,device、driver、bus、class、subsystem 这五个对象始终存在,但注册入口、匹配时机、生命周期管理和 sysfs 呈现方式发生了显著变化。理解它们之间的注册顺序与匹配规则,是跨版本适配的第一道门槛。
bus 是匹配的“裁判”,device 与 driver 是参赛双方,class 负责向用户空间暴露统一接口,subsystem 则是更高层的组织容器。注册的本质是把对象挂到对应链表,并触发一次或多次匹配尝试。
一、五个核心对象的职责与注册入口
在旧版本(2.6 早期)中,驱动常直接调用 device_register()、driver_register(),并手动填充 bus_type 的 match() 回调。新版本则普遍使用 device_initialize() + device_add() 的两段式注册,以及 module_platform_driver()、module_i2c_driver() 等宏来简化驱动注册。下表对比了五个对象的关键注册接口与跨版本差异。
| 对象 | 核心结构 | 典型注册接口 | 跨版本差异要点 |
|---|---|---|---|
| device | struct device |
device_register() / device_add() |
新版本推荐先 device_initialize() 再 device_add(),便于错误回滚 |
| driver | struct device_driver |
driver_register() |
新版本大量使用 module_*_driver() 宏,自动处理 init/exit |
| bus | struct bus_type |
bus_register() |
match() 从手写字符串比较演进为 of_match_table、acpi_match_table 多源匹配 |
| class | struct class |
class_register() / class_create() |
class_create() 参数从 (owner, name) 简化为 (name) |
| subsystem | struct subsys_private |
由 bus_register() 内部创建 |
sysfs 中 /sys/bus/、/sys/class/ 的符号链接布局基本稳定 |
二、注册流程与匹配规则
设备注册的典型路径是:分配 struct device → 设置 parent、bus、release 回调 → device_initialize() → device_add()。在 device_add() 内部,内核会把设备加入 bus 的设备链表,并调用 bus_probe_device(),进而遍历该总线上已注册的驱动,逐一调用 bus->match()。匹配成功后进入 really_probe(),依次执行 probe()、绑定 sysfs 链接、触发 uevent。
驱动注册则相反:driver_register() 先把驱动加入 bus 的驱动链表,再反向遍历该总线上尚未绑定的设备,尝试匹配。因此“先注册设备还是先注册驱动”并不影响最终结果,但会影响 probe 的先后顺序,这在依赖时序的系统中非常关键。
/* 新版本推荐的设备注册片段 */
struct device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev->bus = &my_bus_type;
dev->parent = &pdev->dev;
dev->release = my_dev_release;
device_initialize(dev);
ret = device_add(dev);
if (ret)
put_device(dev);
/* 驱动侧使用宏简化注册 */
static struct platform_driver my_drv = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-device",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_drv);
三、匹配规则的三条主线
- 名称匹配:最传统的方式,比较
device->init_name或dev_name()与driver->name。platform、i2c、spi 等总线早期都依赖它。 - 设备树匹配:通过
of_match_table中的compatible字符串与 DTS 节点比对,是现代 ARM/ARM64 平台的主流方式。 - ACPI 匹配:通过
acpi_match_table中的_HID/_CID匹配,常见于 x86 服务器与部分嵌入式平台。
跨版本适配时最常见的坑是:旧驱动只实现了 .name 匹配,升级到新内核后 DTS 中只有 compatible,导致 probe 永远不触发。此时需要补上 of_match_table,并确保 compatible 字符串与驱动表完全一致。
四、跨版本差异与适配案例
以 platform 驱动为例,2.6.32 时代常见写法是直接填充 platform_driver 并在模块 init 中调用 platform_driver_register();而 5.x 之后推荐使用 module_platform_driver() 宏,并配合 of_device_id 表。若把旧驱动直接移植到新内核,可能出现以下现象:
# 旧内核日志
platform my-device: registered
my_driver my-device: probed
# 新内核日志(缺少 of_match_table)
platform my-device: not probed
# 原因:DTS 只有 compatible,驱动只有 .name,bus->match() 返回 0
driver_override、deferred_probe、依赖资源未就绪等因素影响。跨版本时优先检查匹配表,而不是怀疑 probe 函数本身。
五、复盘与适配检查清单
- 确认设备与驱动是否挂在同一个
bus_type上,bus 指针不能为空。 - 检查
of_match_table、acpi_match_table、.name三者是否至少有一条能命中。 - 确认
device_add()与driver_register()的调用顺序不会导致依赖资源缺失。 - 查看
/sys/bus/<bus>/devices/与/sys/bus/<bus>/drivers/下的符号链接,判断绑定是否成功。 - 使用
dmesg | grep -i "probe\|match\|deferred"定位匹配失败或延迟探测原因。