tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第11~12章 + 配套代码10/11/12/13" author: "尚硅谷研究院" date: 2026-07-12 url: ""
用生活理解:信号量就像动车上的卫生间——
- 二值信号量 = 卫生间指示灯:有人(0)/没人(1)。你想用卫生间,先看灯(获取信号量),绿灯就进去,红灯就等着
- 计数信号量 = 停车场余位显示屏:显示剩余车位数。每进一辆车减1,每出一辆车加1
- 互斥信号量 = 带"优先级继承"的卫生间(优化版):如果你(高优先级)想上厕所但被同事(低优先级)占着,系统会让那个同事赶紧上完让给你
| 类型 | 创建函数 | 计数值 | 典型用途 | 可递归 |
|---|---|---|---|---|
| 二值信号量 | xSemaphoreCreateBinary() |
0 或 1 | 同步(任务间/中断与任务间) | 否 |
| 计数信号量 | xSemaphoreCreateCounting(max,init) |
0~max | 资源管理、事件计数 | 否 |
| 互斥信号量 | xSemaphoreCreateMutex() |
0 或 1 | 互斥访问(带优先级继承) | 是 |
通用操作:xSemaphoreGive() = 释放(给),xSemaphoreTake() = 获取(拿)。是所有信号量都通用的!
就像:task1 负责按铃,task2 听到铃响就开始干活。
#include "semphr.h" // 信号量头文件
SemaphoreHandle_t sem_handle; // 信号量句柄
void freertos_start(void)
{
// 参数1: 队列长度(二值信号量=1)
// 参数2: 每个消息的大小(二值信号量=0)
sem_handle = xSemaphoreCreateBinary();
xTaskCreate(/* task1 */);
xTaskCreate(/* task2 */);
vTaskStartScheduler();
}
void task1(void *pvParameters)
{
uint8_t key = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
// 释放信号量(把卫生间灯从"有人"变"没人")
xSemaphoreGive(sem_handle);
printf("信号量已释放\r\n");
}
vTaskDelay(500);
}
}
void task2(void *pvParameters)
{
while (1)
{
// 获取信号量(等卫生间变空)
// portMAX_DELAY = 死等,直到有信号量可用
xSemaphoreTake(sem_handle, portMAX_DELAY);
printf("获取信号量成功,开始干活\r\n");
}
}
二值信号量本质上就是一个长度=1、项目大小=0 的特殊队列。你不关心"消息内容"是什么,只关心"有没有消息"。
源码实现:信号量底层就是用队列实现的——
xSemaphoreCreateBinary()内部调用了xQueueCreate(1, 0)。就像火车卫生间的指示灯——红灯=有人(已获取),绿灯=空闲(已释放)。队列什么都能干,信号量能干的队列都能干,但队列能干的信号量不一定能干。
SemaphoreHandle_t count_sem;
void freertos_start(void)
{
// 创建一个计数信号量:最大100,初始0
count_sem = xSemaphoreCreateCounting(100, 0);
// 最大计数=100:停车场最多停100辆车
// 初始计数=0:停车场一开始是空的
xTaskCreate(/* task1 */);
xTaskCreate(/* task2 */);
vTaskStartScheduler();
}
void task1(void *pvParameters)
{
uint8_t key = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
xSemaphoreGive(count_sem); // 计数值+1
}
vTaskDelay(500);
}
}
void task2(void *pvParameters)
{
while (1)
{
xSemaphoreTake(count_sem, portMAX_DELAY); // 计数值-1
// 获取后查看当前计数值
UBaseType_t count = uxSemaphoreGetCount(count_sem);
printf("当前计数值: %d\r\n", count);
vTaskDelay(1000);
}
}
| 用途 | 创建参数 | 行为 |
|---|---|---|
| 事件计数 | CreateCounting(max, 0) |
Give=事件发生(+1),Take=处理事件(-1) |
| 资源管理 | CreateCounting(max, max) |
Take=占用资源(-1),Give=释放资源(+1) |
用生活理解:你在机场排队安检(你就是高优先级任务A)。你前面有个老人(低优先级任务B)正在安检,你只能等着。这时来了个 VIP(中优先级任务C),工作人员让 VIP 插队到前面先安检。结果 VIP 安检完了,老人才能继续。老人完了才轮到你。问题:一个"插队的"反而让你(最高优先级)等了最久——这就是优先级翻转。
// 三个任务优先级:Task1(2) < Task2(3) < Task3(4)
// 共享一个二值信号量
void task1(void *pvParameters) // 低优先级 Task1(2)
{
while (1)
{
xSemaphoreTake(sem_handle, portMAX_DELAY); // 拿到信号量
printf("低优先级Task1正在执行...\r\n");
HAL_Delay(3000); // 占用信号量3秒!
xSemaphoreGive(sem_handle);
vTaskDelay(1000);
}
}
void task2(void *pvParameters) // 中优先级 Task2(3)
{
while (1)
{
printf("中优先级Task2正在执行...\r\n");
HAL_Delay(1500); // 纯粹的计算任务
vTaskDelay(1000);
}
}
void task3(void *pvParameters) // 高优先级 Task3(4)
{
while (1)
{
xSemaphoreTake(sem_handle, portMAX_DELAY); // 等信号量
printf("高优先级Task3正在执行...\r\n");
HAL_Delay(1000);
xSemaphoreGive(sem_handle);
vTaskDelay(1000);
}
}
优先级翻转的本质:"倒反天罡"——低优先级任务反而阻塞了高优先级任务。中优先级任务"山中无老虎,猴子称霸王"——不受限制地运行,持续抢占持有锁的低优先级任务,导致高优先级等待时间无限延长。这是信号量的典型问题,不是所有同步机制都有。
执行流程(问题所在):
时间 →
Task1(低) 拿信号量 ──── 干了3秒 ──── 释放
Task3(高) 等着... 等着... 拿到!
Task2(中) 打断了Task1 执行完
^ 问题就在这里!
中优先级插队了,高优先级反而等最久
把 xSemaphoreCreateBinary() 换成 xSemaphoreCreateMutex(),其他代码完全一样!
// freertos_start 中只需改这一行
// sem_handle = xSemaphoreCreateBinary(); // 二值信号量:会有优先级翻转
sem_handle = xSemaphoreCreateMutex(); // 互斥信号量:自动处理优先级翻转
系统检测到"高优先级任务在等低优先级任务释放信号量"时,临时把低优先级任务的优先级提升到和高优先级一样高。这样中优先级任务就不能插队了,低优先级任务赶紧执行完释放信号量,高优先级任务就能拿到。用完就丢——资源释放后优先级再降回去。
创建时自动释放:创建互斥信号量时会自动执行一次 Give 操作,确保初始状态为"可用"。
互斥信号量下:
Task1(低) 拿信号量 ──── 干了3秒 ──── 释放
↑优先级被临时提升到4 ↑恢复原来优先级
Task3(高) 等着... 拿到!
Task2(中) 想插队但发现Task1优先级比自己高了,插不了
| 限制 | 原因 |
|---|---|
| ❌ 不能在中断中使用 | 优先级继承需要在任务上下文中运行 |
| ✅ 可以递归获取 | 同一个任务可以多次 Take,但要Give同样次数 |
code/10_二值信号量/code/11_计数型信号量/code/12_优先级翻转/code/13_互斥信号量/后续章节:[[09-事件标志组与任务通知]]