3. 设备驱动模型基础:bus、device、driver、class 的关系与注册流程
嵌入式 Linux 内核的设备驱动模型(Device Driver Model)是驱动异步 probe 和启动优化的底层骨架。理解 bus、device、driver、class 四个核心对象的关系与注册流程,是掌握驱动异步 probe 配置的前提。
3.1 四大核心对象及其职责
内核设备模型将硬件拓扑抽象为四个基本角色,每个角色在内核中对应一个数据结构,并通过链表、kobject 和 sysfs 文件系统组织在一起。
| 对象 | 内核数据结构 | 核心职责 | sysfs 路径示例 |
|---|---|---|---|
| bus | struct bus_type |
定义总线协议,管理挂载在该总线上的 device 和 driver,负责匹配与 probe | /sys/bus/platform/ |
| device | struct device |
代表一个物理或虚拟设备,包含资源、电源状态、DMA 等信息 | /sys/devices/ |
| driver | struct device_driver |
封装驱动逻辑,提供 probe、remove、suspend/resume 回调 | /sys/bus/*/drivers/ |
| class | struct class |
按功能对设备分类,提供统一的用户空间接口(如 devtmpfs、uevent) | /sys/class/ |
关键理解: bus 是匹配的仲裁者,device 和 driver 通过 bus 的 match 函数建立绑定关系;class 则独立于总线,为上层应用提供设备功能视图。
3.2 四者关系与注册流程总览
下图展示了 bus、device、driver、class 之间的核心关系以及注册时的典型交互流程。注意 class 的注册不依赖具体总线,它通过 kobject 与 device 关联。
3.3 bus 的注册与核心回调
总线是设备模型的骨架。每个总线类型在注册时需要提供 match 函数,这是驱动异步 probe 能否正确触发的关键。
// drivers/base/bus.c - 核心注册函数
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1; // 默认开启自动 probe
// 创建 /sys/bus/<name> 目录
retval = kset_register(&priv->subsys);
// 创建 devices 和 drivers 子目录
retval = bus_create_file(bus, &bus_attr_uevent);
// ...
}
EXPORT_SYMBOL_GPL(bus_register);
关键字段:
drivers_autoprobe 控制总线是否在 driver 注册时自动遍历已挂载的 device 并执行 probe。异步 probe 配置需要在此字段基础上,结合 driver 的 probe_type 标志实现。
3.4 device 与 driver 的注册与匹配
device 和 driver 的注册顺序不影响最终绑定结果,因为 bus 会在两侧都维护链表,并在新对象加入时触发匹配扫描。
3.4.1 device_register() 内部流程
- 初始化 kobject,设置 parent 和 bus 指针
- 将 device 加入 bus 的 devices 链表
- 调用 bus->match() 尝试匹配已注册的 driver
- 若匹配成功,调用 bus->probe() 或 driver->probe()
- 通过 class 创建 devtmpfs 节点(如果 device 关联了 class)
3.4.2 driver_register() 内部流程
- 初始化 driver 的 kobject
- 将 driver 加入 bus 的 drivers 链表
- 如果 bus->drivers_autoprobe 为真,遍历 bus 的 devices 链表,对每个 device 调用 match 和 probe
- 如果 driver 设置了
probe_type = PROBE_PREFER_ASYNCHRONOUS,则 probe 以异步方式执行
// drivers/base/dd.c - driver 注册时的自动 probe 逻辑
int driver_register(struct device_driver *drv)
{
struct bus_type *bus;
int ret;
ret = bus_add_driver(drv); // 挂入 bus 的 drivers 链表
if (ret)
return ret;
// 关键:自动 probe 的触发点
if (drv->bus->drivers_autoprobe) {
ret = driver_attach(drv); // 遍历 devices 并匹配
if (ret)
pr_debug("%s: driver_attach failed: %d\n", drv->name, ret);
}
// ...
}
EXPORT_SYMBOL_GPL(driver_register);
3.5 class 的注册与设备节点创建
class 不参与设备与驱动的匹配,它的核心作用是在用户空间提供设备功能视图,并自动创建设备节点。
| class 回调 | 作用 | 典型实现 |
|---|---|---|
devnode |
返回设备节点的文件名和权限 | fmt_devnode(dev, mode) → "ttyS%d" |
dev_uevent |
发送 uevent 时添加环境变量 | add_uevent_var(env, "DEVTYPE=%s", ...) |
dev_release |
设备释放时的清理回调 | kfree(dev) |
// drivers/base/class.c - class 注册与设备关联
int class_register(struct class *cls)
{
struct subsys_private *cp;
int retval;
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
cp->subsys.kobj.kset = class_kset; // 挂入 /sys/class/
retval = kset_register(&cp->subsys);
// 创建 class 属性文件
// ...
}
EXPORT_SYMBOL_GPL(class_register);
// 设备注册时关联 class
int device_add(struct device *dev)
{
// ...
if (dev->class) {
// 创建 /sys/class/<class_name>/<device_name> 符号链接
// 调用 class->devnode 获取设备节点名
// 通过 devtmpfs 创建设备节点
}
// ...
}
3.6 注册流程中的异步 probe 切入点
在标准注册流程中,异步 probe 的配置点集中在 driver 注册阶段。下图展示了异步 probe 在 driver_register 中的执行路径。
3.7 实际注册示例:platform 总线下的设备与驱动
以嵌入式中最常见的 platform 总线为例,展示完整的注册代码片段。
// 1. 定义 platform 设备
static struct platform_device my_device = {
.name = "my-uart",
.id = 0,
.dev = {
.platform_data = &my_uart_pdata,
.release = my_device_release,
},
.num_resources = ARRAY_SIZE(my_uart_res),
.resource = my_uart_res,
};
// 2. 定义 platform 驱动
static struct platform_driver my_driver = {
.probe = my_uart_probe,
.remove = my_uart_remove,
.driver = {
.name = "my-uart",
.owner = THIS_MODULE,
.probe_type = PROBE_PREFER_ASYNCHRONOUS, // 异步 probe 配置
},
};
// 3. 注册顺序不影响绑定
module_platform_driver(my_driver); // 展开为 module_init + platform_driver_register
// 4. 设备注册(通常在板级文件或设备树中)
platform_device_register(&my_device);
注意: 异步 probe 配置
PROBE_PREFER_ASYNCHRONOUS 仅在 driver 注册时生效。如果 device 在 driver 注册之后才加入总线,probe 仍会以同步方式执行,除非 device 本身也设置了异步标志。在后续章节中,我们将深入分析这种时序问题及其优化策略。
3.8 本章小结
- bus 是匹配的仲裁者,通过
match回调决定 device 与 driver 的绑定关系 - device 和 driver 通过 bus 的链表组织,注册顺序不影响最终绑定
- class 独立于总线,负责用户空间设备节点和 uevent 的生成
- 异步 probe 的切入点位于
driver_register()中的driver_attach()阶段,受drivers_autoprobe和probe_type共同控制 - 理解四者的注册流程,是后续配置驱动异步 probe 和优化启动时间的理论基础