信号量
2026/7/16大约 5 分钟内核组件内核同步信号量
信号量
📚 本节导读
学习时长: 约 25 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 任务管理基础、C 语言
信号量(Semaphore)是操作系统中最经典的同步原语之一。OneOS 提供了计数信号量,用于资源管理和任务同步。它是理解更复杂 IPC 机制的基础。
🎯 学习目标
- 理解信号量的核心概念:计数器 + 等待队列
- 掌握 P 操作(wait)和 V 操作(post)的含义
- 能够使用二值信号量实现任务同步
- 能够使用计数信号量实现资源管理
- 理解 PRIO 和 FIFO 唤醒策略的区别
一、信号量概念
1.1 什么是信号量
信号量是一个非负整数计数器,配合一个任务等待队列。它提供两个原子操作:
┌───────────────────────────────────────────────────────────┐
│ 信号量模型 │
│ │
│ ┌──────────┐ P 操作 (wait) ┌──────────┐ │
│ │ 任务 A │ ───────────────────→ │ │ │
│ └──────────┘ 计数器 - 1 │ 信号量 │ │
│ 如果计数 < 0 │ ┌────┐ │ │
│ 任务阻塞等待 │ │计数│ │ │
│ │ └────┘ │ │
│ ┌──────────┐ V 操作 (post) │ ┌────┐ │ │
│ │ 任务 B │ ───────────────────→ │ │队列│ │ │
│ └──────────┘ 计数器 + 1 │ └────┘ │ │
│ 唤醒等待任务 │ │ │
│ └──────────┘ │
└───────────────────────────────────────────────────────────┘- P 操作(wait):尝试获取信号量。如果计数 > 0,计数减 1 并立即返回;如果计数 = 0,任务进入等待队列阻塞。
- V 操作(post):释放信号量。计数加 1,如果有任务正在等待,唤醒其中一个。
1.2 信号量的两种用途
| 用途 | 初始值 | 最大值 | 典型场景 |
|---|---|---|---|
| 二值信号量 | 0 | 1 | 任务同步、"就绪"通知 |
| 计数信号量 | N | N | 管理 N 个相同资源(缓冲区槽位、外设通道) |
1.3 信号量最大值
OneOS 信号量的最大值定义在 os_sem.h 中:
#define OS_SEM_MAX_VALUE OS_UINT32_MAX /* 最大值为 uint32_t 的最大值 */二、API 详解
以下 API 定义在 kernel/include/os_sem.h 中,使用时需包含该头文件:
#include <os_sem.h>注意:使用信号量需要在
oneos_config.h中启用OS_USING_SEMAPHORE宏。
2.1 静态定义
#define OS_SEMAPHORE_DEFINE(name) os_semaphore_dummy_t name在编译时定义一个信号量控制块,用于静态创建。name 为变量名。
2.2 创建信号量
os_semaphore_id os_semaphore_create(os_semaphore_dummy_t *semaphore_cb,
const char *name,
uint32_t value,
uint32_t max_value);| 参数 | 说明 |
|---|---|
semaphore_cb | 信号量控制块指针。静态创建时传入 OS_SEMAPHORE_DEFINE 定义的变量地址;动态创建时传 OS_NULL |
name | 信号量名称,用于调试和诊断 |
value | 信号量初始计数值 |
max_value | 信号量最大计数值 |
| 返回值 | 成功返回信号量 ID,失败返回 OS_NULL |
2.3 销毁信号量
os_err_t os_semaphore_destroy(os_semaphore_id semaphore_id);销毁信号量,释放其占用的内核资源。如果有任务正在等待该信号量,它们将被唤醒并收到错误码。
2.4 P 操作(等待)
os_err_t os_semaphore_wait(os_semaphore_id semaphore_id, os_tick_t timeout);| 参数 | 说明 |
|---|---|
semaphore_id | 信号量 ID |
timeout | 超时时间(tick 数)。OS_NO_WAIT(0)表示不等待;OS_WAIT_FOREVER 表示永久等待 |
| 返回值 | OS_EOK 成功;OS_ETIMEOUT 超时;OS_ERROR 错误 |
2.5 V 操作(释放)
os_err_t os_semaphore_post(os_semaphore_id semaphore_id);释放信号量。可以在中断上下文中调用。
2.6 查询函数
os_err_t os_semaphore_get_value(os_semaphore_id semaphore_id, uint32_t *count);
os_err_t os_semaphore_get_max_value(os_semaphore_id semaphore_id, uint32_t *max_count);
os_bool_t os_semaphore_is_exist(os_semaphore_id semaphore_id);
const char *os_semaphore_get_name(os_semaphore_id semaphore_id);| 函数 | 说明 |
|---|---|
os_semaphore_get_value | 获取当前信号量计数值 |
os_semaphore_get_max_value | 获取信号量最大计数值 |
os_semaphore_is_exist | 检查信号量是否存在 |
os_semaphore_get_name | 获取信号量名称 |
2.7 唤醒策略
os_err_t os_semaphore_set_wake_type(os_semaphore_id semaphore_id, uint8_t wake_type);| 唤醒类型 | 宏 | 含义 |
|---|---|---|
| 优先级唤醒 | OS_SEM_WAKE_TYPE_PRIO (0x55) | 唤醒等待队列中优先级最高的任务 |
| FIFO 唤醒 | OS_SEM_WAKE_TYPE_FIFO (0xAA) | 唤醒等待队列中等待时间最长的任务 |
三、代码示例
3.1 二值信号量:任务同步
#include <oneos_config.h>
#include <os_task.h>
#include <os_sem.h>
/* 静态定义信号量控制块 */
OS_SEMAPHORE_DEFINE(g_sync_sem);
/* 任务 A:等待信号量 */
void task_a(void *parameter)
{
os_semaphore_id sem_id;
/* 静态创建二值信号量:初始值 0,最大值 1 */
sem_id = os_semaphore_create(&g_sync_sem, "sync_sem", 0, 1);
while (1)
{
/* 等待信号量,永久等待 */
os_semaphore_wait(sem_id, OS_WAIT_FOREVER);
/* 收到信号量,执行同步操作 */
printf("Task A: synchronized!\n");
}
}
/* 任务 B:释放信号量 */
void task_b(void *parameter)
{
os_semaphore_id sem_id = (os_semaphore_id)parameter;
while (1)
{
os_task_sleep(1000); /* 模拟一些工作 */
/* 释放信号量,通知任务 A */
os_semaphore_post(sem_id);
printf("Task B: signal sent\n");
}
}3.2 计数信号量:资源管理
#include <oneos_config.h>
#include <os_task.h>
#include <os_sem.h>
#define BUFFER_SLOT_COUNT 5
OS_SEMAPHORE_DEFINE(g_buffer_sem);
/* 生产者任务 */
void producer_task(void *parameter)
{
os_semaphore_id sem_id;
/* 动态创建计数信号量:初始值 5,最大值 5(5 个缓冲区槽位) */
sem_id = os_semaphore_create(OS_NULL, "buffer_sem", BUFFER_SLOT_COUNT, BUFFER_SLOT_COUNT);
while (1)
{
/* 等待一个空闲槽位 */
os_semaphore_wait(sem_id, OS_WAIT_FOREVER);
/* 获取到一个槽位,写入数据 */
printf("Producer: got a buffer slot\n");
}
}
/* 消费者任务 */
void consumer_task(void *parameter)
{
os_semaphore_id sem_id = (os_semaphore_id)parameter;
while (1)
{
os_task_sleep(2000); /* 消费数据 */
/* 释放一个槽位 */
os_semaphore_post(sem_id);
printf("Consumer: freed a buffer slot\n");
}
}3.3 中断与任务同步
OS_SEMAPHORE_DEFINE(g_irq_sem);
/* 中断服务程序 */
void uart_rx_irq_handler(void)
{
/* 接收数据... */
/* 在中断中释放信号量,通知任务处理 */
os_semaphore_post(g_irq_sem_id);
}
/* 任务中等待中断通知 */
void uart_rx_task(void *parameter)
{
os_semaphore_id sem_id = os_semaphore_create(&g_irq_sem, "irq_sem", 0, 1);
while (1)
{
/* 等待中断通知 */
os_err_t ret = os_semaphore_wait(sem_id, OS_WAIT_FOREVER);
if (ret == OS_EOK)
{
/* 处理接收到的数据 */
process_uart_data();
}
}
}3.4 设置唤醒策略
void set_sem_wake_type_example(void)
{
os_semaphore_id sem_id;
uint32_t count;
sem_id = os_semaphore_create(OS_NULL, "wake_sem", 0, 10);
/* 设置为 FIFO 唤醒(先等待的先被唤醒) */
os_semaphore_set_wake_type(sem_id, OS_SEM_WAKE_TYPE_FIFO);
/* 查询当前计数值 */
os_semaphore_get_value(sem_id, &count);
printf("Semaphore count: %u\n", count);
}📝 本节小结
信号量是 OneOS 中最基础的同步机制,核心要点:
- 计数信号量维护一个计数器,P 操作减 1,V 操作加 1
- 二值信号量(max_value = 1)用于任务间同步
- 计数信号量(max_value = N)用于管理 N 个相同资源
os_semaphore_post可以在中断中调用,是最常用的中断-任务同步方式- 唤醒策略支持 PRIO(按优先级)和 FIFO(按等待时间)两种模式