设备模型
一、为什么要有 Linux 设备模型?
早年 Linux 驱动:
- 各自为政,字符设备、块设备、网卡各玩各的
- 没有统一层级,没法统一电源管理、休眠唤醒、热插拔
- 没法在
/sys下面看到设备拓扑
设备模型解决三件事:
- 把所有总线、设备、驱动、类 组织成一棵内核树
- 自动完成 设备 ↔ 驱动 匹配
- 导出到 sysfs(/sys),用户空间能看、能配置、能热插拔
二、设备模型核心 5 大核心结构体(背住就懂了)
从上到下层级:
bus_type 总线
↓
device_driver 驱动
↓
device 设备
↓
class 设备类
↓
kobject 最底层基类(所有对象的祖宗)
1. struct kobject 内核对象
所有设备模型结构体的基类
- 内核所有总线、设备、类、驱动,都继承 kobject
- 自带引用计数、父子链表、sysfs 文件节点
- 实现:生命周期管理、树形结构、sysfs 目录
理解:kobject 是万物之父
2. struct bus_type 总线类型
代表一条总线,比如:
- platform 平台总线
- I2C 总线
- SPI 总线
- PCIe 总线
- USB 总线
总线做两件核心事:
- 维护两条链表:设备链表 、驱动链表
- 提供 match 匹配函数:拿设备和驱动比对,匹配上就调用 probe
3. struct device_driver 驱动基类
所有驱动的父类,平台驱动、I2C 驱动都继承它核心成员:
- name:驱动名字
- bus:属于哪条总线
- probe /remove/shutdown 等回调
4. struct device 设备基类
所有硬件设备的父类
- 挂在总线上
- 有父设备、子设备链表
- 设备树 node 对应到这里
- 电源管理、属性文件都挂在这
5. struct class 设备类
按功能分类,不按总线分类例子:
class /sys/class/tty串口类class /sys/class/input输入设备类class /sys/class/net网卡类
作用:同一类设备放一起,方便用户空间统一管理、创建设备节点。
三、核心工作流程(最重要,驱动天天在跑)
1. 注册顺序
- 内核先注册 总线 bus_type
- 驱动端:注册 device_driver 到对应总线
- 设备端:注册 device 到同一条总线
2. 设备和驱动匹配过程
- 总线遍历自己的设备链表 和驱动链表
- 调用总线的
match函数比对:- 传统:名字字符串匹配
- 设备树:compatible 字符串匹配
- 匹配成功 → 调用驱动的
probe()函数 - probe 里做硬件初始化、申请资源、创建设备节点
你写的平台驱动、设备树驱动,全是这个流程。
四、sysfs 和 设备模型的关系
/sys 目录就是 设备模型在用户态的镜像 每一个 kobject 对应 /sys 下一个目录每一个属性文件对应内核里的 attribute
常见目录对应:
/sys/bus→ 所有总线/sys/devices→ 真实设备树拓扑/sys/class→ 设备功能类/sys/drivers→ 已注册驱动
你之前代码里:
class_create();
device_create();
本质就是:在内核设备模型里创建 class、device 对象,并在 /sys 生成对应目录文件
五、你学驱动一定会碰到的三层架构
1. 传统字符设备(早期)
只做:主次设备号 + file_operations不依赖完整设备模型,简陋,无总线、无匹配
2. 平台总线驱动(标准 Linux 驱动写法)
基于设备模型:
- 挂在 platform 总线
- platform_driver → device_driver
- platform_device → device走标准 总线匹配 → probe 初始化
3. 设备树 DTS 驱动
设备树节点自动生成 platform_device驱动只需要写 compatible 匹配字符串内核自动帮你完成设备注册、总线匹配、调用 probe
六、极简层级结构图(一眼看懂)
kobject (基类)
├─ bus_type 总线
├─ device_driver 驱动
├─ device 设备
└─ class 设备类
总线(bus)
├─ 挂着一堆驱动(driver)
└─ 挂着一堆设备(device)
↓
设备归属到 class 类
↓
在 /sys 生成目录、属性文件
↓
device_create 在 /dev 生成设备节点
Platform 平台总线
1. 主机驱动和外设驱动分离思想
在 Linux 设备驱动框架的设计中, 除了有分层设计实现以外, 还有分隔的思想。
假如现在有三个平台 A、 B 和 C,这三个平台上都有MPU6050 ,I2C 接口的六轴传感器

每种平台下都有一个主机驱动和设备驱动,主机驱动是必须要的,不同的平台其 I2C 控制器不同。但是设备驱动没必要每个平台都写一
个,因为不管对于不同的SOC ,MPU6050是一样,都是通过 I2C 接口读写数据,只需要一个 MPU6050 的驱动程序即可。
所以,每个平台的 I2C 控制器都提供一个统一的接口(也叫做主机驱动),每个设备的话也只提供一个驱动程序(设备驱动),每个设备通过统
一的 I2C接口驱动来访问,这样就可以大大简化驱动文件。

以上就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来。比如 I2C、SPI会采用驱动分隔的方式来简化驱动的开发。在实际的驱动开
发中,一般 I2C 主机控制器驱动由半导体厂家编写好了,设备驱动一般由设备器件的厂家编写好了,开发人员只需要提供设备信息即可。
将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息 (比如从设备树中获取到设备信息),根据获取到的设备信息来初
始化设备。
驱动只负责驱动,设备只负责设备,总线将两者进行匹配。这就是Linux中的总线(bus)、驱动(driver)和设备(device)模型,即驱动分离。

当向系统注册一个驱动的时,总线会在设备中查找与之匹配的设备,如果有就将两者联系起来。同样的,当向系统中注册一个设备的时
候,总线就会在驱动中查找与之匹配的驱动,如果有也联系起来。 Linux 内核中大量的驱动程序都采用总线、驱动和设备模式。
2. Platform平台驱动模型
在 Linux 2.6 以后的设备驱动模型中, 需关心总线、 设备和驱动这 3 个实体, 总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;同样的,在系统每注册一个驱动的时候, 会寻找与之匹配的设备, 而匹配由总线完成。
Linux 设备和驱动通常都需要挂接在一种总线上, 对于本身依附于 PCI、 USB、 I2C、 SPI 等的设备而言, 这自然不是问题。但是在嵌入式系统里面, 在 SoC 系统中集成的独立外设控制器、 挂接在 SoC内存空间的外设等却不依附于此类总线。
基于这一背景, Linux 发明了一种虚拟的总线, 称为 platform 总线, 相应的设备称为 platform_device, 驱动称为 platform_driver。
通过这种方式实现了设备和驱动的分离, 增强设备驱动的可移植性。平台总线模型也称为 platform 总线模型,是 Linux 内核虚拟出来的一条总线, 不是真实的导线。
平台总线模型就是把原来的驱动C文件给分成了两个 C 文件,一个是 device.c, 一个是 driver.c 。把稳定不变的放在 driver.c 里面, 需要改变的就放在device.c 里面。

平台总线模型将设备代码和驱动代码分离, 将与硬件设备相关的都放到 device.c 文件里面,驱动部分代码放到 driver.c 文件里面。所以对
于大量的同类设备而言,只需要修改设备文件信息,驱动文件不用修改,可以提高代码的重用性,减少重复性代码。
三、Platform 三大核心结构体
1. 总线本身:struct bus_type platform_bus_type
内核早已提前定义好 ,你不用自己创建。位置:/sys/bus/platform
作用:
- 维护两条链表:platform 设备链表、platform 驱动链表
- 提供
match匹配函数,负责设备和驱动配对 - 匹配成功自动调用驱动的
probe
2. 设备端:struct platform_device
代表一个物理设备:
- 寄存器物理地址
- 中断号
- 设备名字、compatible 匹配字符串
- 资源(内存、中断)
设备树 DTS 节点,内核会自动解析、生成 platform_device,不用自己写代码创建设备。
3. 驱动端:struct platform_driver
你自己写的驱动就是这个:
- 匹配表(compatible 字符串)
probe:匹配成功就执行,做硬件初始化remove:设备卸载时释放资源
四、Platform 完整工作流程(背下来就能写驱动)
方式一:传统方式(不使用设备树)
- 内核已有 platform 总线
- 开发者注册 platform_device 设备(手动填资源、名字)
- 开发者注册 platform_driver 驱动
- platform 总线拿 设备名 和 驱动名 比对
- 匹配成功 → 调用 probe()
- probe 里:映射地址、申请中断、创建设备节点、注册字符设备
方式二:设备树 DTS 方式(现在主流)
- 写 DTS 设备树节点,写
compatible = "xxx,yyy" - 内核启动自动解析 DTS,生成 platform_device
- 驱动里
of_match_table写一样的 compatible 字符串 - platform 总线按 compatible 字符串匹配
- 匹配成功 → 执行 probe
- probe 中从设备树读取寄存器、中断、引脚等资源
现在 ARM/ARM64 驱动 全部用设备树 + platform 总线
五、Platform 驱动关键函数关系
cpp
platform_driver_register() 注册驱动
↓
总线匹配 match
↓
匹配成功 → 执行 probe()
↓
probe 里做:
ioremap 寄存器映射
request_irq 申请中断
cdev 注册字符设备
device_create 创建设备节点
卸载:
platform_driver_unregister() → 调用 remove()
六、最简 Platform 驱动代码(可直接编译运行)
platform_demo.c
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
// 匹配表:对应设备树 compatible
static const struct of_device_id my_of_match[] = {
{ .compatible = "my,platform_demo" },
{ /* 结束 */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
// probe:匹配成功进入
static int my_probe(struct platform_device *pdev)
{
pr_info("platform probe 匹配成功\n");
return 0;
}
// remove:设备移除
static int my_remove(struct platform_device *pdev)
{
pr_info("platform remove\n");
return 0;
}
// 注册 platform 驱动
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_platform_drv",
.of_match_table = my_of_match,
},
};
static struct platform_device *my_platform_dev;
static int __init my_platform_init(void)
{
int ret;
ret = platform_driver_register(&my_platform_driver);
if (ret) {
pr_err("platform_driver_register failed: %d\n", ret);
return ret;
}
/*
* 注册一个同名 platform_device,便于在无设备树 overlay 的环境中
* 也能触发 probe。
*/
my_platform_dev = platform_device_register_simple("my_platform_drv", -1, NULL, 0);
if (IS_ERR(my_platform_dev)) {
ret = PTR_ERR(my_platform_dev);
pr_err("platform_device_register_simple failed: %d\n", ret);
platform_driver_unregister(&my_platform_driver);
return ret;
}
pr_info("platform demo init done\n");
return 0;
}
static void __exit my_platform_exit(void)
{
platform_device_unregister(my_platform_dev);
platform_driver_unregister(&my_platform_driver);
pr_info("platform demo exit\n");
}
module_init(my_platform_init);
module_exit(my_platform_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Study");
运行示例

1) 模块加载入口
module_init(my_platform_init)指定加载时先执行my_platform_init()。- 在
my_platform_init()里做两步:platform_driver_register(&my_platform_driver):先把驱动注册到 platform 总线。platform_device_register_simple("my_platform_drv", ...):再注册一个同名设备,方便在没有设备树 overlay 时也能触发匹配。
2) 驱动匹配规则
- 驱动结构在
my_platform_driver:.driver.name = "my_platform_drv".of_match_table = my_of_match(设备树compatible = "my,platform_demo")
- 因为你手动注册的 device 名字就是
"my_platform_drv",会立刻和驱动匹配成功,于是调用my_probe()。 my_probe()当前只打印:platform probe 匹配成功。