news 2026/4/3 5:00:35

20260103 - Linux总线设备驱动模型学习笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
20260103 - Linux总线设备驱动模型学习笔记

Linux 总线设备驱动模型学习笔记

1. 详细介绍总线设备驱动模型

1.1 产生背景:为什么要引入它?

在传统的字符设备驱动编写中,开发者通常将“硬件资源(引脚、地址)”和“软件逻辑(操作流程)”混写在一起。这种方式存在两个主要缺陷:

  • 代码冗余:如果有多个相同的硬件(如 10 个相同的 LED 灯),开发者可能需要拷贝多份几乎相同的驱动代码。
  • 可移植性差:一旦硬件引脚发生变动,必须修改并重新编译整个驱动程序。

为了解决这些问题,Linux 引入了分离(Separation)的思想,即“总线-设备-驱动”模型。


1.2 核心思想:分离与分层

总线设备驱动模型将一个驱动程序的核心任务拆分为两个独立的模块:设备(Device)驱动(Driver),并通过总线(Bus)将它们撮合在一起。

① 设备 (platform_device):描述“有什么”
  • 职责:负责提供硬件资源。
  • 内容:包括硬件的基地址、中断号、GPIO 引脚编号等物理参数。
  • 特点:当硬件更换引脚或地址时,只需要修改设备端的代码,而不需要动业务逻辑。
② 驱动 (platform_driver):描述“怎么做”
  • 职责:负责实现具体的业务逻辑。
  • 内容:包括如何初始化硬件(init)、如何点亮 LED(ctl)、如何处理文件操作(file_operations)等。
  • 特点:驱动程序通过内核提供的接口动态地从“设备端”获取资源,从而实现“一套代码驱动多个板卡”的目标。
③ 总线 (Bus):负责“撮合”
  • 职责:管理设备和驱动的匹配。
  • 机制:总线维护着两张链表(设备链表和驱动链表)。每当一个新的设备或驱动注册到总线上时,总线就会自动触发“匹配(Match)”动作。

1.3 平台总线 (Platform Bus)

对于嵌入式系统中的 SoC 内部控制器(如 I.MX6ULL 的 GPIO、UART、I2C 等),它们通常直接挂在 CPU 内部总线上,并没有类似 USB 或 PCI 这种可以被物理感知的实际总线。

为此,Linux 内核虚拟出了一种总线,称为平台总线 (Platform Bus)

  • 结构体:内核中使用struct platform_device来代表硬件资源,使用struct platform_driver来代表驱动逻辑。
  • 匹配标志:默认情况下,它们通过.name属性(字符串)进行匹配。

1.4 模型的优势总结

维度传统方式总线驱动模型
代码结构硬件资源与逻辑耦合资源与逻辑完全分离
可移植性差,换硬件需改源码编译强,只需更换设备端资源
管理效率低,容易造成驱动代码冗余高,支持多实例匹配

2. 匹配规则

当我们在内核中注册一个platform_device或一个platform_driver时,系统会自动调用平台总线的匹配函数(通常是platform_match)。匹配的目的是为了让驱动程序找到它能控制的硬件资源。

内核遵循一套严格的优先级顺序进行匹配,主要分为以下三个阶段:

2.1 第一优先级:强制选择 (driver_override)

这是优先级最高的一种方式,允许开发者手动“指定”匹配关系。

  • 规则:比较platform_device.driver_overrideplatform_driver.driver.name
  • 原理:如果platform_device结构体中的driver_override成员被设置了某个驱动的名字,那么这个设备将无视其他匹配规则,只与名字相符的驱动进行绑定。
  • 用途:常用于调试,或者在系统中有多个兼容驱动时,强制指定使用某一个驱动。

2.2 第二优先级:ID 表匹配 (id_table)

如果第一阶段没有匹配成功,内核会检查驱动程序是否支持一系列设备。

  • 规则:比较platform_device.nameplatform_driver.id_table[i].name
  • 原理
    • platform_driver结构体中包含一个id_table数组(类型为struct platform_device_id)。
    • 这个数组就像是一个“白名单”,列出了该驱动能够支持的所有设备名称。
    • 内核会遍历这个数组,如果发现某个{.name = "xxx"}与设备的name一致,则匹配成功。
  • 优势:一个驱动程序可以支持多个名字不同的设备。例如,一个 LED 驱动可以同时支持名字为"led_red""led_blue"的两个设备。
  • 私有数据id_table还可以携带driver_data,为不同的匹配项提供不同的私有配置数据。

2.3 第三优先级:名字匹配 (Name Match)

这是最常用、也是最简单的匹配方式,当id_table为空时使用。

  • 规则:比较platform_device.nameplatform_driver.driver.name
  • 原理:直接比对设备结构体中的名字和驱动结构体(内嵌的device_driver)中的名字。
  • 代码体现
    • 在设备端:.name = "100ask_led"
    • 在驱动端:.driver = { .name = "100ask_led" }
  • 局限性:这种方式要求名字必须完全一致,且一个驱动通常只能对应一种名称的设备。

匹配规则优先级表

优先级比较对象 A比较对象 B说明
1 (最高)device.driver_overridedriver.name强制覆盖规则
2 (中等)device.namedriver.id_table[i].name支持多设备列表匹配
3 (最低)device.namedriver.driver.name最基础的同名匹配

2.4 特殊说明:设备树匹配 (Device Tree)

虽然你提到的规则主要针对物理代码定义的匹配,但现代内核(如 4.9.88)最常用的其实是设备树匹配,它的优先级通常非常高:

  • 规则:比较设备树节点的compatible属性与驱动的of_match_table列表。
  • 位置:在上述三种规则之前或之中执行,取决于具体的内核版本实现。

这是 Linux 总线设备驱动模型学习笔记的第三部分,重点分析内核是如何将设备和驱动联结在一起并触发业务代码的。

3. 函数调用关系

在总线模型中,函数调用不再是线性的,而是由内核总线框架根据“注册”行为触发的事件驱动。

3.1 注册阶段的调用关系

① 驱动注册 (platform_driver_register)

当我们在led_drv.c的入口函数中调用platform_driver_register时,内核会发生以下调用:

  1. platform_driver_register(drv): 驱动入口函数发起请求。
  2. driver_register: 进入内核通用驱动层。
  3. bus_add_driver: 将驱动添加到platform_bus的驱动链表中。
  4. driver_attach: 尝试在总线上寻找匹配的设备。
  5. bus_for_each_dev: 遍历总线上的设备链表,对每个设备调用__driver_attach
② 设备注册 (platform_device_register)

在传统的设备端代码(如led_dev.c)中注册设备时:

  1. platform_device_register(pdev): 向内核声明硬件资源。
  2. device_add: 将设备添加到platform_bus的设备链表中。
  3. bus_probe_device: 尝试为该新设备寻找匹配的驱动。
  4. device_initial_probe->__device_attach: 遍历驱动链表进行匹配。

3.3 匹配与探测的核心:Match 与 Probe

无论是先加载驱动还是先加载设备,最终都会汇聚到匹配 (Match)探测 (Probe)这两个核心步骤。

① 匹配 (Match) 过程

内核调用总线定义的.match函数(对于平台总线是platform_match):

  • 该函数会按照上一章提到的优先级(driver_override->id_table->name)进行字符串比对。
  • 如果返回 1:表示匹配成功,进入下一步。
  • 如果返回 0:表示不匹配,继续查找下一个。
② 探测 (Probe) 过程

一旦 Match 成功,内核会启动probe调用链:

  1. really_probe: 内核确认匹配后,准备调用驱动的具体实现。
  2. drv->probe(即imx6ull_led_probe):
    • 获取资源:驱动程序通过platform_get_resource拿到设备定义的寄存器基地址。
    • 地址映射:执行ioremap得到虚拟地址。
    • 注册接口:调用register_chrdev并创建/dev下的设备节点。

3.4 卸载阶段的调用关系 (Remove)

当执行rmmod卸载驱动,或设备被拔出时:

  1. platform_driver_unregister: 发起注销请求。
  2. driver_unregister->__device_release_driver: 找到已绑定的设备。
  3. drv->remove(即imx6ull_led_remove):
    • 执行iounmap释放虚拟地址。
    • 销毁设备节点和类。
    • 注销字符设备号。

3.5 逻辑关系总结表

动作触发函数内核行为驱动层响应
装载驱动platform_driver_register找设备名并匹配匹配成功则执行probe
装载设备platform_device_register找驱动名并匹配匹配成功则执行probe
匹配逻辑platform_match执行三级匹配规则(内核自动执行)
卸载逻辑platform_driver_unregister解除绑定关系执行remove

这是 Linux 总线设备驱动模型学习笔记的第四部分,重点解析驱动开发中频繁调用的内核 API。

4. 常用函数:注册、反注册与资源获取

在平台总线模型中,开发者不再直接操作底层的链表,而是通过内核提供的标准 API 来完成设备资源的声明和驱动逻辑的加载。

4.1 设备端常用函数 (Platform Device)

设备端代码(如led_dev.c)的主要任务是定义struct resource并注册设备。

① 注册与反注册
  • int platform_device_register(struct platform_device *pdev)
    • 作用:将定义好的平台设备注册到内核中。
    • 行为:将设备加入platform_bus的设备链表,并触发总线的匹配过程。
  • void platform_device_unregister(struct platform_device *pdev)
    • 作用:注销设备。
    • 行为:从链表中移除设备,并触发已绑定驱动的remove函数。
② 静态定义宏
  • struct platform_device_register_simple(...)
    • 作用:这是一个快捷函数,用于一次性完成分配、设置和注册一个简单的平台设备。

4.2 驱动端常用函数 (Platform Driver)

驱动端代码(如led_drv.c)负责实现业务逻辑并提取资源。

① 注册与反注册
  • int platform_driver_register(struct platform_driver *drv)
    • 作用:将驱动程序注册到内核。
    • 行为:将驱动加入总线的驱动链表,并搜索匹配的设备。
  • void platform_driver_unregister(struct platform_driver *drv)
    • 作用:注销驱动程序。
  • module_platform_driver(drv)
    • 作用:宏定义。它可以代替initexit函数中冗长的注册/注销代码,是一个一键注册宏。
② 获取资源 (Resource Management)

这是probe函数中最核心的操作:

  • struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
    • 参数说明
      • dev:由内核传给probe的设备指针。
      • type:资源类型(如IORESOURCE_MEM代表内存/寄存器地址,IORESOURCE_IRQ代表中断号)。
      • num:同类资源的索引(比如有两组内存资源,第 0 组和第 1 组)。
    • 示例:获取寄存器物理基地址。

4.3 资源类型定义 (struct resource)

资源结构体是设备向驱动传递物理参数的载体。

structresource{resource_size_tstart;// 资源的起始位置(如物理地址 0x020AC000)resource_size_tend;// 资源的结束位置constchar*name;// 资源的名字unsignedlongflags;// 资源类型标志(IORESOURCE_MEM, IORESOURCE_IO, IORESOURCE_IRQ)};

4.4 函数功能对照表

函数分类函数名称典型使用位置核心目的
设备注册platform_device_registerled_dev.c(init)声明硬件资源存在。
驱动注册platform_driver_registerled_drv.c(init)声明操作逻辑就绪。
资源获取platform_get_resourceprobe函数内部动态获取硬件物理地址。
地址映射devm_ioremap_resourceprobe函数内部将获取的资源自动进行映射(更高级的ioremap)。
驱动注销platform_driver_unregisterled_drv.c(exit)停止驱动并释放资源。

这是 Linux 总线设备驱动模型学习笔记的最后一部分,将设备端、驱动端以及内核总线的交互逻辑完整串联。

5. 写程序的流程

编写一个基于总线模型的驱动程序,本质上是完成“资源定义”“逻辑实现”的解耦。整个流程分为设备端(Resource)和驱动端(Logic)两个独立文件的编写。

5.1 设备端流程:分配 / 设置 / 注册platform_device

目标:告诉内核硬件的物理参数,但不涉及任何控制逻辑。

  1. 定义资源 (struct resource)
    • 指定寄存器的起始地址、长度及类型(IORESOURCE_MEM)。
    • 指定中断号等其他资源(如果需要)。
  2. 设置platform_device结构体
    • .name:指定设备名称(如"100ask_led"),这是与驱动匹配的唯一“暗号”。
    • .resource:关联刚才定义的资源数组。
    • .num_resources:资源的个数。
  3. 注册设备
    • 在模块入口函数中调用platform_device_register()
    • 在模块出口函数中调用platform_device_unregister()

5.2 驱动端流程:分配 / 设置 / 注册platform_driver

目标:编写通用的控制逻辑,动态地根据拿到的资源进行操作。

  1. 定义file_operations结构体
    • 实现标准的open,write等接口。
  2. 实现probe函数(核心逻辑点)
    • 获取资源:调用platform_get_resource()拿到设备端传来的物理地址。
    • 地址映射:调用ioremap()将物理地址转为虚拟地址。
    • 注册字符设备:调用register_chrdev()并创建类与设备节点。
  3. 实现remove函数
    • 执行iounmap()释放地址映射。
    • 注销字符设备并销毁设备节点。
  4. 设置platform_driver结构体
    • .probe.remove:指向刚才实现的函数。
    • .driver.name:必须与设备端的.name完全一致,才能触发匹配。
  5. 注册驱动
    • 在模块入口函数调用platform_driver_register()

5.3 整体运行逻辑串联表

步骤模块动作内核行为结果
1设备端 (led_dev.ko)insmod将设备加入总线设备链表等待驱动匹配
2驱动端 (led_drv.ko)insmod将驱动加入总线驱动链表触发匹配检查
3总线系统匹配name发现name一致调用驱动的probe
4驱动端 (probe)获取资源并映射注册字符设备驱动/dev/100ask_led0出现
5用户空间open/write经过系统调用到达驱动层LED 物理状态改变
6驱动端 (remove)rmmod释放资源,注销设备系统恢复清洁

5.4 为什么这样写更高效?

  • 解耦:如果你的 LED 从 GPIO5_3 换到了 GPIO3_3,你只需要修改并重新编译设备端文件,而处理逻辑复杂的驱动端 (led_drv.c) 一行代码都不用动。
  • 多实例:如果你有两组 LED 硬件,只需注册两个platform_device(使用相同名字),内核就会自动调用两次驱动的probe,为你生成两个设备节点。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/28 8:08:33

Windows Defender彻底移除终极指南:3步搞定所有版本

Windows Defender彻底移除终极指南:3步搞定所有版本 【免费下载链接】windows-defender-remover A tool which is uses to remove Windows Defender in Windows 8.x, Windows 10 (every version) and Windows 11. 项目地址: https://gitcode.com/gh_mirrors/wi/wi…

作者头像 李华
网站建设 2026/4/1 14:07:44

Windows平台Btrfs文件系统完整指南:跨平台数据管理终极方案

Windows平台Btrfs文件系统完整指南:跨平台数据管理终极方案 【免费下载链接】btrfs WinBtrfs - an open-source btrfs driver for Windows 项目地址: https://gitcode.com/gh_mirrors/bt/btrfs 还在为Windows和Linux系统间的文件共享而烦恼吗?想象…

作者头像 李华
网站建设 2026/3/30 8:33:11

SD-PPP:在Photoshop中无缝集成AI绘图功能的完整指南

SD-PPP是一个革命性的开源项目,它通过在Photoshop和ComfyUI之间建立实时数据桥梁,让设计师能够在熟悉的Photoshop界面中直接调用强大的AI绘图功能。这个项目彻底改变了传统AI绘图的工作流程,让创意实现变得更加高效直接。 【免费下载链接】sd…

作者头像 李华
网站建设 2026/4/1 21:27:29

Steam模组下载终极解决方案:WorkshopDL一键获取创意工坊资源

Steam模组下载终极解决方案:WorkshopDL一键获取创意工坊资源 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 还在为GOG版《星露谷物语》无法安装Steam创意工坊模组而…

作者头像 李华
网站建设 2026/3/31 11:25:20

OpenCore Legacy Patcher终极指南:轻松解锁旧Mac的隐藏潜力

OpenCore Legacy Patcher终极指南:轻松解锁旧Mac的隐藏潜力 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为手中的老Mac无法跟上时代步伐而苦恼吗&#…

作者头像 李华
网站建设 2026/3/21 9:55:09

如何彻底解决网盘限速:直链下载助手的完整配置教程

如何彻底解决网盘限速:直链下载助手的完整配置教程 【免费下载链接】Online-disk-direct-link-download-assistant 可以获取网盘文件真实下载地址。基于【网盘直链下载助手】修改(改自6.1.4版本) ,自用,去推广&#xf…

作者头像 李华