【Linux驱动开发】第7天:总线-设备-驱动三大核心模型:通俗讲解+完整流程图
·
目录
- 为什么需要总线-设备-驱动模型?传统驱动的致命痛点
- 大白话类比:用招聘平台一秒懂三大模型
- 三大核心角色详解:总线、设备、驱动
- 完整工作流程:Mermaid流程图+分步拆解
- 核心机制:总线如何自动匹配设备和驱动?
- 传统驱动 vs 总线驱动模型对比
- 最常用总线:platform平台总线快速入门
- 核心总结+面试必背考点
1. 为什么需要总线-设备-驱动模型?传统驱动的致命痛点
在学习总线模型之前,我们先回顾一下前几天写的传统字符设备驱动:
- 所有硬件信息(设备号、寄存器地址、中断号)都硬编码在驱动代码里
- 一个驱动只能对应一个硬件设备
- 换一个硬件,就要改驱动代码重新编译
- 不支持热插拔(比如U盘、鼠标)
- 代码复用性极差,维护成本极高
举个例子:如果有100个不同厂家的串口设备,传统驱动就要写100个几乎一样的驱动,每个驱动里硬编码不同的寄存器地址和中断号。
总线-设备-驱动模型的诞生,就是为了解决这个问题:它把硬件信息和驱动逻辑彻底分离,实现了"一个驱动支持多个设备,一个设备可以被多个驱动匹配",极大提高了代码复用性和系统扩展性。
2. 大白话类比:用招聘平台一秒懂三大模型
这是全网最通俗的类比,看完永远不会忘:
| Linux内核模型 | 招聘平台对应 | 核心作用 |
|---|---|---|
| 总线(Bus) | 招聘平台(智联、BOSS直聘) | 核心管理者,维护所有求职者和公司的信息,负责匹配双方 |
| 设备(Device) | 求职者 | 只描述自己的"个人信息"(硬件参数:地址、中断号、功能),不包含任何工作技能 |
| 驱动(Driver) | 公司 | 只描述自己的"招聘要求"(匹配规则)和"工作内容"(硬件操作逻辑),不包含任何具体求职者的信息 |
工作过程类比
- 求职者(设备)在招聘平台(总线)上注册简历,写明自己的技能和经历
- 公司(驱动)在招聘平台(总线)上发布招聘信息,写明岗位要求
- 招聘平台(总线)自动扫描所有求职者和公司,进行匹配
- 匹配成功:公司(驱动)录用求职者(设备),开始工作(驱动初始化硬件,提供服务)
- 求职者离职(设备拔出):公司(驱动)解除劳动关系,释放资源
- 公司倒闭(驱动卸载):所有员工(设备)被解雇
核心思想:分离与解耦
- 设备只负责描述"我是什么"
- 驱动只负责描述"我能做什么,我要什么样的设备"
- 总线只负责"把合适的人和合适的工作匹配到一起"
3. 三大核心角色详解
3.1 总线(Bus):内核的"匹配中心"
总线是整个模型的核心,是连接设备和驱动的桥梁。
核心职责:
- 维护两个链表:设备链表和驱动链表
- 提供设备注册和驱动注册的接口
- 实现匹配算法,当有新设备或新驱动注册时,自动扫描链表进行匹配
- 处理设备和驱动的注销事件
- 提供总线级别的通用功能(如电源管理、热插拔)
常见总线类型:
- 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 完整工作流程图
4.2 分步详细讲解
步骤1:驱动注册
- 驱动开发者编写驱动代码,实现probe、remove等函数
- 驱动加载时,调用
driver_register()将驱动注册到对应的总线 - 总线将驱动添加到自己的驱动链表中
- 总线自动遍历自己的设备链表,看有没有和这个驱动匹配的设备
步骤2:设备注册
- 系统启动时,内核解析设备树(或BIOS信息),发现硬件设备
- 或者热插拔设备插入(如U盘),内核检测到新设备
- 调用
device_register()将设备注册到对应的总线 - 总线将设备添加到自己的设备链表中
- 总线自动遍历自己的驱动链表,看有没有和这个设备匹配的驱动
步骤3:总线匹配
- 总线调用自己的
match()函数,比较设备和驱动的匹配规则 - 如果匹配成功,总线会建立设备和驱动之间的绑定关系
步骤4:执行probe函数
- 匹配成功后,总线自动调用驱动的
probe()函数 probe()函数是驱动的入口,负责:- 从设备结构体中获取硬件信息(地址、中断号等)
- 初始化硬件
- 注册字符设备/块设备/网络设备
- 创建设备文件,对外提供服务
步骤5:设备正常工作
- 设备和驱动绑定完成,用户可以通过
/dev/下的设备文件访问硬件 - 应用层的open/read/write等系统调用,最终会调用驱动中实现的函数
步骤6:设备拔出或驱动卸载
- 设备拔出时,总线调用驱动的
remove()函数 - 驱动卸载时,总线也会调用所有绑定设备的
remove()函数 remove()函数负责:- 注销设备
- 释放申请的资源(内存、中断、时钟等)
- 关闭硬件
- 设备和驱动解除绑定关系
5. 核心机制:总线如何自动匹配设备和驱动?
不同的总线有不同的匹配规则,最常见的有以下几种:
5.1 platform总线匹配规则(最常用)
platform总线是Linux内核中最基础、最常用的总线,用于连接片上外设(如GPIO、UART、I2C控制器)。
它的匹配优先级从高到低:
- 设备树匹配:比较设备树节点的
compatible属性和驱动的of_match_table - ACPI匹配:比较ACPI设备ID和驱动的
acpi_match_table - 名称匹配:比较设备的
name字段和驱动的name字段
5.2 I2C/SPI总线匹配规则
- 比较设备的
addr(I2C地址/SPI片选号)和驱动支持的地址列表 - 同时比较设备的
name字段和驱动的name字段
5.3 USB总线匹配规则
- 比较设备的
vendor ID(厂商ID)和product ID(产品ID) - 比较设备的类、子类、协议等信息
5.4 PCI总线匹配规则
- 比较设备的
vendor ID和device 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. 核心总结+面试必背考点
核心总结
- 总线-设备-驱动模型的核心思想是分离与解耦,把硬件信息和驱动逻辑彻底分开
- 总线是核心管理者,负责维护设备和驱动链表,实现自动匹配
- 设备只描述硬件信息,驱动只包含操作逻辑和匹配规则
- 匹配成功后,总线调用驱动的probe函数初始化设备
- 现代Linux驱动全部采用总线-设备-驱动模型,传统硬编码驱动已经被淘汰
- platform总线是最常用的总线,用于连接片上外设
面试必背考点
- 为什么Linux内核要采用总线-设备-驱动模型?它解决了什么问题?
- 总线、设备、驱动三者的关系是什么?各自的作用是什么?
- 总线-设备-驱动模型的完整工作流程是什么?
- platform总线的匹配规则是什么?优先级如何?
- probe函数的作用是什么?什么时候会被调用?
- remove函数的作用是什么?什么时候会被调用?
- 传统驱动和总线驱动模型的区别是什么?
- 什么是热插拔?总线模型如何支持热插拔?
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)