Linux 设备模型和platform平台

设备模型

一、为什么要有 Linux 设备模型?

早年 Linux 驱动:

  • 各自为政,字符设备、块设备、网卡各玩各的
  • 没有统一层级,没法统一电源管理、休眠唤醒、热插拔
  • 没法在 /sys 下面看到设备拓扑

设备模型解决三件事:

  1. 把所有总线、设备、驱动、类 组织成一棵内核树
  2. 自动完成 设备 ↔ 驱动 匹配
  3. 导出到 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 总线

总线做两件核心事:

  1. 维护两条链表:设备链表驱动链表
  2. 提供 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. 注册顺序

  1. 内核先注册 总线 bus_type
  2. 驱动端:注册 device_driver 到对应总线
  3. 设备端:注册 device 到同一条总线

2. 设备和驱动匹配过程

  1. 总线遍历自己的设备链表驱动链表
  2. 调用总线的 match 函数比对:
    • 传统:名字字符串匹配
    • 设备树:compatible 字符串匹配
  3. 匹配成功 → 调用驱动的 probe() 函数
  4. 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 完整工作流程(背下来就能写驱动)

方式一:传统方式(不使用设备树)

  1. 内核已有 platform 总线
  2. 开发者注册 platform_device 设备(手动填资源、名字)
  3. 开发者注册 platform_driver 驱动
  4. platform 总线拿 设备名 和 驱动名 比对
  5. 匹配成功 → 调用 probe()
  6. probe 里:映射地址、申请中断、创建设备节点、注册字符设备

方式二:设备树 DTS 方式(现在主流)

  1. 写 DTS 设备树节点,写 compatible = "xxx,yyy"
  2. 内核启动自动解析 DTS,生成 platform_device
  3. 驱动里 of_match_table 写一样的 compatible 字符串
  4. platform 总线按 compatible 字符串匹配
  5. 匹配成功 → 执行 probe
  6. 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() 里做两步:
    1. platform_driver_register(&my_platform_driver):先把驱动注册到 platform 总线。
    2. 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 匹配成功
相关推荐
大树8815 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠15 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质15 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush415 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行52016 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz16 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工16 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智17 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩17 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_17 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化