目录

  1. 为什么需要总线-设备-驱动模型?传统驱动的致命痛点
  2. 大白话类比:用招聘平台一秒懂三大模型
  3. 三大核心角色详解:总线、设备、驱动
  4. 完整工作流程:Mermaid流程图+分步拆解
  5. 核心机制:总线如何自动匹配设备和驱动?
  6. 传统驱动 vs 总线驱动模型对比
  7. 最常用总线:platform平台总线快速入门
  8. 核心总结+面试必背考点

1. 为什么需要总线-设备-驱动模型?传统驱动的致命痛点

在学习总线模型之前,我们先回顾一下前几天写的传统字符设备驱动

  • 所有硬件信息(设备号、寄存器地址、中断号)都硬编码在驱动代码里
  • 一个驱动只能对应一个硬件设备
  • 换一个硬件,就要改驱动代码重新编译
  • 不支持热插拔(比如U盘、鼠标)
  • 代码复用性极差,维护成本极高

举个例子:如果有100个不同厂家的串口设备,传统驱动就要写100个几乎一样的驱动,每个驱动里硬编码不同的寄存器地址和中断号。

总线-设备-驱动模型的诞生,就是为了解决这个问题:它把硬件信息驱动逻辑彻底分离,实现了"一个驱动支持多个设备,一个设备可以被多个驱动匹配",极大提高了代码复用性和系统扩展性。


2. 大白话类比:用招聘平台一秒懂三大模型

这是全网最通俗的类比,看完永远不会忘:

Linux内核模型 招聘平台对应 核心作用
总线(Bus) 招聘平台(智联、BOSS直聘) 核心管理者,维护所有求职者和公司的信息,负责匹配双方
设备(Device) 求职者 只描述自己的"个人信息"(硬件参数:地址、中断号、功能),不包含任何工作技能
驱动(Driver) 公司 只描述自己的"招聘要求"(匹配规则)和"工作内容"(硬件操作逻辑),不包含任何具体求职者的信息

工作过程类比

  1. 求职者(设备)在招聘平台(总线)上注册简历,写明自己的技能和经历
  2. 公司(驱动)在招聘平台(总线)上发布招聘信息,写明岗位要求
  3. 招聘平台(总线)自动扫描所有求职者和公司,进行匹配
  4. 匹配成功:公司(驱动)录用求职者(设备),开始工作(驱动初始化硬件,提供服务)
  5. 求职者离职(设备拔出):公司(驱动)解除劳动关系,释放资源
  6. 公司倒闭(驱动卸载):所有员工(设备)被解雇

核心思想:分离与解耦

  • 设备只负责描述"我是什么"
  • 驱动只负责描述"我能做什么,我要什么样的设备"
  • 总线只负责"把合适的人和合适的工作匹配到一起"

3. 三大核心角色详解

3.1 总线(Bus):内核的"匹配中心"

总线是整个模型的核心,是连接设备和驱动的桥梁。

核心职责

  1. 维护两个链表:设备链表驱动链表
  2. 提供设备注册和驱动注册的接口
  3. 实现匹配算法,当有新设备或新驱动注册时,自动扫描链表进行匹配
  4. 处理设备和驱动的注销事件
  5. 提供总线级别的通用功能(如电源管理、热插拔)

常见总线类型

  • platform总线:用于片上外设(如GPIO、UART、I2C控制器)
  • I2C总线:用于I2C设备
  • SPI总线:用于SPI设备
  • USB总线:用于USB设备
  • PCI总线:用于PCI设备

3.2 设备(Device):硬件的"简历"

设备结构体只包含硬件的客观信息,不包含任何操作逻辑。

设备描述的信息包括

  • 硬件的物理地址(寄存器基地址)
  • 中断号
  • 时钟频率
  • 设备树节点信息(现代Linux系统)
  • 设备名称、唯一标识

核心特点

  • 一个设备只能属于一条总线
  • 设备可以在系统运行时动态注册和注销(支持热插拔)
  • 设备不关心哪个驱动会来驱动它,只需要如实描述自己的信息

3.3 驱动(Driver):硬件的"操作手册"

驱动结构体只包含硬件的操作逻辑匹配规则,不包含任何具体设备的信息。

驱动包含的内容

  • 匹配规则:告诉总线"我要什么样的设备"
  • probe函数:匹配成功后执行的初始化函数(注册字符设备、初始化硬件等)
  • remove函数:设备拔出或驱动卸载时执行的清理函数
  • 硬件操作函数:read、write、ioctl等

核心特点

  • 一个驱动可以支持多个符合匹配规则的设备
  • 驱动可以在系统运行时动态加载和卸载
  • 驱动不关心具体是哪个设备,只要符合匹配规则就可以驱动

4. 完整工作流程:Mermaid流程图+分步拆解

4.1 完整工作流程图

系统启动/驱动加载

驱动注册到总线
driver_register()

系统启动/设备插入

设备注册到总线
device_register()

总线触发匹配
遍历设备链表

总线触发匹配
遍历驱动链表

匹配成功?

调用驱动的probe函数
初始化硬件、注册设备

等待新设备/新驱动注册

设备正常工作
对外提供服务

设备拔出

调用驱动的remove函数
释放资源、注销设备

驱动卸载

设备和驱动解除绑定

4.2 分步详细讲解

步骤1:驱动注册
  • 驱动开发者编写驱动代码,实现probe、remove等函数
  • 驱动加载时,调用driver_register()将驱动注册到对应的总线
  • 总线将驱动添加到自己的驱动链表
  • 总线自动遍历自己的设备链表,看有没有和这个驱动匹配的设备
步骤2:设备注册
  • 系统启动时,内核解析设备树(或BIOS信息),发现硬件设备
  • 或者热插拔设备插入(如U盘),内核检测到新设备
  • 调用device_register()将设备注册到对应的总线
  • 总线将设备添加到自己的设备链表
  • 总线自动遍历自己的驱动链表,看有没有和这个设备匹配的驱动
步骤3:总线匹配
  • 总线调用自己的match()函数,比较设备和驱动的匹配规则
  • 如果匹配成功,总线会建立设备和驱动之间的绑定关系
步骤4:执行probe函数
  • 匹配成功后,总线自动调用驱动的probe()函数
  • probe()函数是驱动的入口,负责:
    1. 从设备结构体中获取硬件信息(地址、中断号等)
    2. 初始化硬件
    3. 注册字符设备/块设备/网络设备
    4. 创建设备文件,对外提供服务
步骤5:设备正常工作
  • 设备和驱动绑定完成,用户可以通过/dev/下的设备文件访问硬件
  • 应用层的open/read/write等系统调用,最终会调用驱动中实现的函数
步骤6:设备拔出或驱动卸载
  • 设备拔出时,总线调用驱动的remove()函数
  • 驱动卸载时,总线也会调用所有绑定设备的remove()函数
  • remove()函数负责:
    1. 注销设备
    2. 释放申请的资源(内存、中断、时钟等)
    3. 关闭硬件
  • 设备和驱动解除绑定关系

5. 核心机制:总线如何自动匹配设备和驱动?

不同的总线有不同的匹配规则,最常见的有以下几种:

5.1 platform总线匹配规则(最常用)

platform总线是Linux内核中最基础、最常用的总线,用于连接片上外设(如GPIO、UART、I2C控制器)。

它的匹配优先级从高到低:

  1. 设备树匹配:比较设备树节点的compatible属性和驱动的of_match_table
  2. ACPI匹配:比较ACPI设备ID和驱动的acpi_match_table
  3. 名称匹配:比较设备的name字段和驱动的name字段

5.2 I2C/SPI总线匹配规则

  • 比较设备的addr(I2C地址/SPI片选号)和驱动支持的地址列表
  • 同时比较设备的name字段和驱动的name字段

5.3 USB总线匹配规则

  • 比较设备的vendor ID(厂商ID)和product ID(产品ID)
  • 比较设备的类、子类、协议等信息

5.4 PCI总线匹配规则

  • 比较设备的vendor IDdevice ID
  • 比较设备的子系统ID、类代码等信息

核心要点:匹配规则是总线实现的,驱动只需要提供匹配表,设备只需要提供对应的信息


6. 传统驱动 vs 总线驱动模型对比

对比项 传统字符设备驱动 总线-设备-驱动模型
设计思想 设备和驱动硬绑定 设备和驱动完全分离
代码复用性 极差,一个驱动对应一个设备 极高,一个驱动支持多个设备
扩展性 差,新增设备需要修改驱动代码 好,新增设备只需要添加设备信息,不需要修改驱动
热插拔支持 不支持 原生支持
维护成本 高,每个设备都要维护一个驱动 低,只需要维护一个驱动
现代Linux支持 逐渐淘汰 标准写法,所有现代驱动都用这个模型

7. 最常用总线:platform平台总线快速入门

platform总线是所有驱动开发者必须掌握的总线,因为绝大多数片上外设都是通过platform总线连接的。

7.1 platform驱动的基本结构

#include <linux/platform_device.h>

// 匹配表:告诉总线我要匹配什么样的设备
static const struct of_device_id my_platform_match[] = {
    { .compatible = "myvendor,mydevice" }, // 设备树compatible属性
    { /* 必须以空结尾 */ }
};
MODULE_DEVICE_TABLE(of, my_platform_match);

// 匹配成功后执行的初始化函数
static int my_probe(struct platform_device *pdev)
{
    printk(KERN_INFO "设备和驱动匹配成功\n");
    
    // 1. 从pdev中获取硬件信息(地址、中断号等)
    // 2. 初始化硬件
    // 3. 注册字符设备
    // 4. 创建设备文件
    
    return 0;
}

// 设备拔出或驱动卸载时执行的清理函数
static int my_remove(struct platform_device *pdev)
{
    printk(KERN_INFO "设备和驱动解除绑定\n");
    
    // 1. 注销设备
    // 2. 释放资源
    
    return 0;
}

// platform驱动结构体
static struct platform_driver my_platform_driver = {
    .driver = {
        .name = "my_platform_driver", // 名称匹配用
        .of_match_table = my_platform_match, // 设备树匹配用
    },
    .probe = my_probe,
    .remove = my_remove,
};

// 模块入口:注册platform驱动
static int __init my_driver_init(void)
{
    return platform_driver_register(&my_platform_driver);
}

// 模块出口:注销platform驱动
static void __exit my_driver_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}

module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");

7.2 核心要点

  • platform驱动不需要自己申请设备号、创建cdev,这些都可以在probe函数中完成
  • 所有硬件信息都从struct platform_device *pdev中获取,不需要硬编码
  • 一个platform驱动可以匹配多个设备树节点中compatible属性为"myvendor,mydevice"的设备

8. 核心总结+面试必背考点

核心总结

  1. 总线-设备-驱动模型的核心思想是分离与解耦,把硬件信息和驱动逻辑彻底分开
  2. 总线是核心管理者,负责维护设备和驱动链表,实现自动匹配
  3. 设备只描述硬件信息,驱动只包含操作逻辑和匹配规则
  4. 匹配成功后,总线调用驱动的probe函数初始化设备
  5. 现代Linux驱动全部采用总线-设备-驱动模型,传统硬编码驱动已经被淘汰
  6. platform总线是最常用的总线,用于连接片上外设

面试必背考点

  1. 为什么Linux内核要采用总线-设备-驱动模型?它解决了什么问题?
  2. 总线、设备、驱动三者的关系是什么?各自的作用是什么?
  3. 总线-设备-驱动模型的完整工作流程是什么?
  4. platform总线的匹配规则是什么?优先级如何?
  5. probe函数的作用是什么?什么时候会被调用?
  6. remove函数的作用是什么?什么时候会被调用?
  7. 传统驱动和总线驱动模型的区别是什么?
  8. 什么是热插拔?总线模型如何支持热插拔?
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐