4. 设备模型基本概念
在裸机开发中,我们通常直接操作寄存器:打开时钟、配置引脚、写数据寄存器。这种方式简单直接,但当系统里出现多个相同外设、多个驱动需要复用、设备需要动态插拔时,代码会迅速变得难以维护。RTOS 设备模型的核心目标,就是把“硬件资源”和“操作硬件的方法”解耦,用统一的抽象让上层应用不再关心底层差异。
一、四个核心概念
设备(Device)是对一个具体硬件实例的抽象。它描述“有一个 UART,位于 0x40004000,使用中断 12,波特率 115200”。设备对象通常包含名称、地址、中断号、私有数据指针以及指向所属驱动的指针。设备是“被操作的对象”。
驱动(Driver)是对一类硬件操作方法的封装。它描述“如何初始化 UART、如何发送一个字节、如何接收一个字节”。驱动提供 open、close、read、write、ioctl 等标准接口,是“操作的方法”。同一个驱动可以服务多个同型号设备实例。
总线(Bus)是设备与驱动相遇的场所,也是匹配规则的载体。在 Linux 中体现为 platform、I2C、SPI、USB 等总线;在 RTOS 中通常简化为“设备注册表 + 匹配键”。总线负责把设备挂上去、把驱动挂上去,然后按名称或 ID 进行配对。
类(Class)是对具有相同行为特征设备的进一步归类,例如串口类、块设备类、网络类、输入类。类向上层提供统一的操作接口,使应用可以用同一套 API 操作不同厂商的同类硬件。
二、RTOS 中的抽象方式
与 Linux 相比,RTOS 设备模型更轻量,但保留了核心思想。常见做法是:用一个全局设备链表保存所有已注册设备;每个设备结构体内嵌一个驱动操作集指针;驱动通过注册函数把操作集绑定到设备;应用通过设备名查找设备,再调用其操作集。
typedef struct rt_device {
const char *name; /* 设备名,如 "uart1" */
struct rt_device_ops *ops; /* 驱动操作集 */
void *user_data; /* 设备私有数据 */
rt_uint16_t type; /* 设备类:串口/块/网络 */
rt_uint16_t flag; /* 读写权限、打开标志 */
struct rt_device *next; /* 设备链表 */
} rt_device_t;
struct rt_device_ops {
rt_err_t (*init) (rt_device_t dev);
rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
rt_err_t (*close) (rt_device_t dev);
rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buf, rt_size_t size);
rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buf, rt_size_t size);
rt_err_t (*control)(rt_device_t dev, int cmd, void *args);
};
这段结构体是设备模型的骨架:name 用于查找,ops 用于调用,type 用于归类,user_data 用于保存硬件相关上下文。应用层只持有 rt_device_t,不关心它背后是 STM32 的 USART 还是 NXP 的 LPUART。
三、注册、匹配与使用流程
- 设备注册:板级初始化时,把每个硬件实例填入设备结构体,调用
rt_device_register()挂入全局链表。 - 驱动绑定:驱动模块把自己的操作集通过
rt_device_register()或匹配函数绑定到对应设备。 - 查找设备:应用调用
rt_device_find("uart1"),按名称遍历链表返回设备句柄。 - 打开与操作:调用
rt_device_open()触发驱动的 open,之后用 read/write/control 完成数据交互。 - 关闭与注销:不再使用时 close,系统退出时 unregister 释放资源。
下面这张图展示了设备、驱动、总线、类四者之间的关系,以及应用通过设备句柄访问硬件的路径。
四、典型场景:串口设备的完整生命周期
假设系统中有两个 UART:调试串口 uart0 和通信串口 uart1。板级代码先注册两个设备对象,驱动模块注册一份 UART 操作集。总线按名称把操作集绑定到两个设备上。应用调用 rt_device_find("uart1") 得到句柄,rt_device_open() 触发驱动的 open 完成时钟与引脚初始化,随后 rt_device_write() 把数据写入发送寄存器。整个过程应用只认识“串口类”的接口,不关心底层是哪个芯片。
| 概念 | 职责 | RTOS 中的典型载体 | 关键字段/接口 |
|---|---|---|---|
| 设备 Device | 描述一个具体硬件实例 | rt_device 结构体实例 | name、user_data、type |
| 驱动 Driver | 封装一类硬件的操作方法 | rt_device_ops 操作集 | init、open、read、write、control |
| 总线 Bus | 提供注册与匹配场所 | 全局设备链表 / 匹配函数 | register、find、match |
| 类 Class | 定义同类设备的统一行为 | 设备 type 分类 + 统一 API | RT_Device_Class_Char 等 |
五、常见误区与复盘
误区二:在应用层直接调用驱动函数。绕过设备句柄会让上层与具体驱动耦合,换芯片时应用需要大改。
误区三:忽略总线匹配的失败处理。设备注册了但驱动没绑定,
rt_device_find 可能返回句柄而 open 失败,必须在 open 中检查 ops 是否为空。
理解设备、驱动、总线、类这四个概念及其在 RTOS 中的轻量抽象,是后续学习具体驱动框架、设备树描述和电源管理的基础。设备模型不是额外负担,而是让驱动可复用、应用可移植的关键一层。