Linux 总线-设备-驱动(Bus-Device-Driver)完整协作原理

一、核心三角色定义

  1. 总线 bus_type

    物理总线:I2C、SPI、PCI、CAN、Platform 平台总线;

    内核抽象 struct bus_type,统一管理匹配规则、探测、卸载、sysfs 接口。

    作用:作为中介,负责设备和驱动匹配、管理挂载在这条总线上的所有设备/驱动。

  2. 设备 device(struct device)

    描述一个真实硬件外设,保存硬件资源(地址、中断、GPIO、时钟、设备树compatible字符串)。

    设备只描述「硬件有什么资源」,不含操作硬件的代码。

  3. 驱动 driver(struct device_driver / platform_driver)

    包含操作硬件的代码(寄存器初始化、read/write、中断处理),提供 probe 探测函数。

    驱动只描述「怎么操作硬件」,不绑定固定硬件。

二、核心协作逻辑:总线做媒人,设备、驱动自动配对

匹配核心规则

总线内部提供匹配回调函数,内核自动遍历:

  1. 新注册设备 → 遍历当前总线上所有已注册驱动,挨个匹配;
  2. 新注册驱动 → 遍历当前总线上所有已注册设备,挨个匹配;
    匹配成功 → 执行驱动 .probe() 函数,完成硬件初始化;
    匹配失败 → 二者保持独立,无任何交互。

不同总线匹配规则不一样:

  • Platform总线:匹配设备树 compatible 字符串;
  • I2C/SPI:匹配设备ID表;
  • PCI:匹配厂商ID、设备ID。

三、完整工作流程分两种场景

场景1:先有设备,后加载驱动(设备树常用,RK3568主流)

  1. 内核解析设备树,在Platform总线下创建 struct device 设备实例;
    存入:总线维护的设备链表,此时只有设备,无驱动。
  2. 加载驱动 .ko,驱动调用 platform_driver_register() 向Platform总线注册 platform_driver;
  3. 总线触发匹配流程:拿驱动的id_table/compatible和总线上所有设备对比;
  4. 找到compatible完全一致的设备 → 匹配成功;
  5. 总线调用驱动的 .probe() 函数:
    • probe内从device中提取硬件资源(寄存器基地址、中断、PWM引脚);
    • 初始化时钟、GPIO、寄存器;
    • 内部创建cdev、class、device_create,生成/dev/xxx字符设备节点;
  6. 用户层open /dev/xxx,调用file_operations操作硬件。

场景2:先注册驱动,后出现设备(热插拔硬件,USB/I2C)

  1. 开机先加载驱动,驱动注册到对应总线,挂在驱动链表;
  2. 硬件热插入总线,内核检测到硬件,生成struct device挂入总线设备链表;
  3. 总线自动拿新设备和所有驱动匹配;
  4. 匹配成功执行probe,初始化硬件。

四、三者数据结构关联关系

  1. struct bus_type

    • 维护两条双向链表:设备链表、驱动链表;
    • 核心回调:match()(匹配)、probe()、remove()、shutdown();
    • sysfs目录:/sys/bus/xxx/,存放devices、drivers子目录。
  2. struct device(硬件)

    • 挂载在对应bus的设备链表;
    • 成员 bus 指向所属总线;
    • 成员 driver:匹配成功后,指向绑定的驱动;
    • 存放硬件资源:struct resource 寄存器、irq、dma、设备树节点。
  3. struct device_driver(驱动代码)

    • 挂载在对应bus的驱动链表;
    • 成员 bus 指向所属总线;
    • 内置匹配表(compatible字符串/ID列表);
    • 提供 .probe 初始化硬件,.remove 卸载释放资源。

绑定关系一句话

device.bus = 所属总线

driver.bus = 所属总线

匹配成功后 device.driver = 对应驱动

五、以你之前的PWM字符驱动举完整实例(Platform总线)

  1. 设备(设备树)
dts 复制代码
pwm_demo@10000000 {
    compatible = "rockchip,pwm-demo";
    reg = <0x10000000 0x1000>;
    gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
};

内核启动自动生成platform device,挂载到platform总线。

  1. 驱动(platform_driver)
c 复制代码
static const struct of_device_id pwm_of_match[] = {
    { .compatible = "rockchip,pwm-demo" },
    {}
};

static struct platform_driver pwm_plat_drv = {
    .probe = pwm_probe,
    .remove = pwm_remove,
    .driver = {
        .name = "pwm_drv",
        .of_match_table = pwm_of_match,
    },
};

module_platform_driver(pwm_plat_drv);
  1. 加载驱动,注册到platform总线;
  2. 总线匹配compatible字符串,匹配成功调用 pwm_probe;
  3. probe函数内部:
    解析reg获取寄存器、解析gpio、使能时钟;
    执行字符设备整套流程:alloc_chrdev_region、cdev_init、cdev_add、class_create、device_create;
  4. 用户操作/dev/pwm_subsystem控制硬件;
  5. rmmod驱动,总线调用 .remove,逆序释放硬件、销毁字符设备。

六、如果自己自定义一套全新总线(拓展你提问的"创建自己的总线")

1. 自定义总线核心结构

定义 struct bus_type my_bus,实现match/probe/remove;

内核调用 bus_register(&my_bus) 注册全新总线。

2. 自定义设备、自定义驱动

  • 自定义设备结构体内嵌 struct device dev;
  • 自定义驱动内嵌 struct device_driver drv;
  • 设备注册进自定义总线,驱动也注册到同一总线;
  • 总线match函数实现自定义匹配规则(比如自定义硬件ID)。

3. 适用场景

极少需要自定义总线;现有Platform/I2C/SPI/PCI总线已经覆盖绝大多数外设,仅自研私有硬件协议才会新建总线。

七、总线模型核心优势(为什么Linux要这套架构)

  1. 设备、驱动完全解耦
    驱动不写死寄存器地址,硬件信息全部放设备树(设备侧);更换硬件只需改设备树,驱动代码不用修改。
  2. 一份驱动适配多个同类型硬件
    只要compatible匹配,同一个驱动可以驱动多个同型号外设。
  3. 自动管理生命周期
    热插拔、卸载、电源管理由总线统一调度,不用驱动单独处理。
  4. sysfs统一可视化管理
    /sys/bus/ 下可直观查看总线、挂载的设备、绑定的驱动,方便调试。

八、极简总结协作流程

  1. 总线作为统一管理容器,分别维护设备链表、驱动链表;
  2. 新增设备/驱动时,总线执行match匹配规则;
  3. 匹配成功,总线调用驱动probe完成硬件初始化;
  4. probe中创建字符设备等交互接口,供用户层使用;
  5. 卸载时总线调用remove,释放硬件与内核资源;
    设备和驱动永远不直接互相查找,全部通过总线中转配对。
相关推荐
JSMSEMI118 小时前
JSM20NP06D N 沟道 + P 沟道互补对管 MOSFET
microsoft
草莓熊Lotso8 小时前
【C++ 基于微服务的即时通讯系统】网关基建:cpp-httplib 与 WebSocketpp 从入门到实战
linux·开发语言·数据库·c++·redis·微服务·云原生
李纲明8 小时前
WordPress 媒体库上传失败或一直转圈:先核 PHP 上传限制与目录权限,再查安全插件与对象存储
运维·开发语言·安全·php·wordpress·媒体库·devdiy
志栋智能16 小时前
运维超自动化的版本控制与回滚机制
运维·自动化
中云DDoS CC防护蔡蔡16 小时前
DDOS和CC攻击谁更可怕
运维·服务器·经验分享·网络安全·ddos
Ruiery16 小时前
Linux 6.6内核内存管理深度解析(十六):mmap — 用户态“申请内存“的真相
linux·运维·服务器
ClickHouseDB16 小时前
ClickHouse Cloud 与 Snowflake 实时性价比对比:CostBench 测试中的 412 倍差距来
运维·数据库·系统架构
欧米欧17 小时前
Linux系统篇(七),环境变量的底层溯源,核心获取机制与安全应用
linux·运维·服务器
Ruiery17 小时前
Linux 6.6内核内存管理深度解析(十八):KASLR 内核地址空间随机化
linux·运维·服务器·安全
todoitbo17 小时前
群晖NAS部署JmalCloud:Docker安装、文件管理、博客与公网访问完整教程
运维·docker·容器