Linux设备驱动框架与驱动模型深度解析
文章目录
- Linux设备驱动框架与驱动模型深度解析
-
-
- 引言
-
- Linux设备模型核心组件
-
- 2.1 Kobject, Kset 与 Ktype
- 2.2 总线 (Bus)、设备 (Device)、驱动 (Driver) 模型
-
- 2.2.1 总线 (struct bus_type)
- 2.2.2 设备 (struct device)
- 2.2.3 驱动 (struct device_driver)
-
- Sysfs 文件系统与驱动的关系
-
- 设备树 (Device Tree) 机制
-
- 4.1 核心概念
- 4.2 驱动与设备树的结合
- 4.3 传统模型 vs 设备树模型
-
- 常见驱动框架差异
-
- 5.1 字符设备 (Character Device)
- 5.2 块设备 (Block Device)
- 5.3 网络设备 (Network Device)
-
- 热插拔 (Hotplug) 机制实现
-
- 6.1 内核视角
- 6.2 用户空间视角
-
- Linux 5.x+ 内核驱动模型新特性
-
- 常见问题排查
-
- 参考文献与扩展学习
-
1. 引言
Linux设备驱动模型(Linux Device Model)是Linux内核中用于抽象和管理硬件设备的统一架构。随着硬件复杂度的增加,传统的驱动开发方式难以应对电源管理、热插拔、复杂的总线拓扑等问题。Linux 2.6内核引入了全新的设备模型,并持续演进至最新的5.x/6.x版本,成为现代Linux驱动开发的核心基石。
本文将从内核源码角度深入剖析Linux驱动框架,涵盖设备模型、总线-设备-驱动架构、sysfs、设备树以及各类驱动框架的差异。
2. Linux设备模型核心组件
Linux设备模型的核心在于构建一个分层的、面向对象的设备树状结构。其底层基础是 kobject 机制。
2.1 Kobject, Kset 与 Ktype
这三个结构体是设备模型的"基类",构成了内核对象的底层骨架。
- kobject: 它是设备模型中最基本的对象,对应sysfs中的一个目录。它提供了引用计数(kref)、父子层级关系(parent)和对象名称等基本功能。
- kset : 它是kobject的集合,通常用于将同类型的kobject归类(例如所有的总线都在
bus_kset下)。kset本身也包含一个kobject,因此它也能在sysfs中体现目录结构。 - ktype : 定义了kobject的通用操作,特别是属性文件的读写操作(sysfs_ops)和释放操作(release)。

图1: Kobject, Kset, Ktype 类关系图
2.2 总线 (Bus)、设备 (Device)、驱动 (Driver) 模型
这是Linux驱动模型中最著名的"铁三角"。
2.2.1 总线 (struct bus_type)
总线是处理器与设备之间的通道。在软件层面,总线负责管理挂载在它上面的设备和驱动。
- 核心职责 : 注册设备、注册驱动、匹配 (Match) 设备与驱动。
- 关键函数 :
match()。当有新设备或新驱动注册到总线上时,总线会调用match函数来检查两者是否适配。
2.2.2 设备 (struct device)
代表一个具体的物理或虚拟设备。
- 核心属性 :
struct bus_type *bus(所属总线),struct device_driver *driver(绑定的驱动),void *platform_data(平台数据),struct device_node *of_node(设备树节点)。
2.2.3 驱动 (struct device_driver)
代表处理特定设备的软件程序。
- 核心函数 :
probe()(匹配成功后初始化),remove()(卸载),suspend()/resume()(电源管理)。
三者关系运作流程:
图2: 总线匹配机制流程图*
3. Sysfs 文件系统与驱动的关系
Sysfs 是一个基于内存的虚拟文件系统,挂载在 /sys 下。它将内核中的设备模型层次结构导出到用户空间,提供了一种可视化的、可交互的视图。
- 映射关系 :
kobject-> sysfs 目录attribute-> sysfs 文件
- 作用 :
- 展示拓扑 :
/sys/devices/展示了全局设备层级。 - 控制接口 : 驱动可以通过
DEVICE_ATTR创建属性文件,用户可以通过cat读取状态或echo写入控制指令。 - 热插拔事件 : 用户空间的
udev/mdev通过监听内核发送的 uevent(基于 sysfs 路径)来动态创建/dev节点。
- 展示拓扑 :
典型目录结构:
bash
/sys/bus/ # 系统中所有的总线 (pci, i2c, usb, platform...)
/sys/class/ # 按功能分类的设备 (net, input, block, tty...)
/sys/devices/ # 所有设备的真实物理层次视图
/sys/drivers/ # 已加载的驱动程序
4. 设备树 (Device Tree) 机制
在ARM架构引入设备树之前,内核代码中充斥着大量的 arch/arm/mach-xxx 板级信息文件(Hard code)。设备树机制将硬件描述从内核源码中分离出来。
4.1 核心概念
- DTS (Device Tree Source): 文本格式的硬件描述文件。
- DTB (Device Tree Blob): 编译后的二进制文件,由Bootloader传递给内核。
- Device Node : 设备树中的节点,被内核解析为
struct device_node。
4.2 驱动与设备树的结合
Linux内核启动时会解析DTB,将节点转换为 platform_device(对于平台总线设备)。
- 匹配方式 :
of_match_table。
驱动程序中定义一个of_device_id数组,其中compatible属性必须与 DTS 节点中的compatible字符串一致。
c
static const struct of_device_id my_driver_dt_ids[] = {
{ .compatible = "vendor,my-device-v1", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_dt_ids);
static struct platform_driver my_driver = {
.driver = {
.name = "my-driver",
.of_match_table = my_driver_dt_ids, // 关联匹配表
},
.probe = my_probe,
// ...
};
4.3 传统模型 vs 设备树模型
| 特性 | 传统模型 (Legacy) | 设备树模型 (Device Tree) |
|---|---|---|
| 硬件描述 | C代码 (struct platform_device) | .dts 文本文件 |
| 修改硬件 | 需重新编译内核 | 只需重新编译DTS |
| 耦合度 | 代码与硬件强耦合 | 代码与硬件解耦 |
| 参数传递 | 平台数据 (pdata) | 设备树属性 (Properties) |
5. 常见驱动框架差异
Linux内核为不同类型的设备提供了不同的子系统框架。
5.1 字符设备 (Character Device)
- 特点: 按字节流访问,无缓存,不支持随机访问(通常)。
- 核心结构 :
struct cdev。 - API :
register_chrdev_region,cdev_add,file_operations(open, read, write, ioctl)。 - 应用: 串口、传感器、LED、GPIO、RTC。
5.2 块设备 (Block Device)
- 特点: 以块(扇区)为单位访问,有复杂的缓存和I/O调度算法,支持随机访问。
- 核心结构 :
struct gendisk,struct request_queue,struct bio。 - API :
alloc_disk,blk_mq_init_queue(多队列),submit_bio。 - 应用: 硬盘、SSD、SD卡、Flash存储。
5.3 网络设备 (Network Device)
- 特点 : 面向数据包,不对应
/dev下的设备节点,而是通过ifconfig(net_device) 管理。它是异步的。 - 核心结构 :
struct net_device,struct sk_buff(套接字缓冲区)。 - API :
register_netdev,netif_start_queue,ndo_start_xmit(发包函数)。 - 应用: 以太网卡、WiFi、CAN总线。
架构对比图:

图3: 三大设备驱动框架架构对比*
6. 热插拔 (Hotplug) 机制实现
热插拔是指在系统运行时添加或移除设备。
6.1 内核视角
当硬件被插入(如USB设备),总线控制器检测到电气变化,触发中断。内核枚举设备,创建 device 对象,并注册到驱动模型。
此时,内核对象核心(kobject core)会调用 kobject_uevent() 发送一个uevent消息(包含环境变量 ACTION=add, DEVPATH=..., SUBSYSTEM=...)。
6.2 用户空间视角
- 内核通过 netlink socket 向用户空间广播 uevent。
- 用户空间守护进程(udevd 或 embedded Linux 中的 mdev)监听该 socket。
- udevd 解析规则文件(
/etc/udev/rules.d/),根据匹配规则执行动作:- 加载驱动模块 (
modprobe)。 - 创建
/dev/下的设备节点 (mknod)。 - 设置权限或创建符号链接。
- 加载驱动模块 (
7. Linux 5.x+ 内核驱动模型新特性
随着内核演进,驱动模型也在不断优化:
- Driver Core 的清理 : 引入了
device_add_groups()等辅助函数,简化 sysfs 属性组的创建。 - fw_devlink (Firmware Device Links) :
- 在 5.x 内核中,引入了更智能的设备依赖管理。内核会自动解析设备树中的
phandle引用(如 clocks, regulators, iommus),并在消费者(Consumer)和提供者(Supplier)之间建立device_link。 - 效果 : 即使驱动加载顺序混乱,内核也能通过
EPROBE_DEFER机制和设备链接保证正确的探测顺序,无需驱动开发者手动处理复杂的依赖。
- 在 5.x 内核中,引入了更智能的设备依赖管理。内核会自动解析设备树中的
- Google Android GKI (Generic Kernel Image) 影响: 推动了模块化和接口标准化,使得驱动更容易作为独立模块维护。
- 辅助总线 (Auxiliary Bus) : 引入
auxiliary_bus,用于解决复杂的、多功能的驱动场景,允许一个主驱动创建多个辅助设备供其他子驱动绑定,解决了mfd(Multifunction Device) 框架在某些场景下的局限性。
8. 常见问题排查
在驱动开发中,常见问题及排查思路:
-
驱动未加载 / Probe 未执行:
- 检查
dmesg日志。 - 确认 DTS 中的
compatible字符串与驱动是否完全一致(包括空格)。 - 确认
status = "okay"。 - 检查
config是否开启了该驱动编译。 - 查看
/sys/bus/platform/devices/下是否有该设备节点(确认设备是否注册)。 - 查看
/sys/bus/platform/drivers/下是否有该驱动(确认驱动是否注册)。
- 检查
-
Probe 延迟 (Deferred Probe):
- 日志显示
deferring probe。 - 原因:依赖的资源(如时钟、电源、GPIO控制器)尚未就绪。
- 对策:这是正常机制,内核稍后会重试。如果一直失败,检查依赖的驱动是否加载成功。
- 调试:
cat /sys/kernel/debug/devices_deferred查看等待列表。
- 日志显示
-
Sysfs 节点无法创建:
- 检查
DEVICE_ATTR权限设置。 - 检查父对象是否已初始化。
- 检查
9. 参考文献与扩展学习
- Source Code : Linux Kernel Source
drivers/base/(核心代码),Documentation/driver-api/。 - Books :
- Linux Device Drivers, 3rd Edition (LDD3) - 经典但稍显陈旧,需结合新内核阅读。
- Essential Linux Device Drivers.
- Linux Kernel Development (Robert Love).
- Links :