2. Linux 设备模型基础:总线、设备、驱动模型,sysfs 文件系统与设备节点的关系

在嵌入式 Linux 系统中,设备节点是用户空间与内核硬件驱动交互的“门把手”。要理解设备节点如何被创建、管理以及映射到具体的硬件,必须首先掌握 Linux 设备模型的核心三要素:总线(Bus)、设备(Device) 和 驱动(Driver)。这三者通过内核的 sysfs 文件系统 暴露给用户空间,最终与 /dev 下的设备节点形成一一对应的映射关系。

2.1 设备模型三要素:总线、设备、驱动

Linux 内核从 2.6 版本开始引入了统一的设备驱动模型(Device Driver Model),其核心思想是将系统中的所有硬件设备抽象为三个相互关联的对象,并通过一个公共的 kobject 机制进行管理。

要素 内核数据结构 核心职责 HAL 移植关注点
总线 (Bus) struct bus_type 定义设备与驱动的匹配规则(match 函数);管理挂载在该总线上的设备和驱动链表。 确认目标 SoC 使用的总线类型(如 platform、I2C、SPI、PCIe),总线匹配规则是否满足硬件拓扑。
设备 (Device) struct device 描述一个具体的硬件实体,包含资源信息(如中断号、内存地址、DMA 通道)。 设备树(Device Tree)中的节点会被解析为 struct device,HAL 需要从中提取寄存器基址等资源。
驱动 (Driver) struct device_driver 包含 probe()、remove() 等操作函数,负责初始化硬件并提供操作接口。 HAL 驱动需要实现 probe() 函数,在匹配成功后创建设备节点并注册文件操作接口。
关键机制: 当内核启动或热插拔事件发生时,总线会遍历其设备链表和驱动链表,调用 match() 函数进行配对。配对成功后,调用驱动的 probe() 方法,驱动在此函数中完成硬件初始化并创建设备节点。

2.2 设备模型核心关系图

下图展示了总线、设备、驱动三者之间的核心关系以及它们与 sysfs 和 /dev 的映射路径:

Linux 设备模型核心关系与设备节点映射 总线 (Bus) 设备 (Device) 驱动 (Driver) 挂载 注册 match() 配对 probe() sysfs 文件系统 (/sys/bus/... /sys/devices/... /sys/class/...) 设备节点 (/dev/xxx) 映射关系: 设备树节点 → struct device → sysfs 目录 → class 子系统 → device_create() → /dev/xxx

2.3 sysfs 文件系统:内核对象到用户空间的桥梁

sysfs 是一个基于内存的虚拟文件系统,通常挂载在 /sys 目录下。它将内核中的 kobject 层次结构以目录和文件的形式导出到用户空间。对于 HAL 移植工程师而言,sysfs 是调试和验证设备模型正确性的第一现场。

2.3.1 sysfs 核心目录结构

sysfs 路径 对应内核对象 HAL 移植用途
/sys/bus/ 所有注册的总线类型(platform、i2c、spi、pci 等) 查看总线支持的设备和驱动列表,确认驱动是否成功注册到对应总线。
/sys/devices/ 系统中所有设备的拓扑树 查看设备在 SoC 内存空间中的挂载位置,检查设备资源是否被正确解析。
/sys/class/ 按功能分类的设备类(如 tty、input、gpio、iio) 确认设备是否被正确归类,HAL 通常通过 class 接口创建设备节点。
/sys/dev/ 设备号与设备名称的映射 查看主次设备号分配情况,用于验证 mknod 或 devtmpfs 是否正确创建设备节点。
实战技巧: 在 HAL 驱动移植调试阶段,执行 ls -l /sys/bus/platform/devices/ 可以快速确认 platform 设备是否被内核枚举。如果设备节点不存在,说明设备树解析或总线匹配可能失败。

2.4 设备节点:从内核到用户空间的最终映射

设备节点是用户空间程序访问内核驱动的标准接口,通常位于 /dev 目录下。设备节点的创建依赖于设备号(主设备号 + 次设备号),而设备号的分配与 sysfs 紧密关联。

2.4.1 设备节点创建流程

  1. 设备号分配: 驱动在 probe() 中调用 alloc_chrdev_region() 或 register_chrdev_region() 获取设备号。
  2. cdev 注册: 调用 cdev_init() 和 cdev_add() 将字符设备添加到内核。
  3. class 创建: 调用 class_create() 在 /sys/class/ 下创建分类目录。
  4. device 创建: 调用 device_create() 在 /sys/class/xxx/ 下创建设备目录,并自动在 /dev 下生成设备节点(依赖 devtmpfs 或 udev)。

2.4.2 设备节点与 sysfs 的对应关系

以下是一个典型的 I2C 设备在 sysfs 和 /dev 中的映射示例:

# sysfs 中的设备信息
/sys/bus/i2c/devices/0-0050/  →  I2C 地址为 0x50 的 EEPROM 设备
  ├── name                    →  "24c02"
  ├── eeprom                  →  通过该文件直接读写 EEPROM 内容
  └── driver                  →  指向 /sys/bus/i2c/drivers/at24/

# /dev 下的设备节点
/dev/i2c-0                   →  I2C 总线控制器 0 的设备节点(主设备号 89,次设备号 0)
/dev/eeprom                  →  用户空间通过该节点访问 EEPROM 驱动
常见陷阱: 在 HAL 移植中,如果 device_create() 调用成功但 /dev 下没有出现节点,通常是因为内核配置中未启用 CONFIG_DEVTMPFS 或用户空间缺少 udev/mdev 守护进程。此时需要手动 mknod 或检查 initramfs 配置。

2.5 设备模型与设备节点映射的完整流程

下面的 SVG 流程图展示了从设备树解析到 /dev 节点生成的全链路:

设备节点映射全链路流程图 设备树解析 (of_node → platform_device) 总线匹配 (bus.match → driver.probe) 驱动 probe() (硬件初始化) 分配设备号 (alloc_chrdev_region) cdev 注册 (cdev_init + cdev_add) class/device 创建 (class_create + device_create) /dev/xxx 节点生成 关键数据结构与 API • struct platform_device:设备树解析后的设备对象 • struct of_device_id:设备树匹配表 • bus_type.match():设备与驱动的配对函数 • device_create():在 sysfs 和 /dev 创建设备节点 • devtmpfs:内核自动管理 /dev 的机制 HAL 移植检查清单: 1. 设备树 compatible 属性是否与驱动匹配表一致 2. probe() 中是否调用了 device_create() 3. 内核是否配置了 CONFIG_DEVTMPFS 4. 用户空间是否有 udev/mdev 处理设备节点

2.6 本章小结

  • 总线、设备、驱动 是 Linux 设备模型的三大支柱,通过 match() + probe() 机制完成硬件与驱动的绑定。
  • sysfs 将内核对象层次结构导出到 /sys,是 HAL 移植工程师调试设备枚举和资源分配的核心工具。
  • 设备节点 的生成依赖于设备号分配、cdev 注册、class/device 创建三步流程,最终通过 devtmpfs 或 udev 在 /dev 下呈现。
  • 在 HAL 驱动移植中,设备节点映射失败通常源于设备树匹配错误、probe() 未正确调用 device_create()、或内核配置缺少 CONFIG_DEVTMPFS。