4. 设备模型核心

Linux设备模型并不是一组孤立的链表,而是由bus_type、device、device_driver三个核心结构体构成的“三角关系”。理解这三者的字段含义、生命周期以及它们之间的匹配与绑定过程,是掌握设备注册与依赖管理的关键。本节从结构体出发,逐步展开到match机制与probe流程。

一、三个核心结构体的职责划分

在include/linux/device.h中,struct bus_type描述一条总线(如platform、PCI、I2C、USB)的共性与操作集;struct device描述一个具体的设备实例;struct device_driver描述一个可驱动某类设备的驱动。三者通过kobject嵌入到sysfs中,形成用户空间可见的层次结构。

结构体 关键字段 作用
bus_type name、match、probe、remove、uevent、devices_kset、drivers_kset 定义总线匹配规则与设备/驱动注册入口
device kobj、parent、bus、driver、platform_data、of_node、release 表示一个设备实例,挂接到总线与父设备
device_driver name、bus、owner、probe、remove、of_match_table、id_table 表示驱动能力,声明可支持的设备范围

其中bus_type的match回调是匹配的核心,probe回调是绑定的核心。设备与驱动都注册到总线的两个kset中,总线负责在设备或驱动加入时触发匹配尝试。

二、匹配机制:match如何判断“门当户对”

当设备或驱动任一方向注册时,总线会调用bus_type->match(dev, drv)。不同总线的match实现不同,但常见策略包括:

  • 名称匹配:比较dev->init_name或dev_name(dev)与drv->name是否一致,platform总线早期常用。
  • ID表匹配:驱动提供id_table,总线遍历表项与设备ID比较,如I2C、SPI。
  • 设备树匹配:通过of_match_table比较compatible字符串,现代ARM平台主流方式。
  • ACPI匹配:通过acpi_match_table比较_HID/_CID。

以platform总线为例,其match顺序大致为:先尝试of_driver_match_device,再尝试acpi_driver_match_device,最后回退到platform_match_id与名称比较。只要任一策略命中,即认为匹配成功。

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    if (of_driver_match_device(dev, drv))
        return 1;
    if (acpi_driver_match_device(dev, drv))
        return 1;
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;
    return strcmp(pdev->name, drv->name) == 0;
}

三、probe流程:从匹配成功到驱动接管设备

匹配成功后,总线不会直接调用驱动的probe,而是通过device_driver_attach或really_probe完成绑定。核心步骤可概括为:

  1. 将dev->driver指向匹配到的驱动,建立双向引用。
  2. 调用bus_type->probe(若存在),再调用drv->probe。
  3. 驱动probe中完成资源申请、寄存器映射、中断注册、子系统注册等。
  4. 若probe返回0,设备进入bound状态,sysfs中生成driver符号链接。
  5. 若probe失败,解绑并释放已申请资源,设备回到未绑定状态。
关键点:probe是驱动真正“接管”设备的时刻。在此之前,设备只是被注册到总线;在此之后,设备才具备实际功能。因此probe中的错误处理必须严谨,否则会造成资源泄漏或设备不可用。

四、典型场景:platform设备与驱动的注册与绑定

以一颗I2C温度传感器为例,设备树中定义compatible为“vendor,tmp102”,内核启动时由of_platform_populate创建platform_device并注册到platform总线。驱动侧定义of_match_table匹配同一compatible,模块加载时注册platform_driver。总线match命中后调用驱动probe,probe中通过i2c或regmap读取寄存器并注册hwmon设备。

static const struct of_device_id tmp102_of_match[] = {
    { .compatible = "vendor,tmp102" },
    { }
};
MODULE_DEVICE_TABLE(of, tmp102_of_match);

static struct platform_driver tmp102_driver = {
    .probe = tmp102_probe,
    .remove = tmp102_remove,
    .driver = {
        .name = "tmp102",
        .of_match_table = tmp102_of_match,
    },
};
module_platform_driver(tmp102_driver);

当设备与驱动都注册后,可通过sysfs观察绑定结果:

ls /sys/bus/platform/devices/
ls /sys/bus/platform/drivers/
ls -l /sys/bus/platform/devices/*/driver

五、误区与复盘

常见误区一:认为设备注册后就会自动probe。实际上必须存在匹配的驱动,且probe成功才会绑定。误区二:在probe中直接使用dev->driver字段判断自身,此时绑定尚未完全建立。误区三:忽略probe返回值,导致失败后设备仍显示为已绑定。误区四:在match中做耗时操作,match应保持轻量,仅做标识比较。

复盘时建议重点检查:设备树compatible与驱动of_match_table是否一致;驱动是否已加载;总线match是否被正确调用;probe返回值与dmesg日志;sysfs中driver符号链接是否存在。掌握这三个结构体与match/probe链路,就掌握了Linux设备模型的主干。

Linux设备模型:bus / device / driver 匹配与probe bus_type name match() probe() / remove() device kobj / parent bus / driver of_node / platform_data device_driver name / bus probe() / remove() of_match_table / id_table 总线触发匹配:bus_type->match(dev, drv) of_match_table / id_table / name 比较 匹配成功 → really_probe() dev->driver = drv;调用 drv->probe() probe成功 → 设备绑定,sysfs生成driver链接 设备注册 驱动注册 任一方向 触发匹配