5.1 设备驱动框架
5.1 设备驱动框架
📚 本节导读
学习时长: 约 90 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言结构体、函数指针、链表、信号量、第 4 章内核组件
本节讲解 OneOS 设备驱动框架的核心架构与全部核心 API。设备驱动框架是所有具体设备驱动(GPIO、I2C、Serial、SPI、Touch 等)的地基——你只有先理解 os_device_t 是怎么组织的、os_device_* 系列 API 是怎么工作的,才能读懂后续每一类具体设备的驱动代码。
本节会带你:
- 看懂
os_device_t这个"一切设备皆统一"的控制块,它的每个字段从哪来、干什么用; - 理解 IOCTL 命令编码体系与设备类型枚举;
- 逐个拆解注册、注销、查找、打开、关闭、读写、控制、通知、遍历等 15 个核心 API 的完整源码与执行流程;
- 通过多个可运行示例掌握实际开发中的标准用法。
🎯 学习目标
- 理解 OneOS 设备驱动框架的分层架构与"统一抽象 + 统一接口"设计理念
- 掌握
os_device_t核心数据结构的每个字段含义及其来源 - 掌握
os_device_ops操作函数表与设备类型枚举 - 理解 IOCTL 命令的高 8 位/低 8 位编码规则及具体数值演算
- 掌握设备注册、注销、查找、打开、关闭、读写、控制、通知的完整流程与每个 API 的源码实现
- 理解设备信号量的作用与阻塞/非阻塞读写机制
- 掌握设备驱动从"注册到使用"的完整生命周期
一、概述
1.1 分层架构与统一抽象
OneOS 设备驱动框架采用分层架构,所有设备统一抽象为 os_device_t 结构体,通过统一的 ops 操作接口访问。设备框架提供设备注册/注销、打开/关闭、读写、控制等统一接口,遵循"一切皆设备"的设计理念。
┌─────────────────────────────────────────────────────────────────┐
│ 应用层(Application) │
│ 使用 os_device_* 系列 API 访问任意设备 │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 设备驱动框架(Device Core) │
│ os_device_register / find / open / close / read / write │
│ control / notify / for_each + 全局设备链表 + 设备信号量 │
└───────────────────────────────┬─────────────────────────────────┘
│ 调用 ops->xxx
▼
┌─────────────────────────────────────────────────────────────────┐
│ 设备驱动(Device Driver) │
│ 实现 struct os_device_ops 的五个函数指针 │
│ init / deinit / read / write / control │
└───────────────────────────────┬─────────────────────────────────┘
│ 操作寄存器/外设
▼
┌─────────────────────────────────────────────────────────────────┐
│ 硬件(MCU 外设/总线) │
└─────────────────────────────────────────────────────────────────┘框架的核心优势在于:
| 特性 | 说明 |
|---|---|
| 统一抽象 | 字符、块、网络、总线设备等均以 os_device_t 表示 |
| 统一接口 | 所有设备通过 init/deinit/read/write/control 五个标准操作函数访问 |
| 引用计数 | 自动管理设备打开/关闭,防止设备在使用中被卸载 |
| 同步机制 | 内置三个信号量(sem/tx_sem/rx_sem)支持阻塞/非阻塞读写 |
| 通知机制 | 支持设备事件通知,方便上层监听设备状态变化 |
| 设备列表 | 全局设备链表统一管理所有已注册设备,可按名查找、遍历 |
1.2 设备框架的全局资源
设备框架在 drivers/device.c 中维护了几个全局资源,是整个框架运作的基础。理解它们,对后续 API 源码详解至关重要。
来源:drivers/device.c
static os_semaphore_id dev_sem;
static os_semaphore_dummy_t dev_sem_dummy;
static os_list_node_t os_device_list = OS_LIST_INIT(os_device_list);
OS_DEFINE_SPINLOCK(gs_device_lock);全局资源说明:
| 全局变量 | 类型 | 作用 |
|---|---|---|
dev_sem | os_semaphore_id | 设备框架全局互斥信号量(初值 1),保护设备列表/所有设备的 ref_count 操作,避免多任务并发访问设备框架造成竞争 |
dev_sem_dummy | os_semaphore_dummy_t | dev_sem 的静态占位控制块,供 os_semaphore_create 静态创建 dev_sem 使用 |
os_device_list | os_list_node_t | 全局设备链表头,用 OS_LIST_INIT 初始化。所有已注册设备通过各自的 list 节点挂到这个链表 |
gs_device_lock | os_spinlock | 设备框架自旋锁,保护回调链表(cb_heads)、通知链表(notify_list)等在中断上下文也会访问的资源 |
关键理解:
os_device_list是整个系统只有 1 个的全局链表头,管理所有类型的已注册设备。dev_sem是设备框架的"看门锁"——注册、查找、打开、关闭、遍历这些 API 都先os_semaphore_wait(dev_sem, OS_WAIT_FOREVER)拿锁,做完再os_semaphore_post(dev_sem)释放,保证设备列表和ref_count的原子性。gs_device_lock与dev_sem职责不同:dev_sem用于普通任务上下文,而gs_device_lock(自旋锁)用于可能被中断打断的回调链表/通知链表操作。
1.3 设备信号量的三类占位与真实信号量
os_device_t 中有三组"占位 + 真实"信号量成员(sem_dummy/sem、tx_sem_dummy/tx_sem、rx_sem_dummy/rx_sem)。要理解这种设计,先看信号量的两种创建方式。
来源:kernel/include/os_sem.h
typedef struct dummy_semaphore os_semaphore_dummy_t;
typedef os_semaphore_dummy_t *os_semaphore_id;
/*
* Here, actually, it is an array of semaphore control structure size.
* The purpose is to serve as a semaphore control block for
* static create of semaphore.
*/
#define OS_SEMAPHORE_DEFINE(name) os_semaphore_dummy_t name关键理解:
os_semaphore_dummy_t是"占位类型",它的大小等于真实信号量控制块的大小,但内容不透明。- 设备框架在
os_device_register里调用os_semaphore_create(&dev->sem_dummy, name, 1, 1),把sem_dummy这块占位内存作为信号量控制块,静态创建信号量,得到真正的os_semaphore_id(即dev->sem)。 - 三对成员的分工:
| 占位成员 | 真实成员 | 信号量初值 | 用途 |
|---|---|---|---|
sem_dummy | sem | 1(二值信号量,初始可用) | 保护单个设备内部操作的互斥(如读写时的数据访问、ref_count 变更) |
tx_sem_dummy | tx_sem | 1(初始可用) | 发送同步,阻塞写等待可写条件 |
rx_sem_dummy | rx_sem | 0(初始不可用) | 接收同步,阻塞读等待数据就绪 |
为什么
rx_sem初值是 0 而tx_sem初值是 1?
因为接收信号量表示"有没有数据可读"——初始时没有数据,所以为 0,任务os_semaphore_wait(rx_sem)会阻塞等待;发送信号量表示"能不能写"——初始时设备空闲可以写,所以为 1,os_semaphore_wait(tx_sem)立即通过。这正体现了信号量"计数"语义在设备读写同步中的运用。
二、核心数据结构
2.1 设备类型枚举 enum os_device_type
OneOS 定义了 27 种设备类型(OS_DEVICE_TYPE_CHAR~OS_DEVICE_TYPE_GENERIC),涵盖了嵌入式系统中常见的所有外设类型。设备类型同时决定了该设备的 IOCTL 命令空间(见 2.4 节)。
来源:drivers/device.h
enum os_device_type
{
OS_DEVICE_TYPE_CHAR = 0, /* character device. */
OS_DEVICE_TYPE_BLOCK, /* block device. */
OS_DEVICE_TYPE_NETIF, /* Net interface. */
OS_DEVICE_TYPE_MTD, /* Memory device. */
OS_DEVICE_TYPE_CAN, /* CAN device. */
OS_DEVICE_TYPE_RTC, /* RTC device. */
OS_DEVICE_TYPE_SOUND, /* Sound device. */
OS_DEVICE_TYPE_GRAPHIC, /* Graphic device. */
OS_DEVICE_TYPE_I2CBUS, /* I2C bus device. */
OS_DEVICE_TYPE_USBDEVICE, /* USB slave device. */
OS_DEVICE_TYPE_USBHOST, /* USB host bus. */
OS_DEVICE_TYPE_SPIBUS, /* SPI bus device. */
OS_DEVICE_TYPE_SPIDEVICE, /* SPI device. */
OS_DEVICE_TYPE_SDIO, /* SDIO bus device. */
OS_DEVICE_TYPE_PM, /* PM pseudo device. */
OS_DEVICE_TYPE_PIPE, /* Pipe device. */
OS_DEVICE_TYPE_PORTAL, /* Portal device. */
OS_DEVICE_TYPE_CLOCKSOURCE, /* ClockSource device. */
OS_DEVICE_TYPE_CLOCKEVENT, /* ClockEvent device. */
OS_DEVICE_TYPE_MISCELLANEOUS, /* Miscellaneous device. */
OS_DEVICE_TYPE_SENSOR, /* Sensor device. */
OS_DEVICE_TYPE_TOUCH, /* Touch device. */
OS_DEVICE_TYPE_INFRARED, /* Infrared device. */
OS_DEVICE_TYPE_WLAN, /* Wlan device. */
OS_DEVICE_TYPE_PWM, /* PWM device. */
OS_DEVICE_TYPE_ENCODER, /* Encoder device. */
OS_DEVICE_TYPE_GENERIC /* Generic device. */
};枚举取值说明(C 枚举默认从 0 开始逐个递增):
| 枚举常量 | 数值 | 说明 |
|---|---|---|
OS_DEVICE_TYPE_CHAR | 0 | 字符设备 |
OS_DEVICE_TYPE_BLOCK | 1 | 块设备 |
OS_DEVICE_TYPE_NETIF | 2 | 网络接口 |
OS_DEVICE_TYPE_MTD | 3 | 存储设备(MTD) |
OS_DEVICE_TYPE_CAN | 4 | CAN 设备 |
OS_DEVICE_TYPE_RTC | 5 | RTC 设备 |
OS_DEVICE_TYPE_SOUND | 6 | 音频设备 |
OS_DEVICE_TYPE_GRAPHIC | 7 | 图形设备 |
OS_DEVICE_TYPE_I2CBUS | 8 | I2C 总线设备 |
OS_DEVICE_TYPE_USBDEVICE | 9 | USB 从设备 |
OS_DEVICE_TYPE_USBHOST | 10 | USB 主机总线 |
OS_DEVICE_TYPE_SPIBUS | 11 | SPI 总线设备 |
OS_DEVICE_TYPE_SPIDEVICE | 12 | SPI 设备 |
OS_DEVICE_TYPE_SDIO | 13 | SDIO 总线设备 |
OS_DEVICE_TYPE_PM | 14 | 电源管理伪设备 |
OS_DEVICE_TYPE_PIPE | 15 | 管道设备 |
OS_DEVICE_TYPE_PORTAL | 16 | 门户设备 |
OS_DEVICE_TYPE_CLOCKSOURCE | 17 | 时钟源设备 |
OS_DEVICE_TYPE_CLOCKEVENT | 18 | 时钟事件设备 |
OS_DEVICE_TYPE_MISCELLANEOUS | 19 | 杂项设备 |
OS_DEVICE_TYPE_SENSOR | 20 | 传感器设备 |
OS_DEVICE_TYPE_TOUCH | 21 | 触摸设备 |
OS_DEVICE_TYPE_INFRARED | 22 | 红外设备 |
OS_DEVICE_TYPE_WLAN | 23 | 无线网络设备 |
OS_DEVICE_TYPE_PWM | 24 | PWM 设备 |
OS_DEVICE_TYPE_ENCODER | 25 | 编码器设备 |
OS_DEVICE_TYPE_GENERIC | 26 | 通用设备 |
关键点:
- 这些数值不是随便定的——它们直接用于 IOCTL 命令编码(见 2.4 节),
OS_DEVICE_TYPE_GENERIC = 26意味着通用命令的高 8 位是0x1A(26 的十六进制)。 - 设备状态枚举
enum os_device_status也很常用,一并列出:
来源:drivers/device.h
enum os_device_status
{
OS_DEVICE_STATUS_NORMAL = 0,
OS_DEVICE_STATUS_REMOVED,
};| 枚举常量 | 数值 | 说明 |
|---|---|---|
OS_DEVICE_STATUS_NORMAL | 0 | 设备正常 |
OS_DEVICE_STATUS_REMOVED | 1 | 设备已被移除(热插拔时置位) |
2.2 设备操作函数表 struct os_device_ops
每个设备驱动必须实现 os_device_ops 中的操作函数指针,这是驱动与框架之间的"契约"。
来源:drivers/device.h
struct os_device_ops
{
os_err_t (*init)(os_device_t *dev);
os_err_t (*deinit)(os_device_t *dev);
os_ssize_t (*read)(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size);
os_ssize_t (*write)(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size);
os_err_t (*control)(os_device_t *dev, int cmd, void *args);
};| 函数指针 | 函数签名 | 说明 |
|---|---|---|
init | os_err_t (*)(os_device_t *dev) | 设备初始化,在设备首次打开(ref_count 从 0 变 1)时由框架调用 |
deinit | os_err_t (*)(os_device_t *dev) | 设备反初始化,在设备关闭到 ref_count 归零时由框架调用,释放设备资源 |
read | os_ssize_t (*)(os_device_t *, os_off_t, void *, os_size_t) | 读取设备数据,返回实际读取字节数 |
write | os_ssize_t (*)(os_device_t *, os_off_t, const void *, os_size_t) | 写入设备数据,返回实际写入字节数 |
control | os_err_t (*)(os_device_t *, int cmd, void *) | 通用设备控制,通过 cmd 区分不同命令 |
关键点:
read/write返回os_ssize_t(有符号大小),既能表示实际字节数(正数),也能返回负数错误码。control的第三个参数类型是void *args(在os_device_control声明中叫arg),用于传递命令参数,需按具体命令自行转换。- 这五个函数指针可以部分为空(
OS_NULL),框架在调用前都会检查(见 4.6 节源码中的dev->ops->read != OS_NULL等判断)。例如只读设备可以不实现write。
2.3 设备结构体 struct os_device —— 设备控制块字段详解
这是设备框架最核心的数据结构。每个已注册的设备实例都会有一份 struct os_device,应用层拿到的 os_device_t * 就指向它。
来源:drivers/device.h
struct os_device
{
char name[OS_NAME_MAX + 1];
enum os_device_type type;
enum os_device_status status;
int32_t ref_count;
const struct os_device_ops *ops;
os_list_node_t list;
os_list_node_t cb_heads[OS_DEVICE_CB_TYPE_NUM];
os_semaphore_dummy_t sem_dummy;
os_semaphore_dummy_t tx_sem_dummy;
os_semaphore_dummy_t rx_sem_dummy;
os_semaphore_id sem;
os_semaphore_id tx_sem;
os_semaphore_id rx_sem;
int32_t tx_count;
int32_t rx_count;
int32_t tx_size;
int32_t rx_size;
os_tick_t tx_timeout;
os_tick_t rx_timeout;
void *user_data;
#ifdef OS_USING_IO_MULTIPLEXING
os_list_node_t poll_list;
#endif
#ifdef OS_USING_DEVICE_NOTIFY
os_list_node_t notify_list;
#endif
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_t plug;
#endif
};2.3.1 字段含义表
| 成员 | 类型 | 说明 |
|---|---|---|
name | char[OS_NAME_MAX + 1] | 设备名称,最大长度 OS_NAME_MAX(默认 15),用于设备查找,注册时由 strncpy 填充 |
type | enum os_device_type | 设备类型,决定设备所属的 IOCTL 命令空间 |
status | enum os_device_status | 设备状态,OS_DEVICE_STATUS_NORMAL 或 OS_DEVICE_STATUS_REMOVED |
ref_count | int32_t | 引用计数,os_device_open 时 +1,os_device_close 时 -1;为 0 时方可卸载 |
ops | const struct os_device_ops * | 操作函数表,指向驱动实现的五个标准操作函数 |
list | os_list_node_t | 设备链表节点,注册时挂入全局 os_device_list |
cb_heads | os_list_node_t[OS_DEVICE_CB_TYPE_NUM] | 回调链表数组(RX/TX 两类,见下方回调链表头说明),挂设备收发回调 |
sem_dummy | os_semaphore_dummy_t | sem 的静态占位控制块 |
tx_sem_dummy | os_semaphore_dummy_t | tx_sem 的静态占位控制块 |
rx_sem_dummy | os_semaphore_dummy_t | rx_sem 的静态占位控制块 |
sem | os_semaphore_id | 通用互斥信号量(初值 1),保护设备内部互斥(读写/ref_count) |
tx_sem | os_semaphore_id | 发送同步信号量(初值 1),阻塞写等待可写 |
rx_sem | os_semaphore_id | 接收同步信号量(初值 0),阻塞读等待数据就绪 |
tx_count | int32_t | 当前发送(占用)计数,用于判断发送缓冲是否写满 |
rx_count | int32_t | 当前接收(可用)计数,用于判断是否有数据可读 |
tx_size | int32_t | 发送缓冲总大小,tx_count == tx_size 表示发送缓冲满 |
rx_size | int32_t | 接收缓冲总大小,rx_count == 0 表示无数据可读 |
tx_timeout | os_tick_t | 发送超时(tick),阻塞写等待 tx_sem 的超时上限,默认 OS_TICK_MAX |
rx_timeout | os_tick_t | 接收超时(tick),阻塞读等待 rx_sem 的超时上限,默认 OS_TICK_MAX |
user_data | void * | 用户私有数据指针,驱动可自由使用(常指向自定义的设备扩展结构) |
poll_list | os_list_node_t | IO 多路复用链表,仅在开启 OS_USING_IO_MULTIPLEXING 时存在 |
notify_list | os_list_node_t | 设备通知回调链表头,仅在开启 OS_USING_DEVICE_NOTIFY 时存在 |
plug | os_plug_t | 设备热插拔插件,仅在开启 OS_DEVICE_SUPPORT_PLUG 时存在 |
2.3.2 链表头字段的"来源"说明
struct os_device 里有 4 个链表头,它们的归属和数量各不相同,务必分清:
| 链表头 | 属于谁 | 数量 | 用途 |
|---|---|---|---|
os_device_list(全局变量) | drivers/device.c 的 static 全局变量 | 系统 1 个 | 管理所有已注册设备,通过 dev->list 挂入 |
dev->list(结构体字段) | 每个设备实例各 1 个 | 每设备 1 个 | 把该设备挂到全局设备链表 |
dev->cb_heads[2](结构体字段) | 每个设备实例各 1 组(RX/TX 两个) | 每设备 2 个 | 挂该设备的收发回调(struct os_device_cb_info) |
dev->notify_list(结构体字段) | 每个设备实例各 1 个 | 每设备 1 个 | 挂该设备的通知回调,仅在 OS_USING_DEVICE_NOTIFY 时存在 |
cb_heads 回调链表头的类型索引:
来源:drivers/device.h
enum os_device_cb_type
{
OS_DEVICE_CB_TYPE_RX = 0,
OS_DEVICE_CB_TYPE_TX,
OS_DEVICE_CB_TYPE_NUM,
};OS_DEVICE_CB_TYPE_RX = 0:接收回调链表OS_DEVICE_CB_TYPE_TX = 1:发送回调链表OS_DEVICE_CB_TYPE_NUM = 2:数量,用于定义cb_heads[OS_DEVICE_CB_TYPE_NUM]即cb_heads[2]
回调节点结构体:
来源:drivers/device.h
struct os_device_cb_info
{
int type;
int size;
void *data;
os_err_t (*cb)(os_device_t *dev, struct os_device_cb_info *info);
os_list_node_t list;
};| 字段 | 说明 |
|---|---|
type | 回调类型(OS_DEVICE_CB_TYPE_RX/TX),决定挂到 cb_heads[0] 还是 cb_heads[1] |
size | 由驱动在触发回调前填充的实际数据大小 |
data | 回调私有数据 |
cb | 回调函数指针 |
list | 链表节点,挂到 dev->cb_heads[type] |
2.3.3 设备对象与多个链表的整体关系图
注意:os_device_list 是所有设备共用的一个全局链表(挂所有 dev->list);而 cb_heads、notify_list 是每个设备内部各自的链表。用图区分如下:
【全局:所有设备共用 1 个链表】
┌──────────────────────────────────────────────────────────┐
│ os_device_list (device.c 静态全局变量) │
│ 头节点(空) │
└──┬─────────────────────┬─────────────────────┬───────────┘
│ list │ list │ list
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ device A│ │ device B│ │ device C│
│ type=0 │ │ type=8 │ │ type=20│
└─────────┘ └─────────┘ └─────────┘
│ │ │
│ 每设备内部独立链表 │ 每设备内部独立链表 │ 每设备内部独立链表
▼ ▼ ▼
cb_heads[RX]/[TX] cb_heads[RX]/[TX] cb_heads[RX]/[TX]
notify_list notify_list notify_list核心关系表(每一行对应一个链表,共 3 类):
| 链表 | 属于谁 | 挂哪个节点 | 何时挂入 |
|---|---|---|---|
os_device_list | device.c 全局变量(系统 1 个) | dev->list | os_device_register 时 os_list_add |
cb_heads[RX]/[TX] | 设备结构体字段(每设备 2 个) | struct os_device_cb_info.list | os_device_control(OS_DEVICE_CTRL_SET_CB) 时 os_list_add_tail |
notify_list | 设备结构体字段(每设备 1 个,需开 NOTIFY) | struct os_device_notify_cb_info.list | os_device_notify_register 时 os_list_add |
补充说明:这些内部字段(list、cb_heads、notify_list、信号量等)都由框架管理,应用层不应直接访问或修改,而应通过 os_device_* 系列 API 操作设备。
2.4 IOCTL 命令体系
2.4.1 命令编码规则
OneOS 使用层次化的 IOCTL 命令编码:高 8 位是设备类型,低 8 位是具体命令。这样同一个命令号(如 0x01)在不同设备类型下就有了不同含义,命令与设备类型天然绑定。
来源:drivers/device.h
/* device io control commands */
#define IOC_CHAR(cmd) ((OS_DEVICE_TYPE_CHAR << 8) | (cmd))
#define IOC_BLOCK(cmd) ((OS_DEVICE_TYPE_BLOCK << 8) | (cmd))
#define IOC_NETIF(cmd) ((OS_DEVICE_TYPE_NETIF << 8) | (cmd))
#define IOC_MTD(cmd) ((OS_DEVICE_TYPE_MTD << 8) | (cmd))
#define IOC_CAN(cmd) ((OS_DEVICE_TYPE_CAN << 8) | (cmd))
#define IOC_RTC(cmd) ((OS_DEVICE_TYPE_RTC << 8) | (cmd))
#define IOC_SOUND(cmd) ((OS_DEVICE_TYPE_SOUND << 8) | (cmd))
#define IOC_GRAPHIC(cmd) ((OS_DEVICE_TYPE_GRAPHIC << 8) | (cmd))
#define IOC_I2CBUS(cmd) ((OS_DEVICE_TYPE_I2CBUS << 8) | (cmd))
#define IOC_USBDEVICE(cmd) ((OS_DEVICE_TYPE_USBDEVICE << 8) | (cmd))
#define IOC_USBHOST(cmd) ((OS_DEVICE_TYPE_USBHOST << 8) | (cmd))
#define IOC_SPIBUS(cmd) ((OS_DEVICE_TYPE_SPIBUS << 8) | (cmd))
#define IOC_SPIDEVICE(cmd) ((OS_DEVICE_TYPE_SPIDEVICE << 8) | (cmd))
#define IOC_SDIO(cmd) ((OS_DEVICE_TYPE_SDIO << 8) | (cmd))
#define IOC_PM(cmd) ((OS_DEVICE_TYPE_PM << 8) | (cmd))
#define IOC_PIPE(cmd) ((OS_DEVICE_TYPE_PIPE << 8) | (cmd))
#define IOC_PORTAL(cmd) ((OS_DEVICE_TYPE_PORTAL << 8) | (cmd))
#define IOC_CLOCKSOURCE(cmd) ((OS_DEVICE_TYPE_CLOCKSOURCE << 8) | (cmd))
#define IOC_CLOCKEVENT(cmd) ((OS_DEVICE_TYPE_CLOCKEVENT << 8) | (cmd))
#define IOC_MISCELLANEOUS(cmd) ((OS_DEVICE_TYPE_MISCELLANEOUS << 8) | (cmd))
#define IOC_SENSOR(cmd) ((OS_DEVICE_TYPE_SENSOR << 8) | (cmd))
#define IOC_TOUCH(cmd) ((OS_DEVICE_TYPE_TOUCH << 8) | (cmd))
#define IOC_INFRARED(cmd) ((OS_DEVICE_TYPE_INFRARED << 8) | (cmd))
#define IOC_WLAN(cmd) ((OS_DEVICE_TYPE_WLAN << 8) | (cmd))
#define IOC_PWM(cmd) ((OS_DEVICE_TYPE_PWM << 8) | (cmd))
#define IOC_ENCODER(cmd) ((OS_DEVICE_TYPE_ENCODER << 8) | (cmd))
#define IOC_GENERIC(cmd) ((OS_DEVICE_TYPE_GENERIC << 8) | (cmd))数值演算:IOC_xxx(cmd) 的本质是 (设备类型 << 8) | cmd。以 IOC_GENERIC(8)(即 OS_DEVICE_CTRL_SET_RX_TIMEOUT)为例:
| 计算项 | 代入计算 | 结果 |
|---|---|---|
| 设备类型值 | OS_DEVICE_TYPE_GENERIC | 26 |
| 左移 8 位 | 26 << 8 | 0x1A00(二进制 0001 1010 0000 0000) |
| 或上命令号 | 0x1A00 | 0x08 | 0x1A08 |
| 最终命令 | OS_DEVICE_CTRL_SET_RX_TIMEOUT | 0x1A08 |
再看一个不同类型的对比——IOC_SENSOR(1) vs IOC_GENERIC(1):
| 计算项 | IOC_SENSOR(1) | IOC_GENERIC(1) |
|---|---|---|
| 设备类型值 | OS_DEVICE_TYPE_SENSOR = 20 | OS_DEVICE_TYPE_GENERIC = 26 |
| 左移 8 位 | 20 << 8 = 0x1400 | 26 << 8 = 0x1A00 |
| 或上 cmd | 0x1400 | 0x01 | 0x1A00 | 0x01 |
| 最终命令 | 0x1401 | 0x1A01 |
关键点:虽然 cmd 都是 1,但因为高 8 位设备类型不同,最终命令码完全不同,从而在 os_device_control 的 switch(cmd) 里能区分"这是传感器命令 1 还是通用命令 1"。
2.4.2 通用命令(所有设备类型均支持)
来源:drivers/device.h
/* general commands */
#define OS_DEVICE_CTRL_FLUSH IOC_GENERIC(1)
#define OS_DEVICE_CTRL_SEEK IOC_GENERIC(2)
#define OS_DEVICE_CTRL_SET_CB IOC_GENERIC(3)
#define OS_DEVICE_CTRL_RM_CB IOC_GENERIC(4)
#define OS_DEVICE_CTRL_RESUME IOC_GENERIC(5)
#define OS_DEVICE_CTRL_SUSPEND IOC_GENERIC(6)
#define OS_DEVICE_CTRL_CONFIG IOC_GENERIC(7)
#define OS_DEVICE_CTRL_SET_RX_TIMEOUT IOC_GENERIC(8)
#define OS_DEVICE_CTRL_SET_TX_TIMEOUT IOC_GENERIC(9)| 通用命令 | 命令码 | 说明 |
|---|---|---|
OS_DEVICE_CTRL_FLUSH | 0x1A01 | 刷新设备 |
OS_DEVICE_CTRL_SEEK | 0x1A02 | 移动读写位置 |
OS_DEVICE_CTRL_SET_CB | 0x1A03 | 设置回调函数 |
OS_DEVICE_CTRL_RM_CB | 0x1A04 | 移除回调函数 |
OS_DEVICE_CTRL_RESUME | 0x1A05 | 恢复设备 |
OS_DEVICE_CTRL_SUSPEND | 0x1A06 | 挂起设备 |
OS_DEVICE_CTRL_CONFIG | 0x1A07 | 配置设备 |
OS_DEVICE_CTRL_SET_RX_TIMEOUT | 0x1A08 | 设置接收超时 |
OS_DEVICE_CTRL_SET_TX_TIMEOUT | 0x1A09 | 设置发送超时 |
其中 OS_DEVICE_CTRL_SET_CB、OS_DEVICE_CTRL_RM_CB、OS_DEVICE_CTRL_SET_RX_TIMEOUT、OS_DEVICE_CTRL_SET_TX_TIMEOUT 这 4 个命令由框架层在 os_device_control 里直接处理(见 4.8 节源码),其余通用命令传给驱动的 ops->control。
2.4.3 特殊命令示例
来源:drivers/device.h
/* special commands */
#define OS_DEVICE_CTRL_CHAR_STREAM 0x10 /* Stream mode on char device. */
#define OS_DEVICE_CTRL_NETIF_GETMAC 0x10 /* Get mac address. */
#define OS_DEVICE_CTRL_MTD_FORMAT 0x10 /* Format a MTD device. */关键点:这些特殊命令使用原始值 0x10,没有经过 IOC_xxx(cmd) 编码。它们由各类型设备驱动内部的 ops->control 直接处理,与框架层无关。注意这里三个命令宏的值都是 0x10,但它们分属不同设备类型(由驱动各自解释),不会冲突。
2.5 设备通知机制
当开启 OS_USING_DEVICE_NOTIFY 配置后,OneOS 提供设备事件通知功能。设备状态变化(注册、注销、打开、关闭、读写、控制)都会广播通知事件,监听者可以注册回调来响应。
通知回调节点结构体:
来源:drivers/device.h
struct os_device_notify_cb_info
{
device_notify_callback callback;
device_notify_filter filter;
os_list_node_t list;
};| 字段 | 说明 |
|---|---|
callback | 通知回调函数,事件发生时被调用 |
filter | 通知过滤函数,返回非 OS_SUCCESS 时终止本次通知广播 |
list | 链表节点,挂到全局通知链表或设备 notify_list |
回调/过滤函数类型:
来源:drivers/device.h
typedef os_err_t (*device_notify_callback)(os_device_t *dev, os_ubase_t event, os_ubase_t args);
typedef os_err_t (*device_notify_filter)(os_device_t *dev, os_ubase_t event, os_ubase_t args);通用通知事件(同样使用高 8 位设备类型编码,通用类型用 ION_GENERIC):
来源:drivers/device.h
#define ION_GENERIC_NONE ION_GENERIC(0)
#define ION_GENERIC_REGISTER ION_GENERIC(1)
#define ION_GENERIC_UNREGISTER ION_GENERIC(2)
#define ION_GENERIC_OPEN ION_GENERIC(3)
#define ION_GENERIC_CLOSE ION_GENERIC(4)
#define ION_GENERIC_READ_BLOCK ION_GENERIC(5)
#define ION_GENERIC_READ_NONBLOCK ION_GENERIC(6)
#define ION_GENERIC_WRITE_BLOCK ION_GENERIC(7)
#define ION_GENERIC_WRITE_NONBLOCK ION_GENERIC(8)
#define ION_GENERIC_CONTROL ION_GENERIC(9)
#define ION_GENERIC_NUM ION_GENERIC(10)数值演算:ION_GENERIC(3) = (OS_DEVICE_TYPE_GENERIC << 8) | 3 = (26 << 8) | 3 = 0x1A03。
| 通知事件 | 值 | 触发时机 |
|---|---|---|
ION_GENERIC_REGISTER | 0x1A01 | 设备注册成功 |
ION_GENERIC_UNREGISTER | 0x1A02 | 设备注销 |
ION_GENERIC_OPEN | 0x1A03 | 设备被打开 |
ION_GENERIC_CLOSE | 0x1A04 | 设备被关闭 |
ION_GENERIC_READ_BLOCK | 0x1A05 | 阻塞读取完成 |
ION_GENERIC_READ_NONBLOCK | 0x1A06 | 非阻塞读取完成 |
ION_GENERIC_WRITE_BLOCK | 0x1A07 | 阻塞写入完成 |
ION_GENERIC_WRITE_NONBLOCK | 0x1A08 | 非阻塞写入完成 |
ION_GENERIC_CONTROL | 0x1A09 | 控制操作 |
关键点:
ION_GENERIC_NONE = 0x1A00是"无事件"占位。- 这些事件在 4.x 各 API 源码的
os_device_notify(dev, ION_GENERIC_XXX, args)调用处自动触发,应用层只需注册回调即可监听。
三、设备框架使用场景
3.1 一句话核心
设备驱动框架适用于需要以统一接口访问不同硬件外设、并希望由框架统一管理设备生命周期(注册/打开/关闭/读写)的场景。它让你"拿到一个设备指针就能读能写",而不必关心底层硬件差异。
3.2 设备框架 vs 其他访问方式对比
在 OneOS 中,访问硬件外设有两种途径:通过设备框架(os_device_*)或直接调用驱动提供的原始接口(如 PIN 驱动的 os_pin_* 原始操作)。对比:
| 维度 | 设备框架(os_device_*) | 驱动原始接口(os_pin_* 等) |
|---|---|---|
| 接口统一性 | 所有设备统一 5 个操作函数 | 各驱动自定义,接口不统一 |
| 生命周期管理 | 框架管理引用计数、init/deinit 自动调用 | 需自己管理初始化 |
| 读写同步 | 内置信号量,支持阻塞/非阻塞 | 通常不提供阻塞同步 |
| 通知机制 | 支持设备事件通知 | 通常不支持 |
| 使用灵活性 | 需先 find + open | 拿到句柄直接用 |
| 适用场景 | 需要统一访问多种设备、需要生命周期管理 | 单一设备、追求极致性能 |
3.3 典型应用场景
场景一:统一访问不同厂商的同类外设(如多个 UART)
产品中有 3 个 UART 口,分别接调试串口、GPS 模块、蓝牙模块,但它们的驱动可能是不同的底层实现。通过设备框架,应用代码统一用 os_device_find("uart0"/"uart1"/"uart2") 拿指针,再 os_device_read_block/os_device_write_block 读写,切换串口只改名称字符串,不改逻辑。这就是"统一抽象"带来的可移植性。
应用任务
│ os_device_find("uart1") → dev
│ os_device_open(dev)
├─ os_device_write_block(dev, 0, cmd, len) ← 发指令给 GPS
├─ os_device_read_block(dev, 0, buf, len) ← 读 GPS 回包
│ os_device_close(dev)为什么用设备框架:应用层无需知道 uart1 底层的寄存器操作,只要遵守 os_device_* 约定即可。若日后更换 GPS 模块驱动,应用代码零改动。
场景二:中断驱动设备的数据就绪通知
一个串口在接收中断里收到数据后,需要唤醒阻塞在 os_device_read_block 上的应用任务。驱动在中断服务程序中调用 os_device_recv_notify(dev),它先 os_semaphore_post(dev->rx_sem) 唤醒阻塞的读任务,再遍历 cb_heads[RX] 调用注册的接收回调。
串口接收中断 (ISR)
│ 收到数据,dev->rx_count 增加
▼
os_device_recv_notify(dev)
├─ os_semaphore_post(dev->rx_sem) → 唤醒 os_device_read_block 里等待的读任务
└─ 遍历 cb_heads[OS_DEVICE_CB_TYPE_RX] 调用各回调为什么用设备框架:框架帮你把"中断→唤醒任务→通知回调"这条链路封装好,驱动只需在中断里调一个 os_device_recv_notify,应用任务就自动被唤醒,无需自己实现信号量同步。
场景三:设备生命周期统一管理(打开计数/自动初始化)
多个模块可能同时打开同一个设备(如共享的存储 Flash)。设备框架的引用计数保证:只有第一个 os_device_open 才触发 init,只有最后一个 os_device_close(ref_count 归零)才触发 deinit。中间多次打开/关闭只是增减计数,不会重复初始化或提前释放。
模块A: open → ref_count=0→1 → 调用 ops->init
模块B: open → ref_count=1→2 → 不再调用 init
模块A: close→ ref_count=2→1 → 不调用 deinit
模块B: close→ ref_count=1→0 → 调用 ops->deinit为什么用设备框架:手动管理"何时初始化、何时释放"极易出错(重复初始化、use-after-free)。框架的引用计数让设备生命周期管理变得安全。
3.4 选型边界表(何时不该用它)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 只读写单个固定外设、追求最低开销 | 驱动原始接口(如 os_pin_*) | 省去 find/open 查找开销,代码更短 |
| 需要极高频的中断内裸操作 | 直接在驱动中断回调里操作寄存器 | 设备框架引入信号量/锁开销,不适合高频裸操作 |
| 仅传递标志位/计数 | 信号量、事件 | 设备框架偏重数据读写,用内核 IPC 更轻量 |
| 需要排他访问同一设备 | 互斥锁(结合设备框架) | sem 只保证设备内部互斥,跨设备共享资源需互斥锁 |
3.5 结合源码的使用注意点
- 必须先 open 再 read/write:
os_device_read_block/write_block里有OS_ASSERT(dev->ref_count != 0)(见 4.6 节源码),未打开就读写会断言失败。 - 设备名唯一:
os_device_register会先os_device_find(name)检查重名,重名返回OS_INVAL(见 4.1 节源码)。 - 默认超时是永久等待:注册时
tx_timeout/rx_timeout都初始化为OS_TICK_MAX,阻塞读写会一直等下去,如需超时返回要先用OS_DEVICE_CTRL_SET_RX_TIMEOUT/SET_TX_TIMEOUT设置。 - read/write 返回 0 的语义:阻塞读在"数据未就绪但超时"或"设备被移除"时返回 0(非负),调用方要能区分"没读到数据"和"出错"。
- 非阻塞返回 0 即暂无数据/暂不可写:
os_device_read_nonblock在rx_size != 0 && rx_count == 0(无数据)时直接返回 0,不阻塞(见 4.6 节源码)。
四、核心 API 详解
说明:以下所有 API 的完整源码均取自
drivers/device.c,与项目源码逐字一致。
4.1 os_device_register —— 注册设备
将设备注册到全局设备列表,是设备驱动的"出生"操作。
os_err_t os_device_register(os_device_t *dev, const char *name);| 参数 | 说明 |
|---|---|
dev | 待注册的设备控制块指针 |
name | 设备名称字符串(需全局唯一) |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 注册成功 |
OS_INVAL | 参数无效(dev 为空,或 name 为空,或设备名已存在) |
完整源码(drivers/device.c,与项目一致):
os_err_t os_device_register(os_device_t *dev, const char *name)
{
int i;
if (dev == OS_NULL)
{
return OS_INVAL;
}
if ((OS_NULL == name) || (OS_NULL != os_device_find(name)))
{
return OS_INVAL;
}
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);
memset(device_name(dev), 0, sizeof(device_name(dev)));
strncpy(device_name(dev), name, OS_NAME_MAX);
dev->ref_count = 0;
dev->tx_timeout = OS_TICK_MAX;
dev->rx_timeout = OS_TICK_MAX;
dev->sem = os_semaphore_create(&dev->sem_dummy, name, 1, 1);
dev->tx_sem = os_semaphore_create(&dev->tx_sem_dummy, name, 1, 1);
dev->rx_sem = os_semaphore_create(&dev->rx_sem_dummy, name, 0, 1);
for (i = 0; i < OS_DEVICE_CB_TYPE_NUM; i++)
os_list_init(&dev->cb_heads[i]);
os_list_add(&os_device_list, &dev->list);
#ifdef OS_DEVICE_SUPPORT_PLUG
dev->plug.upper = OS_NULL;
dev->plug.halt = device_halt;
dev->plug.release = device_release;
os_plug_in(&dev->plug, "device", name);
#endif
#ifdef OS_USING_VFS_DEVFS
#ifdef OS_USING_IO_MULTIPLEXING
os_list_init(&dev->poll_list);
#endif
devfs_register_device(name, (struct dev_file_ops *)&dfops, dev);
#endif
#ifdef OS_USING_DEVICE_NOTIFY
os_list_init(&dev->notify_list);
#endif
os_device_notify(dev, ION_GENERIC_REGISTER, 0);
os_semaphore_post(dev_sem);
return OS_SUCCESS;
}分步详解:
第 1 步:参数校验 —— 设备指针非空(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
return OS_INVAL;
}详细解释:dev 为 OS_NULL 说明传入非法指针,直接返回 OS_INVAL。这是防空指针的基本检查。
第 2 步:名称校验 —— 名称非空且不重名(条件 name == OS_NULL 或已存在同名设备)
if ((OS_NULL == name) || (OS_NULL != os_device_find(name)))
{
return OS_INVAL;
}详细解释:
- 第一个条件
OS_NULL == name:名称为空,无法注册。 - 第二个条件
OS_NULL != os_device_find(name):调用os_device_find查找,若已存在同名设备,说明重名,返回OS_INVAL。这保证了设备名的全局唯一性。
第 3 步:拿全局锁 —— 保护设备列表操作
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);详细解释:获取全局 dev_sem 互斥信号量(见 1.2 节)。该信号量由 device_core_init 在内核启动早期(OS_INIT_LEVEL_PRE_KERNEL_1)通过 os_semaphore_create 静态创建,因此此时已可安全使用。拿锁保证注册过程的原子性。
第 4 步:填充名称与初始化关键字段
memset(device_name(dev), 0, sizeof(device_name(dev)));
strncpy(device_name(dev), name, OS_NAME_MAX);
dev->ref_count = 0;
dev->tx_timeout = OS_TICK_MAX;
dev->rx_timeout = OS_TICK_MAX;详细解释:
device_name(dev)是宏(dev)->name(见 device.h)。先memset清空整个名称缓冲区,再strncpy拷贝名称(最多OS_NAME_MAX字节)。ref_count = 0:新注册设备引用计数为 0(尚未被打开)。tx_timeout = rx_timeout = OS_TICK_MAX:收发超时默认设为最大 tick 值,即默认永久等待。
第 5 步:静态创建三个信号量
dev->sem = os_semaphore_create(&dev->sem_dummy, name, 1, 1);
dev->tx_sem = os_semaphore_create(&dev->tx_sem_dummy, name, 1, 1);
dev->rx_sem = os_semaphore_create(&dev->rx_sem_dummy, name, 0, 1);详细解释:
- 用各自的占位控制块(
*_dummy)静态创建信号量。 sem初值 1、最大值 1:二值互斥信号量。tx_sem初值 1、最大值 1:初始可写。rx_sem初值 0、最大值 1:初始不可读(无数据)。
第 6 步:初始化回调链表
for (i = 0; i < OS_DEVICE_CB_TYPE_NUM; i++)
os_list_init(&dev->cb_heads[i]);详细解释:遍历 RX/TX 两类,把 cb_heads[0]、cb_heads[1] 分别初始化为空链表。
第 7 步:挂入全局设备链表
os_list_add(&os_device_list, &dev->list);详细解释:把设备的 list 节点挂到全局 os_device_list 链表头。此后 os_device_find/os_device_for_each 就能找到它。
第 8 步(条件编译):热插拔支持(条件 OS_DEVICE_SUPPORT_PLUG)
#ifdef OS_DEVICE_SUPPORT_PLUG
dev->plug.upper = OS_NULL;
dev->plug.halt = device_halt;
dev->plug.release = device_release;
os_plug_in(&dev->plug, "device", name);
#endif详细解释:若开启热插拔,注册 device_halt(设备停止)和 device_release(设备释放)两个回调,并调用 os_plug_in 注册插件。
第 9 步(条件编译):VFS/devfs 与 IO 多路复用(条件 OS_USING_VFS_DEVFS)
#ifdef OS_USING_VFS_DEVFS
#ifdef OS_USING_IO_MULTIPLEXING
os_list_init(&dev->poll_list);
#endif
devfs_register_device(name, (struct dev_file_ops *)&dfops, dev);
#endif详细解释:开启 devfs 时把设备注册到 VFS 文件系统(可通过 /dev/name 访问);若开启 IO 多路复用,初始化 poll_list。
第 10 步(条件编译):通知链表初始化(条件 OS_USING_DEVICE_NOTIFY)
#ifdef OS_USING_DEVICE_NOTIFY
os_list_init(&dev->notify_list);
#endif详细解释:开启通知时初始化 notify_list 空链表。
第 11 步:广播注册通知并释放锁
os_device_notify(dev, ION_GENERIC_REGISTER, 0);
os_semaphore_post(dev_sem);
return OS_SUCCESS;详细解释:广播"设备已注册"事件,然后释放 dev_sem,返回成功。
os_device_register() 条件走向总结:
os_device_register() 条件走向总结:
├── dev == OS_NULL(参数校验失败)→ 返回 OS_INVAL
├── name == OS_NULL 或 已存在同名设备 → 返回 OS_INVAL
├── 拿 dev_sem 全局锁
├── 填充名称/初始化字段/建信号量/初始化链表/挂入设备列表
├── #ifdef 分支(按配置走)
│ ├── OS_DEVICE_SUPPORT_PLUG → 注册热插拔回调
│ ├── OS_USING_VFS_DEVFS → 注册到 VFS(含 poll_list 初始化)
│ └── OS_USING_DEVICE_NOTIFY → 初始化 notify_list
├── 广播 ION_GENERIC_REGISTER
└── 释放 dev_sem → 返回 OS_SUCCESS使用示例(注册一个简单的字符设备):
#include <device.h>
#include <string.h>
static os_err_t demo_dev_init(os_device_t *dev)
{
os_kprintf("demo dev init\r\n");
return OS_EOK;
}
static os_err_t demo_dev_deinit(os_device_t *dev)
{
os_kprintf("demo dev deinit\r\n");
return OS_EOK;
}
static os_ssize_t demo_dev_read(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size)
{
/* 实际驱动中在此读取硬件数据 */
return size;
}
static os_ssize_t demo_dev_write(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size)
{
/* 实际驱动中在此写入硬件 */
return size;
}
static os_err_t demo_dev_control(os_device_t *dev, int cmd, void *args)
{
return OS_EOK;
}
static const struct os_device_ops demo_dev_ops = {
.init = demo_dev_init,
.deinit = demo_dev_deinit,
.read = demo_dev_read,
.write = demo_dev_write,
.control = demo_dev_control,
};
static struct os_device demo_dev = {
.type = OS_DEVICE_TYPE_CHAR,
.ops = &demo_dev_ops,
};
void register_demo_device(void)
{
os_err_t ret;
ret = os_device_register(&demo_dev, "demo");
if (ret != OS_EOK)
{
os_kprintf("register demo device failed, ret=%d\r\n", ret);
return;
}
os_kprintf("register demo device ok\r\n");
}说明:注册后,应用层即可通过
os_device_find("demo")找到该设备并打开使用(完整流程见"六、使用示例")。
4.2 os_device_find —— 按名称查找设备
os_device_t *os_device_find(const char *name);| 参数 | 说明 |
|---|---|
name | 要查找的设备名称 |
| 返回值 | 说明 |
|---|---|
| 设备指针 | 找到对应设备 |
OS_NULL | 未找到 |
完整源码(drivers/device.c,与项目一致):
os_device_t *os_device_find(const char *name)
{
os_device_t *dev;
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);
os_list_for_each_entry(dev, &os_device_list, os_device_t, list)
{
if (0 == strncmp(device_name(dev), name, OS_NAME_MAX))
{
os_semaphore_post(dev_sem);
return dev;
}
}
os_semaphore_post(dev_sem);
return OS_NULL;
}分步详解:
第 1 步:拿全局锁
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);详细解释:获取 dev_sem,防止遍历设备链表时其他任务并发注册/注销设备。
第 2 步:遍历设备链表匹配名称
os_list_for_each_entry(dev, &os_device_list, os_device_t, list)
{
if (0 == strncmp(device_name(dev), name, OS_NAME_MAX))
{
os_semaphore_post(dev_sem);
return dev;
}
}详细解释:
os_list_for_each_entry遍历全局设备链表的每个设备。strncmp比较设备名与目标名,最多比较OS_NAME_MAX字节;相等(返回 0)即找到。
第 3 步:释放锁并返回结果
os_semaphore_post(dev_sem);
return OS_NULL;详细解释:若遍历完都没匹配到,释放锁并返回 OS_NULL。
os_device_find() 条件走向总结:
os_device_find() 条件走向总结:
├── 拿 dev_sem
├── 遍历设备链表,若 strncmp(名称) == 0(匹配)
│ └── 释放 dev_sem → 返回该设备指针
└── 遍历结束未匹配
└── 释放 dev_sem → 返回 OS_NULL使用示例:
os_device_t *dev = os_device_find("uart1");
if (dev == OS_NULL)
{
os_kprintf("uart1 not found\r\n");
return;
}
os_kprintf("uart1 found\r\n");4.3 os_device_open —— 打开设备
os_err_t os_device_open(os_device_t *dev);| 参数 | 说明 |
|---|---|
dev | 设备控制块指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 打开成功 |
OS_INVAL | 设备指针为空 |
| 其他 | ops->init 的返回结果(首次打开且初始化失败时) |
完整源码(drivers/device.c,与项目一致):
os_err_t os_device_open(os_device_t *dev)
{
os_err_t result = OS_SUCCESS;
if (dev == OS_NULL)
{
return OS_INVAL;
}
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
if (dev->ref_count++ == 0)
{
if (dev->ops != OS_NULL && dev->ops->init != OS_NULL)
{
result = dev->ops->init(dev);
if (result != OS_SUCCESS)
{
os_kprintf("Initialize device:%s failed. The error code is %d", device_name(dev), result);
dev->ref_count--;
}
}
}
os_semaphore_post(dev->sem);
os_device_notify(dev, ION_GENERIC_OPEN, 0);
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_t *plug = os_plug_get("device", dev->name);
OS_ASSERT(&dev->plug == plug);
#endif
return result;
}分步详解:
第 1 步:参数校验(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
return OS_INVAL;
}详细解释:防空指针。
第 2 步:拿设备互斥锁 dev->sem
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);详细解释:获取设备自身的互斥信号量 dev->sem,保证对 ref_count 和 init 的操作不被其他任务打断。
第 3 步:首次打开时初始化(条件 dev->ref_count++ == 0)
if (dev->ref_count++ == 0)
{
if (dev->ops != OS_NULL && dev->ops->init != OS_NULL)
{
result = dev->ops->init(dev);
if (result != OS_SUCCESS)
{
os_kprintf("Initialize device:%s failed. The error code is %d", device_name(dev), result);
dev->ref_count--;
}
}
}详细解释:
- 这里
dev->ref_count++是后置自增:判断的是自增前的值是否为 0。 - 若自增前为 0(即首次打开):进入
if分支,检查ops和ops->init非空后调用init。- 若
init返回非OS_SUCCESS:打印错误信息,并dev->ref_count--回滚刚才的自增(相当于这次 open 失败,引用计数不增加)。
- 若
- 若自增前不为 0(已打开过):跳过
init,只增加引用计数。
关键点 —— 为什么首次打开才调用 init?
init通常做一次性硬件初始化(配置寄存器、申请资源),多次打开时重复初始化会浪费资源甚至出错。- 因此只在
ref_count从 0 变 1 的首次打开时调用一次。
第 4 步:释放设备锁
os_semaphore_post(dev->sem);详细解释:释放 dev->sem。
第 5 步:广播打开通知
os_device_notify(dev, ION_GENERIC_OPEN, 0);详细解释:广播"设备已打开"事件。
第 6 步(条件编译):热插拔校验(条件 OS_DEVICE_SUPPORT_PLUG)
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_t *plug = os_plug_get("device", dev->name);
OS_ASSERT(&dev->plug == plug);
#endif详细解释:热插拔场景下校验设备的 plug 是否与框架登记的插件一致。
第 7 步:返回结果
return result;详细解释:返回 init 的结果(成功为 OS_SUCCESS)。
os_device_open() 条件走向总结:
os_device_open() 条件走向总结:
├── dev == OS_NULL → 返回 OS_INVAL
├── 拿 dev->sem
├── dev->ref_count++(自增后)
│ ├── 自增前 == 0(首次打开)
│ │ ├── ops 非空 且 ops->init 非空
│ │ │ ├── init 返回 OS_SUCCESS → result = OS_SUCCESS
│ │ │ └── init 失败 → 打印错误 + ref_count--(回滚)
│ │ └── ops 为空或 init 为空 → 不调用,result 保持 OS_SUCCESS
│ └── 自增前 != 0(非首次)→ 仅计数增加,不调用 init
├── 释放 dev->sem
├── 广播 ION_GENERIC_OPEN
└── 返回 result4.4 os_device_open_s —— 按名称查找并打开设备
os_device_t *os_device_open_s(const char *name);| 参数 | 说明 |
|---|---|
name | 设备名称 |
| 返回值 | 说明 |
|---|---|
| 设备指针 | 找到并成功打开 |
OS_NULL | 未找到,或打开初始化失败 |
完整源码(drivers/device.c,与项目一致):
os_device_t *os_device_open_s(const char *name)
{
os_device_t *device;
os_err_t result = OS_SUCCESS;
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);
os_list_for_each_entry(device, &os_device_list, os_device_t, list)
{
if (0 == strncmp(device_name(device), name, OS_NAME_MAX))
{
os_semaphore_wait(device->sem, OS_WAIT_FOREVER);
if (device->ref_count++ > 0)
{
os_semaphore_post(dev_sem);
}
else
{
os_semaphore_post(dev_sem);
if (device->ops != OS_NULL && device->ops->init != OS_NULL)
{
result = device->ops->init(device);
if (result != OS_SUCCESS)
{
os_kprintf("Initialize device:%s failed. The error code is %d", device_name(device), result);
device->ref_count--;
}
}
}
os_semaphore_post(device->sem);
os_device_notify(device, ION_GENERIC_OPEN, 0);
if (result == OS_SUCCESS)
{
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_t *plug = os_plug_get("device", device->name);
OS_ASSERT(&device->plug == plug);
#endif
return device;
}
else
{
return OS_NULL;
}
}
}
os_semaphore_post(dev_sem);
return OS_NULL;
}分步详解:
第 1 步:拿全局锁
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);详细解释:拿 dev_sem 保护遍历。
第 2 步:遍历查找匹配设备
os_list_for_each_entry(device, &os_device_list, os_device_t, list)
{
if (0 == strncmp(device_name(device), name, OS_NAME_MAX))
{详细解释:遍历设备链表,strncmp 匹配名称。
第 3 步:拿设备锁
os_semaphore_wait(device->sem, OS_WAIT_FOREVER);详细解释:找到设备后拿它的互斥锁。
第 4 步:非首次打开分支(条件 device->ref_count++ > 0)
if (device->ref_count++ > 0)
{
os_semaphore_post(dev_sem);
}
else
{
os_semaphore_post(dev_sem);详细解释:
device->ref_count++ > 0:自增前引用计数大于 0,说明已打开过,直接释放全局锁dev_sem。- 否则(自增前为 0,首次打开):也先释放全局锁,进入下面的初始化分支。
注意:这里
ref_count++ > 0与os_device_open里ref_count++ == 0判断逻辑互补——都是判断"自增前是否为 0"。
第 5 步:首次打开初始化分支(条件 首次打开 且 ops->init 非空)
if (device->ops != OS_NULL && device->ops->init != OS_NULL)
{
result = device->ops->init(device);
if (result != OS_SUCCESS)
{
os_kprintf("Initialize device:%s failed. The error code is %d", device_name(device), result);
device->ref_count--;
}
}
}详细解释:与 os_device_open 相同的 init 逻辑,失败时打印并回滚引用计数。
第 6 步:释放设备锁并广播打开通知
os_semaphore_post(device->sem);
os_device_notify(device, ION_GENERIC_OPEN, 0);详细解释:释放设备锁,广播打开事件。
第 7 步:根据 init 结果返回(条件 result == OS_SUCCESS)
if (result == OS_SUCCESS)
{
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_t *plug = os_plug_get("device", device->name);
OS_ASSERT(&device->plug == plug);
#endif
return device;
}
else
{
return OS_NULL;
}详细解释:初始化成功返回设备指针;失败返回 OS_NULL。
os_device_open_s() 条件走向总结:
os_device_open_s() 条件走向总结:
├── 拿 dev_sem
├── 遍历设备链表
│ ├── 未匹配到名称 → 释放 dev_sem → 返回 OS_NULL
│ └── 匹配到名称
│ ├── 拿 device->sem
│ ├── device->ref_count++ > 0(自增前>0,已打开过)
│ │ └── 释放 dev_sem,不初始化
│ └── device->ref_count++ 自增前为 0(首次打开)
│ ├── 释放 dev_sem
│ ├── ops 非空且 init 非空
│ │ ├── init 成功 → result = OS_SUCCESS
│ │ └── init 失败 → 打印 + ref_count--
│ └── ops 空/init 空 → result 保持 OS_SUCCESS
│ ├── 释放 device->sem,广播 ION_GENERIC_OPEN
│ ├── result == OS_SUCCESS → 返回 device
│ └── result != OS_SUCCESS → 返回 OS_NULL
└── 遍历结束 → 释放 dev_sem → 返回 OS_NULL4.5 os_device_close —— 关闭设备
os_err_t os_device_close(os_device_t *dev);| 参数 | 说明 |
|---|---|
dev | 设备控制块指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 关闭成功 |
OS_INVAL | 设备指针为空 |
OS_FAILURE | 设备引用计数已为 0(未打开却关闭) |
完整源码(drivers/device.c,与项目一致):
os_err_t os_device_close(os_device_t *dev)
{
int i;
os_err_t result = OS_SUCCESS;
struct os_device_cb_info *node;
struct os_device_cb_info *tmp;
if (dev == OS_NULL)
{
return OS_INVAL;
}
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
if (dev->ref_count == 0)
{
os_semaphore_post(dev->sem);
os_semaphore_post(dev_sem);
os_kprintf("invalid close device %p\r\n", dev);
return OS_FAILURE;
}
if (dev->ref_count-- > 1)
{
os_semaphore_post(dev_sem);
goto end;
}
os_semaphore_post(dev_sem);
if (dev->ops != OS_NULL && dev->ops->deinit != OS_NULL)
{
dev->ops->deinit(dev);
}
for (i = 0; i < OS_DEVICE_CB_TYPE_NUM; i++)
{
os_list_for_each_entry_safe(node, tmp, &dev->cb_heads[i], struct os_device_cb_info, list)
{
os_list_del(&node->list);
os_free(node);
}
}
end:
os_semaphore_post(dev->sem);
os_device_notify(dev, ION_GENERIC_CLOSE, 0);
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_put(&dev->plug);
#endif
return result;
}分步详解:
第 1 步:参数校验(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
return OS_INVAL;
}详细解释:防空指针。
第 2 步:拿全局锁 + 设备锁
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);详细解释:同时拿 dev_sem(全局)和 dev->sem(设备)两把锁,保证关闭操作的原子性。
第 3 步:关闭未打开设备的错误检查(条件 dev->ref_count == 0)
if (dev->ref_count == 0)
{
os_semaphore_post(dev->sem);
os_semaphore_post(dev_sem);
os_kprintf("invalid close device %p\r\n", dev);
return OS_FAILURE;
}详细解释:若引用计数已为 0(从未打开或已关闭完毕),说明"重复关闭",释放两把锁、打印错误、返回 OS_FAILURE。
第 4 步:非最后一次关闭分支(条件 dev->ref_count-- > 1)
if (dev->ref_count-- > 1)
{
os_semaphore_post(dev_sem);
goto end;
}详细解释:
dev->ref_count-- > 1:自减前引用计数大于 1,说明还有其他模块持有该设备,只需减计数,不触发deinit。- 释放全局锁
dev_sem,goto end跳到收尾。
第 5 步:最后一次关闭 —— 调用 deinit(条件 自减前 == 1)
os_semaphore_post(dev_sem);
if (dev->ops != OS_NULL && dev->ops->deinit != OS_NULL)
{
dev->ops->deinit(dev);
}详细解释:
- 自减前引用计数为 1,减后为 0,这是最后一次关闭。
- 释放全局锁后,检查
ops->deinit非空则调用,释放设备资源。
第 6 步:清理回调链表
for (i = 0; i < OS_DEVICE_CB_TYPE_NUM; i++)
{
os_list_for_each_entry_safe(node, tmp, &dev->cb_heads[i], struct os_device_cb_info, list)
{
os_list_del(&node->list);
os_free(node);
}
}详细解释:遍历 RX/TX 两类回调链表,逐个 os_list_del 摘除节点并 os_free 释放内存。关键点:这是设备关闭时清理回调资源的地方。
第 7 步:收尾(end 标签)
end:
os_semaphore_post(dev->sem);
os_device_notify(dev, ION_GENERIC_CLOSE, 0);
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_put(&dev->plug);
#endif
return result;详细解释:释放设备锁,广播关闭通知,热插拔场景 os_plug_put,返回结果。
os_device_close() 条件走向总结:
os_device_close() 条件走向总结:
├── dev == OS_NULL → 返回 OS_INVAL
├── 拿 dev_sem + dev->sem
├── dev->ref_count == 0(重复关闭)
│ └── 释放两把锁 → 打印错误 → 返回 OS_FAILURE
├── dev->ref_count-- > 1(自减前>1,非最后一次)
│ ├── 释放 dev_sem
│ └── goto end → 释放 dev->sem + 广播 CLOSE → 返回 OS_SUCCESS
└── 自减前 == 1(最后一次关闭)
├── 释放 dev_sem
├── ops 非空且 deinit 非空 → 调用 deinit
├── 遍历 cb_heads[0]/[1],逐个删除并 free 回调节点
└── end: 释放 dev->sem + 广播 CLOSE → 返回 OS_SUCCESS使用示例:
os_device_t *dev = os_device_find("demo");
os_err_t ret = os_device_open(dev);
if (ret != OS_EOK)
{
return;
}
/* 使用设备:此处可进行 read/write/control 等操作 */
ret = os_device_close(dev);
if (ret != OS_EOK)
{
os_kprintf("close failed, ret=%d\r\n", ret);
}4.6 os_device_read_block / os_device_read_nonblock —— 读取设备
阻塞读与非阻塞读两个函数,签名相同:
os_ssize_t os_device_read_block(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size);
os_ssize_t os_device_read_nonblock(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size);| 参数 | 说明 |
|---|---|
dev | 设备控制块指针 |
pos | 读取位置偏移量 |
buffer | 接收数据的缓冲区 |
size | 期望读取的数据大小 |
| 返回值 | 说明 |
|---|---|
| 实际读取字节数 | 成功(≥ 0) |
OS_INVAL | 设备指针为空 |
0 | 无数据/超时/设备移除/未实现 read |
完整源码(drivers/device.c,与项目一致):
os_ssize_t os_device_read_block(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size)
{
os_ssize_t count;
if (dev == OS_NULL)
{
return OS_INVAL;
}
OS_ASSERT(dev->ref_count != 0);
if (dev->ops == OS_NULL || dev->ops->read == OS_NULL)
return 0;
if (size == 0)
return 0;
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return 0;
/* block */
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
while ((dev->rx_size != 0) && (dev->rx_count == 0) && (dev->status == OS_DEVICE_STATUS_NORMAL))
{
os_semaphore_post(dev->sem);
if (os_semaphore_wait(dev->rx_sem, dev->rx_timeout) != OS_SUCCESS)
return 0;
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
}
if (dev->status == OS_DEVICE_STATUS_REMOVED)
{
os_semaphore_post(dev->sem);
return 0;
}
/* read */
count = dev->ops->read(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_READ_BLOCK, count);
/* wake up other thread */
if (dev->rx_count != 0)
{
os_semaphore_post(dev->rx_sem);
}
os_semaphore_post(dev->sem);
return count;
}os_device_read_block() 分步详解:
第 1 步:参数校验(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
return OS_INVAL;
}详细解释:防空指针。
第 2 步:断言设备已打开
OS_ASSERT(dev->ref_count != 0);详细解释:ref_count 必须非 0(设备已打开),否则说明未打开就读取,断言失败。这是调试期检查,提示使用前必须 os_device_open。
第 3 步:未实现 read 的检查(条件 ops == OS_NULL || ops->read == OS_NULL)
if (dev->ops == OS_NULL || dev->ops->read == OS_NULL)
return 0;详细解释:驱动未实现 read 时返回 0。
第 4 步:读取大小为 0 的检查(条件 size == 0)
if (size == 0)
return 0;详细解释:无需读取 0 字节。
第 5 步:设备已移除检查(条件 dev->status == OS_DEVICE_STATUS_REMOVED)
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return 0;详细解释:设备被移除(热插拔拔出)时无法读取,返回 0。
第 6 步:拿设备锁
/* block */
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);详细解释:获取设备互斥锁。
第 7 步:阻塞等待数据(条件 rx_size != 0 && rx_count == 0 && status == NORMAL)
while ((dev->rx_size != 0) && (dev->rx_count == 0) && (dev->status == OS_DEVICE_STATUS_NORMAL))
{
os_semaphore_post(dev->sem);
if (os_semaphore_wait(dev->rx_sem, dev->rx_timeout) != OS_SUCCESS)
return 0;
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
}详细解释:
(1)进入 while 循环的三个条件(必须同时成立才阻塞)
| 条件 | 含义 | 为真(进入阻塞) | 为假(直接去读) |
|---|---|---|---|
dev->rx_size != 0 | 设备有接收能力 | 驱动配置了接收缓冲区 | rx_size == 0:纯输出设备,不用等 |
dev->rx_count == 0 | 当前无数据 | 接收缓冲空 | rx_count != 0:已有数据,直接读 |
dev->status == NORMAL | 设备正常 | 设备在正常状态 | 被移除则跳过,第 8 步返回 0 |
数值演算表(设 rx_size = 128):
| 场景 | rx_size != 0 | rx_count == 0 | status == NORMAL | 三条件齐? | 行为 |
|---|---|---|---|---|---|
| 初始无数据 | 真 | 真 | 真 | 是 | 进入循环,阻塞等数据 |
已有数据 rx_count = 5 | 真 | 假 | 真 | 否 | 跳过,直接读 5 字节 |
设备不接收 rx_size = 0 | 假 | 真 | 真 | 否 | 跳过,直接读 |
| 设备被移除 | 真 | 真 | 假 | 否 | 跳过,第 8 步返回 0 |
(2)循环体的三步逻辑
第 ① 步:os_semaphore_post(dev->sem) —— 先释放设备锁
os_semaphore_post(dev->sem);等待数据是可能长时间阻塞的操作。若持锁等待 rx_sem,数据到达时驱动中断里调用的 os_device_recv_notify(内部 os_semaphore_post(rx_sem) 唤醒)可能还需要拿 dev->sem 或操作设备,会拿不到锁而卡死,造成死锁/阻塞。所以必须先释放 dev->sem,让驱动和其他任务正常工作,自己再去等数据。
第 ② 步:os_semaphore_wait(dev->rx_sem, dev->rx_timeout) —— 阻塞等待数据信号量
if (os_semaphore_wait(dev->rx_sem, dev->rx_timeout) != OS_SUCCESS)
return 0;- 这是阻塞读的核心:当前任务在接收信号量
rx_sem上睡眠。 - 唤醒来源:驱动在中断里调用
os_device_recv_notify(dev)→os_semaphore_post(rx_sem)→ 唤醒本任务。 - 超时返回:
rx_timeout时间到了还没数据,os_semaphore_wait返回非OS_SUCCESS,函数return 0(超时未读到数据)。rx_timeout默认是OS_TICK_MAX(永久等待,见 4.1 节)。
第 ③ 步:os_semaphore_wait(dev->sem, OS_WAIT_FOREVER) —— 被唤醒后重新拿回设备锁
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);被唤醒后重新获取设备锁 dev->sem(第 ① 步释放了),拿到锁后回到 while 顶部重新判断三个条件。
(3)为什么用 while 而不是 if —— 防止"唤醒后条件仍不满足"的竞态
若只判断一次(if),会出现:任务发现 rx_count == 0 进入等待 → 中断来数据唤醒任务、rx_count 变 1 → 但更高优先级任务可能抢先读走数据、rx_count 又变回 0 → 此时直接读会读到空数据。
用 while 后,被唤醒会重新检查条件:若醒来发现 rx_count == 0(数据又被抢走),就再次进入循环继续等,直到真正有数据才跳出,保证"确实有数据才去读"。
(4)完整执行流程
任务调用 os_device_read_block(dev, pos, buffer, size)
│
▼
拿 dev->sem(设备锁)
│
▼
while 判断三个条件:
rx_size != 0 且 rx_count == 0 且 status == NORMAL
│
├── 条件不满足(有数据/不接收/被移除)→ 跳出循环,直接去读
│
└── 条件满足(无数据可等)→ 进入循环体:
│
① os_semaphore_post(dev->sem) // 先放锁,避免持锁死等
│
② os_semaphore_wait(rx_sem, timeout) // 阻塞等数据
│ ├── 超时 → return 0
│ └── 被 os_device_recv_notify 唤醒
│
③ os_semaphore_wait(dev->sem) // 重新拿锁
│
▼
回到 while 顶部,重新判断条件(防竞态,数据被抢走就再等)关键点 —— 阻塞等待数据的易错点:
dev->sem与dev->rx_sem是两把不同的锁:dev->sem是设备互斥锁(保护设备内部数据),rx_sem是接收信号量(表示"有没有数据")。这里"放dev->sem→ 等rx_sem→ 再拿dev->sem"是典型的避免持锁阻塞手法。- 唤醒后不一定能读到数据:被
os_device_recv_notify唤醒只是"有数据来了"的信号,实际可能被别的任务抢走,所以要靠while重新判断。 - 中断唤醒闭环:数据到达 → 驱动 ISR 调
os_device_recv_notify→os_semaphore_post(rx_sem)→ 唤醒此处阻塞的读任务,这就是"阻塞读"拿到数据的完整链路。
第 8 步:等待被唤醒后再次检查设备状态(条件 设备被移除)
if (dev->status == OS_DEVICE_STATUS_REMOVED)
{
os_semaphore_post(dev->sem);
return 0;
}详细解释:被唤醒后若设备已被移除,释放锁返回 0。
第 9 步:调用底层 read
/* read */
count = dev->ops->read(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_READ_BLOCK, count);详细解释:调用驱动的 ops->read 实际读取数据,并广播"阻塞读取完成"通知。
第 10 步:唤醒其他读线程
/* wake up other thread */
if (dev->rx_count != 0)
{
os_semaphore_post(dev->rx_sem);
}详细解释:若仍有数据(rx_count != 0),os_semaphore_post(rx_sem) 唤醒下一个等待读的任务,实现多读任务轮询读取。
第 11 步:释放锁并返回
os_semaphore_post(dev->sem);
return count;详细解释:释放设备锁,返回实际读取字节数。
os_device_read_block() 条件走向总结:
os_device_read_block() 条件走向总结:
├── dev == OS_NULL → 返回 OS_INVAL
├── OS_ASSERT(ref_count != 0)(未打开则断言失败)
├── ops 为空 或 ops->read 为空 → 返回 0
├── size == 0 → 返回 0
├── status == REMOVED → 返回 0
├── 拿 dev->sem
├── 进入 while:(rx_size != 0) && (rx_count == 0) && (status == NORMAL)
│ ├── 释放 dev->sem
│ ├── os_semaphore_wait(rx_sem, rx_timeout)
│ │ ├── 超时 → 返回 0
│ │ └── 被唤醒 → 拿 dev->sem,回到循环顶部重新判断
│ └── 循环条件不再成立 → 退出
├── 退出循环后 status == REMOVED → 释放锁 → 返回 0
├── count = ops->read(dev, pos, buffer, size);广播 ION_GENERIC_READ_BLOCK
├── rx_count != 0 → os_semaphore_post(rx_sem)
└── 释放 dev->sem → 返回 countos_device_read_nonblock() 完整源码(drivers/device.c,与项目一致):
os_ssize_t os_device_read_nonblock(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size)
{
os_ssize_t count;
if (dev == OS_NULL)
{
return OS_INVAL;
}
OS_ASSERT(dev->ref_count != 0);
if (dev->ops == OS_NULL || dev->ops->read == OS_NULL)
return 0;
if (size == 0)
return 0;
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return 0;
if (dev->rx_size != 0 && dev->rx_count == 0)
return 0;
/* nonblock */
count = dev->ops->read(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_READ_NONBLOCK, count);
return count;
}os_device_read_nonblock() 分步详解:
第 1~5 步:参数校验与前置检查(与阻塞版相同的四个检查)
if (dev == OS_NULL)
{
return OS_INVAL;
}
OS_ASSERT(dev->ref_count != 0);
if (dev->ops == OS_NULL || dev->ops->read == OS_NULL)
return 0;
if (size == 0)
return 0;
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return 0;详细解释:依次做防空、断言已打开、read 是否实现、size 是否为 0、设备是否移除的检查,与阻塞版第 1~5 步完全一致。
第 6 步:无数据立即返回(条件 rx_size != 0 && rx_count == 0)
if (dev->rx_size != 0 && dev->rx_count == 0)
return 0;详细解释:这是阻塞与非阻塞读的核心区别。非阻塞版在"设备声明了接收能力但当前无数据"时直接返回 0,不阻塞、不等待 rx_sem。
第 7 步:调用底层 read
/* nonblock */
count = dev->ops->read(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_READ_NONBLOCK, count);
return count;详细解释:直接调用 ops->read 读取,广播"非阻塞读取完成"通知,返回结果。
阻塞 vs 非阻塞读对比:
| 特性 | os_device_read_block | os_device_read_nonblock |
|---|---|---|
| 无数据时 | 阻塞等待 rx_sem(可超时) | 立即返回 0 |
| 等待超时 | rx_timeout 控制 | 无等待 |
| 适用场景 | 交互式/需要保证拿到数据的读取 | 轮询式读取、中断回调中调用 |
4.7 os_device_write_block / os_device_write_nonblock —— 写入设备
os_ssize_t os_device_write_block(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size);
os_ssize_t os_device_write_nonblock(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size);| 参数 | 说明 |
|---|---|
dev | 设备控制块指针 |
pos | 写入位置偏移量 |
buffer | 待写入的数据缓冲区 |
size | 期望写入的数据大小 |
| 返回值 | 说明 |
|---|---|
| 实际写入字节数 | 成功(≥ 0) |
OS_INVAL | 设备指针为空 |
0 | 缓冲满/超时/设备移除/未实现 write |
完整源码(drivers/device.c,与项目一致):
os_ssize_t os_device_write_block(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size)
{
os_ssize_t count;
if (dev == OS_NULL)
{
return OS_INVAL;
}
OS_ASSERT(dev->ref_count != 0);
if (dev->ops == OS_NULL || dev->ops->write == OS_NULL)
return 0;
if (size == 0)
return 0;
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return 0;
/* block */
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
while ((dev->tx_size != 0) && (dev->tx_count == dev->tx_size) && (dev->status == OS_DEVICE_STATUS_NORMAL))
{
os_semaphore_post(dev->sem);
if (os_semaphore_wait(dev->tx_sem, dev->tx_timeout) != OS_SUCCESS)
return 0;
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
}
if (dev->status == OS_DEVICE_STATUS_REMOVED)
{
os_semaphore_post(dev->sem);
return 0;
}
/* write */
count = dev->ops->write(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_WRITE_BLOCK, count);
/* wake up other thread */
if (dev->tx_count != dev->tx_size)
{
os_semaphore_post(dev->tx_sem);
}
os_semaphore_post(dev->sem);
return count;
}os_device_write_block() 分步详解:
第 1~5 步:参数校验与前置检查
if (dev == OS_NULL)
{
return OS_INVAL;
}
OS_ASSERT(dev->ref_count != 0);
if (dev->ops == OS_NULL || dev->ops->write == OS_NULL)
return 0;
if (size == 0)
return 0;
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return 0;详细解释:与读函数相同的防空、断言已打开、write 是否实现、size、设备状态检查。
第 6 步:拿设备锁
/* block */
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);详细解释:获取设备互斥锁。
第 7 步:阻塞等待可写(条件 tx_size != 0 && tx_count == tx_size && status == NORMAL)
while ((dev->tx_size != 0) && (dev->tx_count == dev->tx_size) && (dev->status == OS_DEVICE_STATUS_NORMAL))
{
os_semaphore_post(dev->sem);
if (os_semaphore_wait(dev->tx_sem, dev->tx_timeout) != OS_SUCCESS)
return 0;
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
}详细解释:
- 循环条件:
tx_size != 0(设备声明发送能力)且tx_count == tx_size(发送缓冲已满)且 设备正常。 - 循环体内:释放设备锁 →
os_semaphore_wait(tx_sem, tx_timeout)等待可写信号量;超时返回 0;被os_device_send_notify唤醒后重新拿锁判断。
关键点 —— 为什么是 tx_count == tx_size 才算满?
tx_count表示当前占用/待发送的计数,tx_size是发送缓冲总大小。二者相等说明缓冲已满,无法再写,需等待驱动发送完成(通过os_device_send_notifyposttx_sem)。
第 8 步:唤醒后再检查设备状态
if (dev->status == OS_DEVICE_STATUS_REMOVED)
{
os_semaphore_post(dev->sem);
return 0;
}详细解释:被唤醒后设备已移除则返回 0。
第 9 步:调用底层 write
/* write */
count = dev->ops->write(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_WRITE_BLOCK, count);详细解释:调用 ops->write 实际写入,广播"阻塞写入完成"通知。
第 10 步:唤醒其他写线程
/* wake up other thread */
if (dev->tx_count != dev->tx_size)
{
os_semaphore_post(dev->tx_sem);
}详细解释:写入后若缓冲未满(tx_count != tx_size),post tx_sem 唤醒下一个等待写的任务。
第 11 步:释放锁并返回
os_semaphore_post(dev->sem);
return count;详细解释:释放设备锁,返回实际写入字节数。
os_device_write_block() 条件走向总结:
os_device_write_block() 条件走向总结:
├── dev == OS_NULL → 返回 OS_INVAL
├── OS_ASSERT(ref_count != 0)
├── ops 为空 或 ops->write 为空 → 返回 0
├── size == 0 → 返回 0
├── status == REMOVED → 返回 0
├── 拿 dev->sem
├── while:(tx_size != 0) && (tx_count == tx_size) && (status == NORMAL)
│ ├── 释放 dev->sem
│ ├── os_semaphore_wait(tx_sem, tx_timeout)
│ │ ├── 超时 → 返回 0
│ │ └── 被唤醒 → 拿锁,回顶部判断
│ └── 条件不成立 → 退出
├── status == REMOVED → 释放锁 → 返回 0
├── count = ops->write(dev, pos, buffer, size);广播 ION_GENERIC_WRITE_BLOCK
├── tx_count != tx_size → post(tx_sem)
└── 释放 dev->sem → 返回 countos_device_write_nonblock() 完整源码(drivers/device.c,与项目一致):
os_ssize_t os_device_write_nonblock(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size)
{
os_ssize_t count;
if (dev == OS_NULL)
{
return OS_INVAL;
}
OS_ASSERT(dev->ref_count != 0);
if (dev->ops == OS_NULL || dev->ops->write == OS_NULL)
return 0;
if (size == 0)
return 0;
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return 0;
if (dev->tx_size != 0 && dev->tx_count == dev->tx_size)
return 0;
/* nonblock */
count = dev->ops->write(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_WRITE_NONBLOCK, count);
return count;
}os_device_write_nonblock() 分步详解:
第 1~5 步:参数校验与前置检查(与阻塞版一致,此处不再重复贴代码)
第 6 步:缓冲满立即返回(条件 tx_size != 0 && tx_count == tx_size)
if (dev->tx_size != 0 && dev->tx_count == dev->tx_size)
return 0;详细解释:非阻塞写的核心区别——发送缓冲满时直接返回 0,不等待 tx_sem。
第 7 步:调用底层 write
/* nonblock */
count = dev->ops->write(dev, pos, buffer, size);
os_device_notify(dev, ION_GENERIC_WRITE_NONBLOCK, count);
return count;详细解释:直接调用 ops->write,广播通知,返回结果。
阻塞 vs 非阻塞写对比:
| 特性 | os_device_write_block | os_device_write_nonblock |
|---|---|---|
| 缓冲满时 | 阻塞等待 tx_sem(可超时) | 立即返回 0 |
| 等待超时 | tx_timeout 控制 | 无等待 |
| 适用场景 | 需要保证数据写完的发送 | 轮询发送、中断回调中发送 |
读写使用示例:
#include <device.h>
#include <string.h>
#define BUF_SIZE 64
void device_rw_demo(void)
{
os_device_t *dev;
os_ssize_t n;
uint8_t tx_buf[BUF_SIZE] = "hello device";
uint8_t rx_buf[BUF_SIZE] = {0};
dev = os_device_find("uart1");
if (dev == OS_NULL)
{
os_kprintf("uart1 not found\r\n");
return;
}
if (os_device_open(dev) != OS_EOK)
{
os_kprintf("open failed\r\n");
return;
}
/* 阻塞写 */
n = os_device_write_block(dev, 0, tx_buf, os_strlen((const char *)tx_buf));
if (n > 0)
{
os_kprintf("sent %d bytes\r\n", n);
}
/* 阻塞读(会一直等数据,需先设超时) */
n = os_device_read_block(dev, 0, rx_buf, BUF_SIZE);
if (n > 0)
{
os_kprintf("recv %d bytes: %s\r\n", n, rx_buf);
}
os_device_close(dev);
}4.8 os_device_control —— 控制设备
os_err_t os_device_control(os_device_t *dev, int cmd, void *arg);| 参数 | 说明 |
|---|---|
dev | 设备控制块指针 |
cmd | 控制命令(用 IOC_xxx(cmd) 宏构造,含设备类型信息) |
arg | 命令参数 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 控制成功 |
OS_INVAL | 设备指针为空 |
OS_NODEV | 设备已被移除 |
OS_BUSY | 回调已存在(SET_CB 时) |
OS_FAILURE | 移除不存在的回调(RM_CB 时) |
OS_NOSYS | 设备未实现 control |
| 其他 | ops->control 的返回结果 |
完整源码(drivers/device.c,与项目一致):
os_err_t os_device_control(os_device_t *dev, int cmd, void *arg)
{
os_err_t ret = OS_SUCCESS;
os_ubase_t level;
if (dev == OS_NULL)
{
return OS_INVAL;
}
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return OS_NODEV;
os_device_notify(dev, ION_GENERIC_CONTROL, cmd);
switch (cmd)
{
case OS_DEVICE_CTRL_SET_CB:
{
OS_ASSERT(OS_NULL != arg);
struct os_device_cb_info *info = os_calloc(1, sizeof(struct os_device_cb_info));
struct os_device_cb_info *node;
struct os_device_cb_info *tmp;
OS_ASSERT(info);
*info = *(struct os_device_cb_info *)arg;
os_spin_lock_irq(&gs_device_lock, &level);
os_list_for_each_entry_safe(node, tmp, &dev->cb_heads[info->type], struct os_device_cb_info, list)
{
if (info->cb == node->cb)
{
os_spin_unlock_irq(&gs_device_lock, level);
os_kprintf("device callback allready exist %p\r\n", info->cb);
os_free(info);
ret = OS_BUSY;
goto end;
}
}
os_list_add_tail(&dev->cb_heads[info->type], &info->list);
os_spin_unlock_irq(&gs_device_lock, level);
goto end;
}
case OS_DEVICE_CTRL_RM_CB:
{
OS_ASSERT(OS_NULL != arg);
struct os_device_cb_info *info = arg;
struct os_device_cb_info *node;
struct os_device_cb_info *tmp;
os_spin_lock_irq(&gs_device_lock, &level);
os_list_for_each_entry_safe(node, tmp, &dev->cb_heads[info->type], struct os_device_cb_info, list)
{
if (info->cb == node->cb)
{
os_list_del(&node->list);
os_spin_unlock_irq(&gs_device_lock, level);
os_free(node);
goto end;
}
}
os_spin_unlock_irq(&gs_device_lock, level);
ret = OS_FAILURE;
goto end;
}
case OS_DEVICE_CTRL_SET_RX_TIMEOUT:
dev->rx_timeout = *(os_ubase_t *)arg;
goto end;
case OS_DEVICE_CTRL_SET_TX_TIMEOUT:
dev->tx_timeout = *(os_ubase_t *)arg;
goto end;
}
if (OS_NULL == dev->ops || OS_NULL == dev->ops->control)
{
ret = OS_NOSYS;
goto end;
}
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
ret = dev->ops->control(dev, cmd, arg);
os_semaphore_post(dev->sem);
end:
return ret;
}分步详解:
第 1 步:参数校验(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
return OS_INVAL;
}详细解释:防空指针。
第 2 步:设备移除检查(条件 status == OS_DEVICE_STATUS_REMOVED)
if (dev->status == OS_DEVICE_STATUS_REMOVED)
return OS_NODEV;详细解释:设备被移除时返回 OS_NODEV。
第 3 步:广播控制通知
os_device_notify(dev, ION_GENERIC_CONTROL, cmd);详细解释:广播"控制操作"事件,args 传入 cmd。
第 4 步:处理 OS_DEVICE_CTRL_SET_CB(条件 cmd == SET_CB)
case OS_DEVICE_CTRL_SET_CB:
{
OS_ASSERT(OS_NULL != arg);
struct os_device_cb_info *info = os_calloc(1, sizeof(struct os_device_cb_info));
struct os_device_cb_info *node;
struct os_device_cb_info *tmp;
OS_ASSERT(info);
*info = *(struct os_device_cb_info *)arg;
os_spin_lock_irq(&gs_device_lock, &level);
os_list_for_each_entry_safe(node, tmp, &dev->cb_heads[info->type], struct os_device_cb_info, list)
{
if (info->cb == node->cb)
{
os_spin_unlock_irq(&gs_device_lock, level);
os_kprintf("device callback allready exist %p\r\n", info->cb);
os_free(info);
ret = OS_BUSY;
goto end;
}
}
os_list_add_tail(&dev->cb_heads[info->type], &info->list);
os_spin_unlock_irq(&gs_device_lock, level);
goto end;
}详细解释:
OS_ASSERT(arg != OS_NULL):回调信息指针非空。os_calloc申请新节点,OS_ASSERT(info)确认申请成功。*info = *(struct os_device_cb_info *)arg:把arg指向的回调信息拷贝到新节点。os_spin_lock_irq(&gs_device_lock, &level):用自旋锁保护(因为回调链表可能在中断中被访问)。- 遍历
cb_heads[info->type]:若已存在相同cb函数,打印提示、释放新节点、置ret = OS_BUSY返回。 - 否则
os_list_add_tail挂到对应回调链表,释放锁。
第 5 步:处理 OS_DEVICE_CTRL_RM_CB(条件 cmd == RM_CB)
case OS_DEVICE_CTRL_RM_CB:
{
OS_ASSERT(OS_NULL != arg);
struct os_device_cb_info *info = arg;
struct os_device_cb_info *node;
struct os_device_cb_info *tmp;
os_spin_lock_irq(&gs_device_lock, &level);
os_list_for_each_entry_safe(node, tmp, &dev->cb_heads[info->type], struct os_device_cb_info, list)
{
if (info->cb == node->cb)
{
os_list_del(&node->list);
os_spin_unlock_irq(&gs_device_lock, level);
os_free(node);
goto end;
}
}
os_spin_unlock_irq(&gs_device_lock, level);
ret = OS_FAILURE;
goto end;
}详细解释:
- 注意
RM_CB与SET_CB不同:info = arg直接复用arg指向的节点(不是拷贝)。 - 遍历匹配
cb后os_list_del摘除并os_free释放。 - 未找到则
ret = OS_FAILURE。
第 6 步:处理超时设置命令(条件 cmd == SET_RX_TIMEOUT / SET_TX_TIMEOUT)
case OS_DEVICE_CTRL_SET_RX_TIMEOUT:
dev->rx_timeout = *(os_ubase_t *)arg;
goto end;
case OS_DEVICE_CTRL_SET_TX_TIMEOUT:
dev->tx_timeout = *(os_ubase_t *)arg;
goto end;详细解释:直接把 arg 指向的值(os_ubase_t)写入 rx_timeout/tx_timeout 字段。这就是设置收发超时的底层实现。
第 7 步:其他命令交给驱动(条件 非框架处理命令)
if (OS_NULL == dev->ops || OS_NULL == dev->ops->control)
{
ret = OS_NOSYS;
goto end;
}
os_semaphore_wait(dev->sem, OS_WAIT_FOREVER);
ret = dev->ops->control(dev, cmd, arg);
os_semaphore_post(dev->sem);详细解释:
- 走到这里说明
cmd不是框架处理的 4 个命令之一,需交给驱动。 - 若驱动未实现
control,返回OS_NOSYS。 - 否则拿设备锁 → 调用
ops->control→ 释放锁。
第 8 步:返回结果
end:
return ret;详细解释:所有分支都 goto end 汇聚到这里返回 ret。
os_device_control() 条件走向总结:
os_device_control() 条件走向总结:
├── dev == OS_NULL → 返回 OS_INVAL
├── status == REMOVED → 返回 OS_NODEV
├── 广播 ION_GENERIC_CONTROL
├── switch(cmd)
│ ├── SET_CB:arg 空→断言;拷贝 info;加自旋锁
│ │ ├── 已存在相同 cb → 打印 + free + ret=OS_BUSY
│ │ └── 不存在 → list_add_tail + 解锁 → goto end
│ ├── RM_CB:arg 空→断言;加自旋锁
│ │ ├── 找到 cb → list_del + free → goto end
│ │ └── 未找到 → 解锁 + ret=OS_FAILURE → goto end
│ ├── SET_RX_TIMEOUT:rx_timeout = *(os_ubase_t*)arg → goto end
│ ├── SET_TX_TIMEOUT:tx_timeout = *(os_ubase_t*)arg → goto end
│ └── 其他命令(不匹配上面任何 case)
│ ├── ops 空 或 ops->control 空 → ret = OS_NOSYS
│ └── 否则 → 拿 dev->sem → ret = ops->control(dev, cmd, arg) → 释放锁
└── end: 返回 ret使用示例(设置超时 + 注册接收回调):
#include <device.h>
/* 接收回调 */
static os_err_t rx_callback(os_device_t *dev, struct os_device_cb_info *info)
{
os_kprintf("rx data, size=%d\r\n", info->size);
return OS_EOK;
}
void control_demo(void)
{
os_device_t *dev = os_device_find("uart1");
if (dev == OS_NULL)
{
return;
}
/* 设置接收超时 500 tick */
os_ubase_t timeout = 500;
os_device_control(dev, OS_DEVICE_CTRL_SET_RX_TIMEOUT, &timeout);
/* 注册接收回调 */
struct os_device_cb_info info = {0};
info.type = OS_DEVICE_CB_TYPE_RX;
info.cb = rx_callback;
os_err_t ret = os_device_control(dev, OS_DEVICE_CTRL_SET_CB, &info);
if (ret != OS_EOK)
{
os_kprintf("set cb failed, ret=%d\r\n", ret);
}
}4.9 os_device_unregister / os_device_removed —— 注销设备
os_device_unregister 把设备从全局设备列表移除。
os_err_t os_device_unregister(os_device_t *dev);| 参数 | 说明 |
|---|---|
dev | 设备控制块指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 注销成功 |
OS_INVAL | 设备指针为空 |
完整源码(drivers/device.c,与项目一致):
/* deprecated function */
os_err_t os_device_unregister(os_device_t *dev)
{
if (dev == OS_NULL)
{
return OS_INVAL;
}
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_out(&dev->plug);
return OS_SUCCESS;
#else
return _device_unregister(dev);
#endif
}分步详解:
第 1 步:参数校验(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
return OS_INVAL;
}详细解释:防空指针。
第 2 步:按配置走两条路径(条件 OS_DEVICE_SUPPORT_PLUG)
#ifdef OS_DEVICE_SUPPORT_PLUG
os_plug_out(&dev->plug);
return OS_SUCCESS;
#else
return _device_unregister(dev);
#endif详细解释:
- 开启热插拔:
os_plug_out拔出设备插件(真正注销由插件的 release 回调触发)。 - 未开启:调用内部函数
_device_unregister直接注销。
关键理解:注释标明 /* deprecated function */,即该 API 已标记为废弃。它是内部辅助函数 _device_unregister 的薄封装。
核心辅助函数 _device_unregister(drivers/device.c,与项目一致):
static os_err_t _device_unregister(os_device_t *dev)
{
OS_ASSERT(OS_NULL != dev);
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);
OS_ASSERT(dev->ref_count == 0);
#ifdef OS_USING_VFS_DEVFS
devfs_unregister_device(device_name(dev));
#endif
os_list_del(&dev->list);
os_semaphore_destroy(dev->sem);
os_semaphore_destroy(dev->tx_sem);
os_semaphore_destroy(dev->rx_sem);
os_device_notify(dev, ION_GENERIC_UNREGISTER, 0);
os_semaphore_post(dev_sem);
return OS_SUCCESS;
}分步详解:
第 1 步:断言与拿锁
OS_ASSERT(OS_NULL != dev);
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);详细解释:断言设备非空;拿全局锁。
第 2 步:断言引用计数为 0
OS_ASSERT(dev->ref_count == 0);详细解释:注销前提是设备没有被使用(引用计数为 0)。若设备仍被打开,这里会断言失败。这是防止"设备使用中被卸载"的关键机制。
第 3 步:注销 devfs 设备(条件 OS_USING_VFS_DEVFS)
#ifdef OS_USING_VFS_DEVFS
devfs_unregister_device(device_name(dev));
#endif详细解释:开启 devfs 时从 VFS 注销。
第 4 步:从全局链表摘除
os_list_del(&dev->list);详细解释:从 os_device_list 摘除该设备节点。
第 5 步:销毁三个信号量
os_semaphore_destroy(dev->sem);
os_semaphore_destroy(dev->tx_sem);
os_semaphore_destroy(dev->rx_sem);详细解释:销毁设备的三类信号量,释放内核资源。
第 6 步:广播注销通知并释放锁
os_device_notify(dev, ION_GENERIC_UNREGISTER, 0);
os_semaphore_post(dev_sem);
return OS_SUCCESS;详细解释:广播注销事件,释放全局锁,返回成功。
os_device_unregister() / _device_unregister() 条件走向总结:
os_device_unregister() 条件走向总结:
├── dev == OS_NULL → 返回 OS_INVAL
├── 开启 OS_DEVICE_SUPPORT_PLUG
│ └── os_plug_out(&dev->plug) → 返回 OS_SUCCESS
└── 未开启 → 调用 _device_unregister(dev)
├── OS_ASSERT(dev != NULL)
├── 拿 dev_sem
├── OS_ASSERT(ref_count == 0)(被使用则断言失败)
├── #ifdef OS_USING_VFS_DEVFS → devfs_unregister_device
├── os_list_del(&dev->list)
├── os_semaphore_destroy(sem/tx_sem/rx_sem)
├── 广播 ION_GENERIC_UNREGISTER
└── 释放 dev_sem → 返回 OS_SUCCESS关于 os_device_removed:该函数在 drivers/device.h 中有声明(os_err_t os_device_removed(os_device_t *dev);),但在当前 drivers/device.c 中未见实现(属于已声明但未实现的接口)。使用时应以 os_device_unregister 为主。
4.10 os_device_recv_notify / os_device_send_notify —— 收发通知
这两个函数通常由驱动在中断服务程序(ISR)中调用,用于唤醒阻塞读写的任务并触发回调。
void os_device_recv_notify(os_device_t *dev);
void os_device_send_notify(os_device_t *dev);| 参数 | 说明 |
|---|---|
dev | 设备控制块指针 |
无返回值。
os_device_recv_notify() 完整源码(drivers/device.c,与项目一致):
void os_device_recv_notify(os_device_t *dev)
{
struct os_device_cb_info *info;
/* wake up block thread */
os_semaphore_post(dev->rx_sem);
/* invoke rx callback */
os_list_for_each_entry(info, &dev->cb_heads[OS_DEVICE_CB_TYPE_RX], struct os_device_cb_info, list)
{
if (info != OS_NULL && info->cb != OS_NULL)
{
info->size = dev->rx_count;
info->cb(dev, info);
}
}
#ifdef OS_USING_IO_MULTIPLEXING
dev_poll_wakeup(dev, POLLIN);
#endif
}os_device_recv_notify() 分步详解:
第 1 步:释放接收信号量
/* wake up block thread */
os_semaphore_post(dev->rx_sem);详细解释:os_semaphore_post(rx_sem) 唤醒阻塞在 os_device_read_block 中 os_semaphore_wait(rx_sem, rx_timeout) 上的读任务。
第 2 步:遍历并调用接收回调
/* invoke rx callback */
os_list_for_each_entry(info, &dev->cb_heads[OS_DEVICE_CB_TYPE_RX], struct os_device_cb_info, list)
{
if (info != OS_NULL && info->cb != OS_NULL)
{
info->size = dev->rx_count;
info->cb(dev, info);
}
}详细解释:
- 遍历
cb_heads[OS_DEVICE_CB_TYPE_RX](接收回调链表)。 - 每个节点回调前先填
info->size = dev->rx_count(当前可读数据量),再调用cb(dev, info)。 - 这就是
os_device_control(SET_CB)注册的回调被触发的地方。
第 3 步(条件编译):IO 多路复用唤醒(条件 OS_USING_IO_MULTIPLEXING)
#ifdef OS_USING_IO_MULTIPLEXING
dev_poll_wakeup(dev, POLLIN);
#endif详细解释:开启 IO 多路复用时通知 poll 等待者"可读"。
os_device_send_notify() 完整源码(drivers/device.c,与项目一致):
void os_device_send_notify(os_device_t *dev)
{
struct os_device_cb_info *info;
/* wake up block thread */
os_semaphore_post(dev->tx_sem);
/* invoke tx callback */
os_list_for_each_entry(info, &dev->cb_heads[OS_DEVICE_CB_TYPE_TX], struct os_device_cb_info, list)
{
if (info != OS_NULL && info->cb != OS_NULL)
{
info->size = dev->tx_size - dev->tx_count;
info->cb(dev, info);
}
}
#ifdef OS_USING_IO_MULTIPLEXING
dev_poll_wakeup(dev, POLLOUT);
#endif
}os_device_send_notify() 分步详解:
第 1 步:释放发送信号量
/* wake up block thread */
os_semaphore_post(dev->tx_sem);详细解释:os_semaphore_post(tx_sem) 唤醒阻塞在 os_device_write_block 中等待可写的写任务。
第 2 步:遍历并调用发送回调
/* invoke tx callback */
os_list_for_each_entry(info, &dev->cb_heads[OS_DEVICE_CB_TYPE_TX], struct os_device_cb_info, list)
{
if (info != OS_NULL && info->cb != OS_NULL)
{
info->size = dev->tx_size - dev->tx_count;
info->cb(dev, info);
}
}详细解释:遍历 cb_heads[TX],回调前填 info->size = dev->tx_size - dev->tx_count(当前可写空间),再调用回调。
第 3 步(条件编译):IO 多路复用唤醒
#ifdef OS_USING_IO_MULTIPLEXING
dev_poll_wakeup(dev, POLLOUT);
#endif详细解释:通知 poll 等待者"可写"。
4.11 os_device_for_each —— 遍历所有设备
int32_t os_device_for_each(os_err_t (*func)(os_device_t *dev, void *data), void *data);| 参数 | 说明 |
|---|---|
func | 遍历回调函数,对每个设备调用一次;返回非 OS_SUCCESS 时终止遍历 |
data | 传给回调的私有数据 |
| 返回值 | 说明 |
|---|---|
cnt | 实际遍历的设备数量(回调返回非 OS_SUCCESS 时提前停止并返回已遍历数) |
完整源码(drivers/device.c,与项目一致):
int32_t os_device_for_each(os_err_t (*func)(os_device_t *dev, void *data), void *data)
{
os_device_t *dev;
os_device_t *tmp;
int32_t cnt = 0;
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);
os_list_for_each_entry_safe(dev, tmp, &os_device_list, os_device_t, list)
{
if (OS_SUCCESS != func(dev, data))
{
os_semaphore_post(dev_sem);
return cnt;
}
cnt++;
}
os_semaphore_post(dev_sem);
return cnt;
}分步详解:
第 1 步:拿全局锁
os_semaphore_wait(dev_sem, OS_WAIT_FOREVER);详细解释:获取全局锁,保证遍历期间设备链表不被并发修改。
第 2 步:遍历并调用回调
os_list_for_each_entry_safe(dev, tmp, &os_device_list, os_device_t, list)
{
if (OS_SUCCESS != func(dev, data))
{
os_semaphore_post(dev_sem);
return cnt;
}
cnt++;
}详细解释:
- 用
os_list_for_each_entry_safe(带tmp保存后继节点)遍历,这样回调内部即使删除了当前设备也不会破坏遍历。 - 每次调用
func(dev, data),若返回非OS_SUCCESS,则释放锁并提前终止,返回已遍历数量cnt。
第 3 步:释放锁并返回
os_semaphore_post(dev_sem);
return cnt;详细解释:全部遍历完,释放锁,返回遍历到的设备总数。
使用示例(统计所有设备):
#include <device.h>
static os_err_t count_device(os_device_t *dev, void *data)
{
int32_t *cnt = (int32_t *)data;
(*cnt)++;
os_kprintf("dev: %s\r\n", dev->name);
return OS_EOK;
}
void count_all_devices(void)
{
int32_t cnt = 0;
int32_t total = os_device_for_each(count_device, &cnt);
os_kprintf("total %d devices, callback counted %d\r\n", total, cnt);
}4.12 设备通知 API —— os_device_notify / os_device_notify_register / os_device_notify_unregister
仅在开启 OS_USING_DEVICE_NOTIFY 时可用。
void os_device_notify(os_device_t *dev, os_ubase_t event, os_ubase_t args);
os_err_t os_device_notify_register(os_device_t *dev, device_notify_callback callback, device_notify_filter filter);
os_err_t os_device_notify_unregister(os_device_t *dev, device_notify_callback callback, device_notify_filter filter);os_device_notify() 完整源码(drivers/device.c,与项目一致):
void os_device_notify(os_device_t *dev, os_ubase_t event, os_ubase_t args)
{
struct os_device_notify_cb_info *notify_info;
os_list_for_each_entry(notify_info, &os_device_notify_list, struct os_device_notify_cb_info, list)
{
if (notify_info->callback != OS_NULL)
{
if (notify_info->filter != OS_NULL)
{
if (notify_info->filter(dev, event, args) != OS_SUCCESS)
{
return;
}
}
notify_info->callback(dev, event, args);
}
}
os_list_for_each_entry(notify_info, &dev->notify_list, struct os_device_notify_cb_info, list)
{
if (notify_info->callback != OS_NULL)
{
if (notify_info->filter != OS_NULL)
{
if (notify_info->filter(dev, event, args) != OS_SUCCESS)
{
return;
}
}
notify_info->callback(dev, event, args);
}
}
}分步详解:
第 1 步:遍历全局通知链表
os_list_for_each_entry(notify_info, &os_device_notify_list, struct os_device_notify_cb_info, list)
{
if (notify_info->callback != OS_NULL)
{
if (notify_info->filter != OS_NULL)
{
if (notify_info->filter(dev, event, args) != OS_SUCCESS)
{
return;
}
}
notify_info->callback(dev, event, args);
}
}详细解释:
os_device_notify_list是全局通知链表(device.c静态变量),存放注册时dev == OS_NULL的"全局监听"回调。- 每个节点:先看
callback非空,再看filter非空时调用过滤函数;若过滤函数返回非OS_SUCCESS,直接 return 终止整个通知广播;否则调用callback。
第 2 步:遍历设备私有通知链表
os_list_for_each_entry(notify_info, &dev->notify_list, struct os_device_notify_cb_info, list)
{
if (notify_info->callback != OS_NULL)
{
if (notify_info->filter != OS_NULL)
{
if (notify_info->filter(dev, event, args) != OS_SUCCESS)
{
return;
}
}
notify_info->callback(dev, event, args);
}
}详细解释:逻辑同上,但遍历的是该设备的 notify_list(注册时 dev != OS_NULL 的回调)。
os_device_notify() 条件走向总结:
os_device_notify() 条件走向总结:
├── 遍历 os_device_notify_list(全局监听)
│ └── 每节点:
│ ├── callback != NULL 且 filter != NULL 且 filter(dev, event, args) != OS_SUCCESS
│ │ └── 直接 return(终止广播)
│ └── 否则(callback 非空)→ callback(dev, event, args)
└── 遍历 dev->notify_list(设备私有监听)
└── 逻辑同上os_device_notify_register() 完整源码(drivers/device.c,与项目一致):
os_err_t os_device_notify_register(os_device_t *dev, device_notify_callback callback, device_notify_filter filter)
{
os_ubase_t level;
struct os_device_notify_cb_info *cb_info;
struct os_device_notify_cb_info *node;
struct os_device_notify_cb_info *tmp;
OS_ASSERT(OS_NULL != callback);
cb_info = (struct os_device_notify_cb_info *)os_calloc(1, sizeof(struct os_device_notify_cb_info));
cb_info->callback = callback;
cb_info->filter = filter;
os_spin_lock_irq(&gs_device_lock, &level);
if (dev == OS_NULL)
{
os_list_for_each_entry_safe(node, tmp, &os_device_notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_spin_unlock_irq(&gs_device_lock, level);
return OS_SUCCESS;
}
}
os_list_add(&os_device_notify_list, &cb_info->list);
}
else
{
os_list_for_each_entry_safe(node, tmp, &dev->notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_spin_unlock_irq(&gs_device_lock, level);
return OS_SUCCESS;
}
}
os_list_add(&dev->notify_list, &cb_info->list);
}
os_spin_unlock_irq(&gs_device_lock, level);
return OS_SUCCESS;
}分步详解:
第 1 步:断言回调非空并分配节点
OS_ASSERT(OS_NULL != callback);
cb_info = (struct os_device_notify_cb_info *)os_calloc(1, sizeof(struct os_device_notify_cb_info));
cb_info->callback = callback;
cb_info->filter = filter;详细解释:断言 callback 非空;os_calloc 分配节点并填充回调/过滤函数。
第 2 步:拿自旋锁(中断安全)
os_spin_lock_irq(&gs_device_lock, &level);详细解释:用 gs_device_lock 自旋锁保护(通知链表可能在中断中广播,需中断安全)。
第 3 步:注册到全局链表(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
os_list_for_each_entry_safe(node, tmp, &os_device_notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_spin_unlock_irq(&gs_device_lock, level);
return OS_SUCCESS;
}
}
os_list_add(&os_device_notify_list, &cb_info->list);
}详细解释:dev == OS_NULL 表示注册全局监听。遍历全局链表,若已有相同 callback + filter 组合,则直接返回(不重复注册,但泄漏了刚分配的 cb_info——这是源码现状,注意避免重复注册);否则 os_list_add 挂入全局链表。
第 4 步:注册到设备链表(条件 dev != OS_NULL)
else
{
os_list_for_each_entry_safe(node, tmp, &dev->notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_spin_unlock_irq(&gs_device_lock, level);
return OS_SUCCESS;
}
}
os_list_add(&dev->notify_list, &cb_info->list);
}详细解释:dev != OS_NULL 时注册到该设备的 notify_list,逻辑同上。
第 5 步:释放锁并返回
os_spin_unlock_irq(&gs_device_lock, level);
return OS_SUCCESS;详细解释:释放自旋锁,返回成功。
os_device_notify_unregister() 完整源码(drivers/device.c,与项目一致):
os_err_t os_device_notify_unregister(os_device_t *dev, device_notify_callback callback, device_notify_filter filter)
{
os_ubase_t level;
struct os_device_notify_cb_info *node;
struct os_device_notify_cb_info *tmp;
OS_ASSERT(OS_NULL != callback);
os_spin_lock_irq(&gs_device_lock, &level);
if (dev == OS_NULL)
{
os_list_for_each_entry_safe(node, tmp, &os_device_notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_list_del(&node->list);
os_spin_unlock_irq(&gs_device_lock, level);
os_free(node);
return OS_SUCCESS;
}
}
}
else
{
os_list_for_each_entry_safe(node, tmp, &dev->notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_list_del(&node->list);
os_spin_unlock_irq(&gs_device_lock, level);
os_free(node);
return OS_SUCCESS;
}
}
}
os_spin_unlock_irq(&gs_device_lock, level);
return OS_FAILURE;
}分步详解:
第 1 步:断言与拿锁
OS_ASSERT(OS_NULL != callback);
os_spin_lock_irq(&gs_device_lock, &level);详细解释:断言回调非空;拿自旋锁。
第 2 步:全局链表查找并移除(条件 dev == OS_NULL)
if (dev == OS_NULL)
{
os_list_for_each_entry_safe(node, tmp, &os_device_notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_list_del(&node->list);
os_spin_unlock_irq(&gs_device_lock, level);
os_free(node);
return OS_SUCCESS;
}
}
}详细解释:在全局链表查找匹配的 callback + filter,找到则摘除、解锁、释放节点,返回成功。
第 3 步:设备链表查找并移除(条件 dev != OS_NULL)
else
{
os_list_for_each_entry_safe(node, tmp, &dev->notify_list, struct os_device_notify_cb_info, list)
{
if ((callback == node->callback) && (filter == node->filter))
{
os_list_del(&node->list);
os_spin_unlock_irq(&gs_device_lock, level);
os_free(node);
return OS_SUCCESS;
}
}
}详细解释:在设备私有链表查找并移除,逻辑同上。
第 4 步:未找到则失败
os_spin_unlock_irq(&gs_device_lock, level);
return OS_FAILURE;详细解释:两个链表都没找到匹配项,解锁后返回 OS_FAILURE。
os_device_notify_register() / os_device_notify_unregister() 条件走向总结:
os_device_notify_register() 条件走向总结:
├── OS_ASSERT(callback != NULL)
├── os_calloc 分配 cb_info,填充 callback/filter
├── 拿 gs_device_lock
├── dev == OS_NULL(全局注册)
│ ├── 已存在相同 callback+filter → 解锁 → 返回 OS_SUCCESS
│ └── 不存在 → os_list_add 到 os_device_notify_list
└── dev != OS_NULL(设备注册)
├── 已存在相同 callback+filter → 解锁 → 返回 OS_SUCCESS
└── 不存在 → os_list_add 到 dev->notify_list
├── 解锁 → 返回 OS_SUCCESS
os_device_notify_unregister() 条件走向总结:
├── OS_ASSERT(callback != NULL)
├── 拿 gs_device_lock
├── dev == OS_NULL(全局)
│ ├── 找到 → os_list_del + 解锁 + os_free → 返回 OS_SUCCESS
│ └── 未找到 →(跳过)
└── dev != OS_NULL(设备)
├── 找到 → os_list_del + 解锁 + os_free → 返回 OS_SUCCESS
└── 未找到 →(跳过)
├── 解锁 → 返回 OS_FAILURE设备通知使用示例:
#include <device.h>
static os_err_t my_notify_cb(os_device_t *dev, os_ubase_t event, os_ubase_t args)
{
os_kprintf("device %s event 0x%x, args %d\r\n", dev->name, event, args);
return OS_EOK;
}
void notify_demo(void)
{
os_device_t *dev = os_device_find("uart1");
os_err_t ret;
/* 注册到指定设备(dev != OS_NULL) */
ret = os_device_notify_register(dev, my_notify_cb, OS_NULL);
if (ret != OS_EOK)
{
os_kprintf("register notify failed, ret=%d\r\n", ret);
return;
}
/* 注销 */
ret = os_device_notify_unregister(dev, my_notify_cb, OS_NULL);
if (ret != OS_EOK)
{
os_kprintf("unregister notify failed, ret=%d\r\n", ret);
}
}说明:
filter = OS_NULL表示接收所有事件(不过滤)。
五、设备驱动注册与使用流程
设备驱动从注册到使用的完整生命周期如下:
┌─────────────────────────────────────────────────────────────┐
│ 驱动开发阶段 │
│ │
│ 1. 定义 os_device_t 实例(或嵌入到自定义结构体中) │
│ 2. 实现 struct os_device_ops 中的操作函数 │
│ 3. 调用 os_device_register() 注册设备 │
│ ├─ 填充名称/初始化字段/静态创建 sem/tx_sem/rx_sem │
│ ├─ os_list_add 挂入全局设备链表 │
│ └─ 广播 ION_GENERIC_REGISTER │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 应用使用阶段 │
│ │
│ 4. os_device_find("device_name") 查找设备 │
│ │ │
│ ▼ │
│ 5. os_device_open(dev) 打开设备 │
│ ├─ ref_count++ │
│ └─ 首次(ref_count从0→1) → 调用 ops->init(dev) │
│ │ │
│ ▼ │
│ 6. os_device_read_block / os_device_write_block │
│ 调用 ops->read / ops->write(含阻塞/信号量同步) │
│ │ │
│ ▼ │
│ 7. os_device_control(dev, cmd, arg) │
│ ├─ 框架命令:SET_CB/RM_CB/SET_RX_TIMEOUT/SET_TX_TIMEOUT │
│ └─ 其他命令:调用 ops->control │
│ │ │
│ ▼ │
│ 8. os_device_close(dev) 关闭设备 │
│ ├─ ref_count-- │
│ └─ 归零(ref_count→0) → 调用 ops->deinit(dev) │
│ │ │
│ ▼ │
│ 9. (可选)os_device_unregister(dev) 注销设备 │
│ 要求 ref_count == 0,否则断言失败 │
└─────────────────────────────────────────────────────────────┘引用计数变化的关键时刻:
| 操作 | ref_count 变化 | 是否触发回调 |
|---|---|---|
首次 os_device_open | 0 → 1 | 调用 ops->init |
再次 os_device_open | 1 → 2 | 不调用 |
os_device_close | 2 → 1 | 不调用 |
最后一次 os_device_close | 1 → 0 | 调用 ops->deinit |
os_device_unregister | 必须为 0 | 否则断言失败 |
5.1 注册阶段广播 ION_GENERIC_REGISTER 的作用
流程图中"驱动开发阶段"第 3 步末尾的 广播 ION_GENERIC_REGISTER,对应 os_device_register 源码的最后一行(drivers/device.c):
os_device_notify(dev, ION_GENERIC_REGISTER, 0);
os_semaphore_post(dev_sem);
return OS_SUCCESS;5.1.1 它在做什么
调用 os_device_notify(dev, ION_GENERIC_REGISTER, 0),向所有注册了通知回调的监听者广播一个"设备已注册"事件:
| 参数 | 值 | 含义 |
|---|---|---|
dev | 刚刚注册成功的设备 | 广播的对象 |
event | ION_GENERIC_REGISTER(0x1A01) | 事件类型:设备注册 |
args | 0 | 本事件无额外参数 |
5.1.2 核心价值 —— 让监听者"无需轮询即可感知新设备"
作用:让监听者第一时间感知"新设备出现了",而不必反复调用 os_device_find 去猜设备是否就绪。
一个监听模块(如 UI 层、系统服务、热插拔管理)可以提前注册一个全局通知回调:
/* dev = OS_NULL 表示注册为全局监听,会收到所有设备的注册/注销事件 */
os_device_notify_register(OS_NULL, my_register_cb, my_filter);之后,只要有任意设备 os_device_register 成功,该回调就会被自动触发,event 参数为 ION_GENERIC_REGISTER。监听者据此可以:
- 得知"某类设备已就绪",立即尝试打开并使用;
- 刷新本地的设备清单/界面列表;
- 在设备注册的瞬间执行必要的初始化动作。
如果不广播会怎样:
- 监听者无法获知设备何时注册,只能定时轮询
os_device_find去试探设备就绪; - 既增加 CPU 开销与逻辑复杂度,又存在"错过注册时机"的窗口。
5.1.3 广播流向
os_device_notify 内部会遍历两条链表(见 4.12 节):
| 遍历链表 | 属于谁 | 监听对象 |
|---|---|---|
os_device_notify_list | 全局(device.c 静态变量) | 注册时传 dev == OS_NULL,监听所有设备的事件 |
dev->notify_list | 该设备实例 | 注册时传 dev != OS_NULL,监听指定设备的事件 |
两条链上的每个回调,若 filter 返回 OS_SUCCESS(或 filter == OS_NULL 不过滤)就会被调用,args 传入 0。
5.1.4 前提条件
⚠️ 该广播仅在开启 OS_USING_DEVICE_NOTIFY 配置时生效。若未开启,os_device_notify 在 drivers/device.h 中被定义为空宏:
#define os_device_notify(...)此时这一行相当于空操作,不执行任何逻辑。这也是通知事件与通用 IOCTL 命令(OS_DEVICE_CTRL_*,始终生效)的一个重要区别。
六、完整使用示例
示例一:查找设备、打开、读写、关闭的完整流程
#include <device.h>
#include <string.h>
#define BUFFER_SIZE 128
void device_demo(void)
{
os_device_t *dev;
os_ssize_t ret;
uint8_t tx_buf[BUFFER_SIZE] = "Hello OneOS Device!";
uint8_t rx_buf[BUFFER_SIZE] = {0};
/* 1. 按名称查找设备 */
dev = os_device_find("uart1");
if (dev == OS_NULL)
{
os_kprintf("Device not found!\r\n");
return;
}
/* 2. 打开设备(首次打开会调用 ops->init) */
if (os_device_open(dev) != OS_EOK)
{
os_kprintf("Failed to open device!\r\n");
return;
}
/* 3. 设置超时时间(单位 tick),避免阻塞读写永久等待 */
os_ubase_t timeout = 1000;
os_device_control(dev, OS_DEVICE_CTRL_SET_RX_TIMEOUT, &timeout);
os_device_control(dev, OS_DEVICE_CTRL_SET_TX_TIMEOUT, &timeout);
/* 4. 写入数据(阻塞模式),返回值是实际写入字节数 */
ret = os_device_write_block(dev, 0, tx_buf, os_strlen((const char *)tx_buf));
if (ret > 0)
{
os_kprintf("Sent %d bytes successfully\r\n", ret);
}
/* 5. 读取数据(阻塞模式),返回值是实际读取字节数 */
ret = os_device_read_block(dev, 0, rx_buf, BUFFER_SIZE);
if (ret > 0)
{
os_kprintf("Received %d bytes: %s\r\n", ret, rx_buf);
}
/* 6. 关闭设备(ref_count 归零时调用 ops->deinit) */
os_device_close(dev);
}说明:示例中发送缓冲区用字符串字面量直接初始化,超时参数类型为
os_ubase_t(与os_device_control的SET_RX_TIMEOUT/SET_TX_TIMEOUT分支源码一致),确保示例与项目 API 一致、可编译运行。实际效果:成功会打印发送/接收字节数,失败会打印对应错误。
示例二:使用 os_device_open_s 一步完成查找和打开
#include <device.h>
void device_demo_simple(void)
{
os_device_t *dev;
uint8_t data = 0x55;
os_ssize_t n;
/* 一步完成查找和打开(内部先 find 再 open) */
dev = os_device_open_s("spi1");
if (dev == OS_NULL)
{
os_kprintf("Failed to find and open device!\r\n");
return;
}
/* 非阻塞写入 1 字节 */
n = os_device_write_nonblock(dev, 0, &data, 1);
os_kprintf("write result: %d\r\n", n);
/* 关闭设备 */
os_device_close(dev);
}示例三:遍历所有设备
#include <device.h>
static os_err_t print_device_info(os_device_t *dev, void *data)
{
os_kprintf("Device: %-16s Type: %d Ref: %d\r\n",
dev->name, dev->type, dev->ref_count);
return OS_EOK;
}
void list_all_devices(void)
{
os_kprintf("=== All Registered Devices ===\r\n");
os_device_for_each(print_device_info, OS_NULL);
}示例四:注册一个完整设备驱动
展示一个自定义设备从"定义结构体、实现 ops、注册"的完整驱动开发流程(与 4.1 节呼应):
#include <device.h>
#include <string.h>
/* 自定义设备扩展数据 */
struct demo_private_data
{
uint32_t reg_value;
};
static struct demo_private_data demo_priv;
/* 实现五个 ops */
static os_err_t demo_init(os_device_t *dev)
{
os_kprintf("demo init\r\n");
return OS_EOK;
}
static os_err_t demo_deinit(os_device_t *dev)
{
os_kprintf("demo deinit\r\n");
return OS_EOK;
}
static os_ssize_t demo_read(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size)
{
os_kprintf("demo read %d bytes\r\n", size);
return size;
}
static os_ssize_t demo_write(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size)
{
os_kprintf("demo write %d bytes\r\n", size);
return size;
}
static os_err_t demo_control(os_device_t *dev, int cmd, void *args)
{
return OS_EOK;
}
static const struct os_device_ops demo_ops = {
.init = demo_init,
.deinit = demo_deinit,
.read = demo_read,
.write = demo_write,
.control = demo_control,
};
/* 设备实例(可直接定义,或嵌入自定义结构体) */
static struct os_device demo_dev = {
.type = OS_DEVICE_TYPE_CHAR,
.ops = &demo_ops,
};
void demo_driver_init(void)
{
os_err_t ret = os_device_register(&demo_dev, "demo");
if (ret != OS_EOK)
{
os_kprintf("register demo failed, ret=%d\r\n", ret);
return;
}
os_kprintf("demo device registered\r\n");
}📝 本节小结
核心要点回顾
本节围绕 OneOS 设备驱动框架展开,核心要点如下:
1. 分层架构与统一抽象
- 所有设备统一抽象为
os_device_t,通过ops操作接口访问,遵循"一切皆设备"。 - 框架统一管理设备注册/注销、查找、打开/关闭、读写、控制、通知、遍历。
2. os_device_t 核心字段
name/type/status/ref_count/ops描述设备基本属性。- 三类信号量(
sem/tx_sem/rx_sem)支持互斥与阻塞/非阻塞读写同步。 list挂全局设备链表;cb_heads[2]/notify_list挂回调与通知。
3. IOCTL 命令体系
- 高 8 位设备类型 + 低 8 位命令号,
IOC_xxx(cmd)编码。 - 通用命令
OS_DEVICE_CTRL_*,其中SET_CB/RM_CB/SET_RX_TIMEOUT/SET_TX_TIMEOUT由框架处理。
4. 引用计数生命周期
- 首次打开(0→1)调用
init,最后一次关闭(1→0)调用deinit,注销要求ref_count == 0。
5. 阻塞/非阻塞读写
- 阻塞版等待
rx_sem/tx_sem(可超时);非阻塞版无数据/缓冲满立即返回 0。 - 驱动在中断里调用
os_device_recv_notify/os_device_send_notify唤醒任务。
6. 通知机制
os_device_notify广播事件;register/unregister注册/注销回调,filter可做事件过滤。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 未打开设备就 read/write | 必须先用 os_device_open,否则 OS_ASSERT(ref_count != 0) 断言失败 |
| 注册重名设备 | os_device_register 会先 os_device_find 查重,重名返回 OS_INVAL |
| 重复关闭设备 | os_device_close 在 ref_count == 0 时返回 OS_FAILURE,勿重复关闭 |
| 注册回调重复 | OS_DEVICE_CTRL_SET_CB 对相同 cb 返回 OS_BUSY,勿重复注册 |
| 未设置超时就阻塞读写 | 默认超时是 OS_TICK_MAX(永久等待),交互式设备务必先用 SET_RX_TIMEOUT/SET_TX_TIMEOUT 设置 |
| 混淆阻塞/非阻塞 | 非阻塞在无数据/缓冲满时返回 0(不等待);阻塞会等待信号量 |
| 注销被使用的设备 | _device_unregister 要求 ref_count == 0,否则断言失败;先 close 到 0 再注销 |
误用 os_device_removed | 该函数仅在 device.h 声明、device.c 未实现,注销请用 os_device_unregister |
混淆 rx_count/tx_count 与 rx_size/tx_size | rx_count==0 表示无数据可读;tx_count==tx_size 表示发送缓冲满 |
| 在中断里使用阻塞 API | 中断中只能用 os_device_recv_notify/send_notify 等非阻塞通知,不能用阻塞读写 |
下一步
设备驱动框架是所有具体设备驱动的基础。接下来请学习下一小节:
- 5.2 GPIO设备:了解如何基于设备框架实现 GPIO/PIN 设备的模式配置、电平读写与外部中断。