os_semaphore_create 函数详解
os_semaphore_create 函数详解
📚 本节导读
学习时长: 约 15 分钟
难度级别: ⭐⭐☆☆☆(中级)
前置知识:
- 信号量基本概念(P/V 操作)
- 链表基础知识
🎯 学习目标
- 理解信号量的两种创建方式:静态创建和动态创建
- 掌握
os_semaphore_create的参数含义和使用方法 - 理解
os_semaphore_dummy_t与os_semaphore_t的关系 - 了解信号量资源链表的注册机制
一、函数概述
1.1 函数定位
os_semaphore_create 负责创建信号量,是信号量 API 的入口函数。它支持两种内存分配方式:静态分配(用户预分配控制块)和动态分配(从堆中分配)。
1.2 基本信息
| 属性 | 值 |
|---|---|
| 函数名 | os_semaphore_create |
| 头文件 | kernel/include/os_sem.h(第 54-55 行) |
| 源文件 | kernel/source/os_sem.c(第 91-153 行) |
1.3 函数签名
os_semaphore_id
os_semaphore_create(os_semaphore_dummy_t *semaphore_cb, const char *name, uint32_t value, uint32_t max_value);1.4 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
semaphore_cb | os_semaphore_dummy_t * | 静态信号量控制块指针,传入 OS_NULL 则动态分配 |
name | const char * | 信号量名称,用于调试和识别 |
value | uint32_t | 初始计数值 |
max_value | uint32_t | 最大计数值(必须 >= value 且 > 0) |
1.5 返回值
| 返回值 | 说明 |
|---|---|
os_semaphore_id(非 NULL) | 创建成功,返回信号量标识符 |
OS_NULL | 创建失败 |
关于 os_semaphore_id
os_semaphore_id 本质是一个指针类型,指向内核中的 os_semaphore_t 结构体。用户通过这个 ID 调用后续的 os_semaphore_wait、os_semaphore_post 等操作函数。
二、完整代码
来源: kernel/source/os_sem.c(第 91-153 行)
os_semaphore_id
os_semaphore_create(os_semaphore_dummy_t *semaphore_cb, const char *name, uint32_t value, uint32_t max_value)
{
uint8_t alloc_type;
const os_semaphore_t *iter_sem;
os_semaphore_t *sem;
os_bool_t flag;
const os_list_node_t *pos;
OS_ASSERT(max_value >= value);
OS_ASSERT(max_value > 0U);
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != semaphore_cb);
#endif
flag = OS_TRUE;
if (OS_NULL != semaphore_cb)
{
sem = OS_TYPE_CONVERT(os_semaphore_t *, semaphore_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
os_spin_lock(&gs_os_semaphore_resource_list_lock);
os_list_for_each(pos, &gs_os_semaphore_resource_list_head)
{
iter_sem = os_list_entry(pos, os_semaphore_t, resource_node);
if (iter_sem == sem)
{
sem = OS_NULL;
os_spin_unlock(&gs_os_semaphore_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, SEMAPHORE_TAG, "%s", (OS_KERNEL_ERR_INFO));
flag = OS_FALSE;
break;
}
}
}
#ifdef OS_USING_HEAP
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
sem = (os_semaphore_t *)OS_KERNEL_MALLOC(sizeof(os_semaphore_t));
if (OS_NULL == sem)
{
OS_KERN_LOG(KERN_ERROR, SEMAPHORE_TAG, "Semaphore memory malloc fail");
flag = OS_FALSE;
}
}
#endif
if (OS_TRUE == flag)
{
_k_semaphore_init(sem, value, max_value, name, alloc_type);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
os_spin_lock(&gs_os_semaphore_resource_list_lock);
}
os_list_add_tail(&gs_os_semaphore_resource_list_head, &sem->resource_node);
os_spin_unlock(&gs_os_semaphore_resource_list_lock);
}
return OS_TYPE_CONVERT(os_semaphore_id, sem);
}三、执行流程总览
函数的执行流程可以用以下流程图表示:
os_semaphore_create() 被调用
↓
参数断言检查
↓
semaphore_cb == NULL ?
/ \
是 否
↓ ↓
动态分配 静态分配
(从堆中 malloc) (使用传入的控制块)
↓ ↓
分配成功? 检查是否已注册
/ \ / \
是 否 否 是
↓ ↓ ↓ ↓
初始化 返回NULL 初始化 返回NULL
↓
注册到全局资源链表
↓
返回 os_semaphore_id四、分步详解
第一步:参数断言检查
OS_ASSERT(max_value >= value);
OS_ASSERT(max_value > 0U);
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != semaphore_cb);
#endif三条断言分别检查:
| 断言 | 含义 | 失败原因 |
|---|---|---|
max_value >= value | 最大值必须 >= 初始值 | 参数逻辑错误 |
max_value > 0 | 最大值必须 > 0 | 信号量至少要有一个计数资源 |
semaphore_cb != NULL | 未启用堆时必须提供控制块 | 无堆可用,无法动态分配 |
STM32F103ZET6 实际配置
OS_USING_HEAP:已定义(oneos_config.h第 113 行)OS_USING_ASSERT:已定义(oneos_config.h第 48 行)
结论:断言检查已启用,且支持动态内存分配(第三条断言不会触发)。
第二步:静态创建分支
当 semaphore_cb 非 NULL 时,进入静态创建分支:
if (OS_NULL != semaphore_cb)
{
sem = OS_TYPE_CONVERT(os_semaphore_t *, semaphore_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
os_spin_lock(&gs_os_semaphore_resource_list_lock);
os_list_for_each(pos, &gs_os_semaphore_resource_list_head)
{
iter_sem = os_list_entry(pos, os_semaphore_t, resource_node);
if (iter_sem == sem)
{
sem = OS_NULL;
os_spin_unlock(&gs_os_semaphore_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, SEMAPHORE_TAG, "%s", (OS_KERNEL_ERR_INFO));
flag = OS_FALSE;
break;
}
}
}逐步解释:
- 类型转换:
sem = OS_TYPE_CONVERT(os_semaphore_t *, semaphore_cb);将用户传入的 os_semaphore_dummy_t * 转换为内核使用的 os_semaphore_t *。两者内存布局兼容,共享同一块内存。
- 标记分配类型:
alloc_type = OS_ALLOC_TYPE_STATIC;设置为 OS_ALLOC_TYPE_STATIC,表示这块内存是用户静态分配的。
- 检查重复注册:
os_spin_lock(&gs_os_semaphore_resource_list_lock);
os_list_for_each(pos, &gs_os_semaphore_resource_list_head)
{
iter_sem = os_list_entry(pos, os_semaphore_t, resource_node);
if (iter_sem == sem)
{
sem = OS_NULL;
os_spin_unlock(&gs_os_semaphore_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, SEMAPHORE_TAG, "%s", (OS_KERNEL_ERR_INFO));
flag = OS_FALSE;
break;
}
}逐步解释:
加锁保护链表:
os_spin_lock(&gs_os_semaphore_resource_list_lock);获取自旋锁,进入临界区。后续对链表的遍历操作在此锁保护下执行,防止其他线程同时修改链表。
遍历链表:
os_list_for_each(pos, &gs_os_semaphore_resource_list_head)os_list_for_each 是链表遍历宏,依次将链表中每个节点的 os_list_node_t 指针赋给 pos,从 head.next(第一个节点)开始,直到回到 head(遍历完毕)。
反向推导信号量结构体:
iter_sem = os_list_entry(pos, os_semaphore_t, resource_node);os_list_entry 是一个指针运算宏,根据链表节点 pos 的地址和 resource_node 在 os_semaphore_t 中的偏移量,计算出外层 os_semaphore_t 结构体的起始地址。这是因为链表节点是嵌入在信号量结构体内部的成员。
宏展开过程:
os_list_entry 定义在 kernel/include/os_list.h 第 367 行:
#define os_list_entry(ptr, type, member) os_container_of(ptr, type, member)os_container_of 定义在 kernel/include/os_types.h 第 66 行:
#define os_container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))代入实际参数后展开为:
iter_sem = ((os_semaphore_t *)((char *)(pos) - offsetof(os_semaphore_t, resource_node)));具体例子:
假设 os_semaphore_t 结构体内存布局如下(resource_node 的偏移量为 20):
os_semaphore_t(起始地址 0x20001000)
┌──────────────────────────────┐
│ parent │ 偏移 0 (os_object_t,12 字节)
├──────────────────────────────┤
│ value │ 偏移 12 (uint32_t,4 字节)
├──────────────────────────────┤
│ max_value │ 偏移 16 (uint32_t,4 字节)
├──────────────────────────────┤
│ alloc_type │ 偏移 20 (uint8_t,1 字节)
│ padding │ (3 字节对齐填充)
├──────────────────────────────┤ ← pos 指向这里(0x20001018)
│ resource_node.next │ 偏移 24 (uint32_t,4 字节)
│ resource_node.prev │ 偏移 28 (uint32_t,4 字节)
└──────────────────────────────┘ 总大小 32 字节os_list_for_each 遍历时,pos 是 os_list_node_t * 类型,它指向链表中每个节点的 resource_node。因为在注册信号量时,是将 sem->resource_node 插入链表的,所以遍历时 pos 自然就指向了 resource_node。
os_list_for_each 定义在 kernel/include/os_list.h 第 441 行:
#define os_list_for_each(pos, head) for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next)展开后等价于:
for (pos = head->next; pos != head; pos = pos->next)pos 的变化过程(以上面的两个信号量为例):
初始:pos = head->next → 指向 sem_1 的 resource_node
↓
第一次循环:pos = pos->next → 指向 sem_2 的 resource_node
↓
第二次循环:pos = pos->next → 回到 head,循环结束所以 pos 始终是 os_list_node_t * 类型,指向的是每个信号量结构体内部的 resource_node 成员,而不是信号量结构体本身。这就是为什么需要 os_list_entry 把 pos"转换回" os_semaphore_t *。
os_list_entry 的计算过程:
iter_sem = (os_semaphore_t *)((char *)pos - offsetof(os_semaphore_t, resource_node))
= (os_semaphore_t *)(0x20001018 - 20)
= (os_semaphore_t *)(0x20001004) // ← 近似于结构体起始地址
≈ 0x20001000 // ← 实际起始地址(精确值取决于对齐)简单说:链表节点 pos 在结构体内部,通过减去偏移量,就能"退回到"结构体的起始地址,从而获得整个信号量结构体的指针。
地址比较判断重复:
if (iter_sem == sem)将遍历到的信号量地址与当前要创建的信号量地址比较。如果相同,说明这个信号量控制块已经注册过了(例如用户对同一个静态控制块重复调用 os_semaphore_create)。
重复时的处理:
sem = OS_NULL; // 置空,后续不执行初始化
os_spin_unlock(&gs_os_semaphore_resource_list_lock); // 解锁,退出临界区
OS_KERN_LOG(KERN_ERROR, ...); // 打印错误日志
flag = OS_FALSE; // 标记失败
break; // 跳出遍历循环未找到重复时:
如果遍历完链表没有找到相同地址,os_spin_lock 获取的锁仍然持有(未调用 os_spin_unlock),直接进入后续的初始化和注册步骤(第四步)。锁的释放将在第四步的 os_list_add_tail 之后统一执行。
关于 gs_os_semaphore_resource_list_lock 和 gs_os_semaphore_resource_list_head
gs_os_semaphore_resource_list_lock 是一个自旋锁,gs_os_semaphore_resource_list_head 是全局信号量资源链表的头节点,两者都定义在 kernel/source/os_sem.c 第 44-45 行:
static os_list_node_t gs_os_semaphore_resource_list_head = OS_LIST_INIT(gs_os_semaphore_resource_list_head);
static OS_DEFINE_SPINLOCK(gs_os_semaphore_resource_list_lock);gs_os_semaphore_resource_list_head:编译时创建的环形双向链表,所有通过os_semaphore_create创建的信号量都挂载于此gs_os_semaphore_resource_list_lock:保护链表的自旋锁,确保多线程/多核心并发安全
为什么叫 os_semaphore_dummy_t?
os_semaphore_dummy_t 是一个占位结构体,定义在 kernel/include/os_dummy.h。它的成员名都带 _dummy 后缀,用于用户代码中静态分配内存。内核内部通过 OS_TYPE_CONVERT 类型转换将其视为 os_semaphore_t 使用,两者内存布局完全一致。
典型用法:
// 1. 静态定义控制块
OS_SEMAPHORE_DEFINE(my_sem);
// 2. 传入创建函数
os_semaphore_id id = os_semaphore_create(&my_sem, "my_sem", 1, 1);深入了解
关于自旋锁的宏展开过程、os_spinlock_t 结构体详解、链表变化图解等详细内容,请参阅 信号量资源链表机制。
第三步:动态创建分支
当 semaphore_cb 为 NULL 时,进入动态创建分支(仅在 OS_USING_HEAP 定义时编译):
#ifdef OS_USING_HEAP
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
sem = (os_semaphore_t *)OS_KERNEL_MALLOC(sizeof(os_semaphore_t));
if (OS_NULL == sem)
{
OS_KERN_LOG(KERN_ERROR, SEMAPHORE_TAG, "Semaphore memory malloc fail");
flag = OS_FALSE;
}
}
#endif逐步解释:
- 标记分配类型:
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;设置为 OS_KOBJ_ALLOC_TYPE_DYNAMIC。
- 堆分配内存:
sem = (os_semaphore_t *)OS_KERNEL_MALLOC(sizeof(os_semaphore_t));调用 OS_KERNEL_MALLOC 从内核堆中分配 sizeof(os_semaphore_t) 字节。
- 检查分配结果:
if (OS_NULL == sem)
{
OS_KERN_LOG(KERN_ERROR, SEMAPHORE_TAG, "Semaphore memory malloc fail");
flag = OS_FALSE;
}如果堆内存不足,sem 为 NULL,记录错误日志并设置 flag = OS_FALSE。
注意
动态创建的信号量需要手动调用 os_semaphore_destroy 释放内存,否则会造成内存泄漏。
第四步:初始化并注册
当 flag 为 OS_TRUE(前序步骤成功)时:
if (OS_TRUE == flag)
{
_k_semaphore_init(sem, value, max_value, name, alloc_type);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
os_spin_lock(&gs_os_semaphore_resource_list_lock);
}
os_list_add_tail(&gs_os_semaphore_resource_list_head, &sem->resource_node);
os_spin_unlock(&gs_os_semaphore_resource_list_lock);
}逐步解释:
- 初始化信号量:
_k_semaphore_init(sem, value, max_value, name, alloc_type);调用 _k_semaphore_init 设置初始值、最大值、名称和分配类型。下面详细分析这个内联函数的实现。
函数来源
_k_semaphore_init 定义在 kernel/source/os_sem.c 第 47-74 行,是一个 OS_INLINE 内联函数,仅供内核内部调用,不会暴露给用户层。
要理解这个函数,先看信号量控制块的结构体定义(kernel/source/os_prototypes.h 第 183-199 行):
struct os_semaphore
{
os_list_node_t task_list_head; /* 阻塞任务链表头 */
os_list_node_t resource_node; /* 全局资源链表节点 */
uint32_t count; /* 当前计数值 */
uint32_t max_count; /* 最大计数值 */
uint8_t object_inited; /* 对象初始化标记 */
uint8_t object_alloc_type; /* 分配类型(静态/动态) */
uint8_t wake_type; /* 唤醒策略(优先级/FIFO) */
char name[OS_NAME_MAX + 1]; /* 信号量名称 */
os_spinlock_t lock; /* SMP 自旋锁 */
};_k_semaphore_init 函数完整源码:
OS_INLINE void
_k_semaphore_init(os_semaphore_t *semaphore, uint32_t value, uint32_t max_value,
const char *name, uint8_t alloc_type)
{
os_list_init(&semaphore->task_list_head);
semaphore->count = value;
semaphore->max_count = max_value;
semaphore->wake_type = OS_SEM_WAKE_TYPE_PRIO;
semaphore->object_alloc_type = alloc_type;
semaphore->object_inited = OS_KOBJ_INITED;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_init(&semaphore->lock);
#endif
if (OS_NULL != name)
{
(void)strncpy(&semaphore->name[0], name, OS_NAME_MAX);
semaphore->name[OS_NAME_MAX] = '\0';
}
else
{
semaphore->name[0] = '\0';
}
return;
}逐字段初始化详解:
(a) 初始化阻塞任务链表:
os_list_init(&semaphore->task_list_head);将 task_list_head 初始化为空的双向循环链表。当有线程因 os_semaphore_wait 资源不足而阻塞时,会被挂到这个链表上等待唤醒。
(b) 设置计数值和上限:
semaphore->count = value;
semaphore->max_count = max_value;count:当前可用资源数。例如创建一个值为 3 的信号量,表示初始有 3 个资源可用。max_count:计数上限,防止os_semaphore_post无限制累加。当count == max_count时再 post 会返回OS_FULL。
(c) 设置唤醒策略:
semaphore->wake_type = OS_SEM_WAKE_TYPE_PRIO;默认使用优先级唤醒(值为 0x55),即多个线程阻塞时,唤醒优先级最高的线程。也可在创建后通过 os_semaphore_set_wake_type 改为 FIFO 模式(值为 0xAA)。
(d) 设置分配类型和初始化标记:
semaphore->object_alloc_type = alloc_type;
semaphore->object_inited = OS_KOBJ_INITED;object_alloc_type:记录这个信号量是静态分配还是动态分配。销毁时需要据此决定是否调用OS_KERNEL_FREE释放内存。object_inited:标记对象已初始化。后续wait、post、destroy操作都会先检查这个标记,防止使用未初始化的对象。
(e) SMP 自旋锁初始化(条件编译):
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_init(&semaphore->lock);
#endif仅在多核(SMP)且非单锁模式下,初始化信号量自身的自旋锁,保护并发访问。
(f) 拷贝名称:
if (OS_NULL != name)
{
(void)strncpy(&semaphore->name[0], name, OS_NAME_MAX);
semaphore->name[OS_NAME_MAX] = '\0';
}
else
{
semaphore->name[0] = '\0';
}安全地拷贝名称字符串,限制最大长度为 OS_NAME_MAX,并手动添加 \0 终止符防止越界。如果传入 NULL,则置空字符串。
初始化后的信号量内存布局
以 OneOS 启动流程中 device_core_init 创建设备互斥信号量为例:
dev_sem = os_semaphore_create(&dev_sem_dummy, "dev_sem", 1, 1);在 os_semaphore_create 内部,_k_semaphore_init 被调用时的实参为:
_k_semaphore_init(sem, 1, 1, "dev_sem", OS_ALLOC_TYPE_STATIC);初始化完成后,dev_sem_dummy(即 dev_sem)的内存状态如下:
dev_sem_dummy(os_semaphore_t 内存布局)
┌────────────────────────────────────┐
│ task_list_head │ ← 空链表(next/prev 都指向自身)
│ .next = &task_list_head │
│ .prev = &task_list_head │
├────────────────────────────────────┤
│ resource_node │ ← 尚未注册,后续在 os_list_add_tail 时填入
├────────────────────────────────────┤
│ count = 1 │ ← 当前可用资源数(互斥信号量)
│ max_count = 1 │ ← 最大计数值(二值信号量)
├────────────────────────────────────┤
│ object_inited = OS_KOBJ_INITED │ ← 已初始化标记
│ object_alloc_type = STATIC │ ← 静态分配(dev_sem_dummy 是静态定义的)
│ wake_type = 0x55 (PRIO) │ ← 优先级唤醒
├────────────────────────────────────┤
│ name = "dev_sem\0" │ ← 信号量名称
├────────────────────────────────────┤
│ lock = (已初始化) │ ← SMP 自旋锁
└────────────────────────────────────┘实际代码位置
dev_sem_dummy 定义在 drivers/device.c 第 34 行,通过 OS_INIT_CALL 宏注册在 OS_INIT_LEVEL_PRE_KERNEL_1 / OS_INIT_SUBLEVEL_LOW 级别执行。详见:device_core_init 函数详解
- 加锁保护:
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
os_spin_lock(&gs_os_semaphore_resource_list_lock);
}仅动态分配的信号量需要在此处加锁。静态分支在第二步已经加过锁且未解锁(因为检查通过后直接走到这里),而动态分支在此之前未持有锁。
- 注册到资源链表:
os_list_add_tail(&gs_os_semaphore_resource_list_head, &sem->resource_node);
os_spin_unlock(&gs_os_semaphore_resource_list_lock);将信号量的 resource_node 添加到全局资源链表 gs_os_semaphore_resource_list_head 的尾部,然后解锁。
以 dev_sem = os_semaphore_create(&dev_sem_dummy, "dev_sem", 1, 1) 为例,注册完成后的全局资源链表状态如下:
gs_os_semaphore_resource_list_lock(自旋锁)
gs_os_semaphore_resource_list_head dev_sem_dummy
┌──────────────────────────┐ ┌──────────────────────────┐
│ .next ──────────────────│───────→ .next ────→│ .task_list_head (空) │
│ │ │ .resource_node │
│ │ │ ├─ .next ─────────────│──→ gs_os_semaphore_resource_list_head
│ │ │ └─ .prev ─────────────│──→ gs_os_semaphore_resource_list_head
│ │ │ .count = 1 │
│ .prev ──────────────────│───────→ .prev ────→│ .max_count = 1 │
│ │ │ .name = "dev_sem" │
└──────────────────────────┘ │ .object_inited = INITED │
└──────────────────────────┘链表结构说明
os_list_add_tail 执行后,gs_os_semaphore_resource_list_head 形成一个只有一个节点的环形双向链表:
gs_os_semaphore_resource_list_head.next指向dev_sem_dummy.resource_node(唯一的节点)gs_os_semaphore_resource_list_head.prev也指向dev_sem_dummy.resource_nodedev_sem_dummy.resource_node.next和.prev都指回gs_os_semaphore_resource_list_head
如果后续再创建第二个信号量,新节点会被插入 gs_os_semaphore_resource_list_head 和 dev_sem_dummy 之间(尾插法),形成更大的环形链表。
第五步:返回结果
return OS_TYPE_CONVERT(os_semaphore_id, sem);- 创建成功:返回有效的
os_semaphore_id(非 NULL) - 创建失败:
sem为 NULL,返回OS_NULL
五、静态创建 vs 动态创建
| 特性 | 静态创建 | 动态创建 |
|---|---|---|
semaphore_cb 参数 | 传入非 NULL 指针 | 传入 OS_NULL |
| 内存来源 | 编译时静态分配 | 运行时从堆分配 |
| 配置要求 | 不依赖 OS_USING_HEAP | 需要 OS_USING_HEAP |
| 内存位置 | .data 或 .bss 段 | 堆区 |
| 生命周期 | 随程序始终存在 | 需手动调用 os_semaphore_destroy |
| 适用场景 | 内核关键对象、资源受限场景 | 用户应用层、灵活需求场景 |
| 使用示例 | OS_SEMAPHORE_DEFINE(sem);os_semaphore_create(&sem, ...) | os_semaphore_create(OS_NULL, ...) |
💡 本节总结
重点回顾
两种创建方式:
- 静态创建:传入预分配的控制块指针,不依赖堆
- 动态创建:传入 NULL,从堆中分配内存
关键流程:参数检查 → 分配内存 → 初始化 → 注册到资源链表 → 返回 ID
类型转换:
os_semaphore_dummy_t通过OS_TYPE_CONVERT转换为os_semaphore_t,两者内存布局兼容线程安全:资源链表操作使用自旋锁保护,支持 SMP 架构
📚 扩展阅读
- device_core_init 函数详解 - 了解设备框架如何使用信号量