--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第11章 信号量" author: "尚硅谷研究院" date: 2026-07-17 created: 2026-07-17 --- # 信号量机制 > **用生活理解**:信号量就像动车上的卫生间指示灯。二值信号量 = 一个卫生间,有人/无人两种状态(0/1);计数信号量 = 一排卫生间,有几个空闲就给几个"可用"标志;互斥信号量 = 带"插队保护"的卫生间——如果一个大佬(高优先级任务)在等,正占用卫生间的人会临时获得"VIP卡"(优先级提升),赶紧用完让位。 ## 概念介绍 ### 基本概念 信号量(Semaphore)是 FreeRTOS 中用于任务间同步和资源管理的机制,底层基于队列实现。 - **二值信号量**:只能取 0 或 1,本质是长度为 1 的队列。用于事件通知、同步 - **计数信号量**:可取 0~N,本质是长度为 N 的队列。用于资源计数 - **互斥信号量**:特殊的二值信号量,具有**优先级继承**特性,解决优先级翻转问题 ### 工作原理 信号量操作只有两个:**Give(释放)**和 **Take(获取)**。 - Take 成功减 1,Give 成功加 1 - 计数为 0 时 Take 操作可阻塞等待 - 阻塞时高优先级任务优先解除阻塞 **优先级翻转**(Priority Inversion): - 低优先级任务持有信号量 → 高优先级任务等待信号量 → 中优先级任务抢占低优先级任务 - 结果:高优先级任务被中优先级任务间接阻塞,优先级"倒反天罡" **优先级继承**(Mutex 解决翻转): - 低优先级任务持有互斥信号量时,若有高优先级任务在等待,低优先级任务被**临时提升**到高优先级任务的优先级 - 释放后恢复原优先级,中优先级任务无法再抢占 ### 核心API | 函数 | 参数 | 功能简述 | | ---------------------------- | ------------------------------ | ------------------ | | `xSemaphoreCreateBinary()` | 无 | 动态创建二值信号量 | | `xSemaphoreCreateCounting()` | `uxMaxCount`, `uxInitialCount` | 动态创建计数信号量 | | `xSemaphoreCreateMutex()` | 无 | 动态创建互斥信号量 | | `xSemaphoreGive()` | `xSemaphore` | 释放信号量 | | `xSemaphoreTake()` | `xSemaphore`, `xTicksToWait` | 获取信号量 | | `uxSemaphoreGetCount()` | `xSemaphore` | 获取当前计数值 | ### 对比表 | 特性 | 二值信号量 | 计数信号量 | 互斥信号量 | | ---------- | ---------- | ---------- | ---------- | | 初始值 | 0 | 用户指定 | 1 | | 最大值 | 1 | 用户指定 | 1 | | 优先级继承 | 否 | 否 | 有 | | 递归获取 | 否 | 否 | 可配置 | | 适用场景 | 事件通知 | 资源计数 | 互斥访问 | ## 实验 ### 实验1:二值信号量 (P10) **需求**:KEY1 释放信号量 → task2 获取信号量并打印。 **代码来源:**03_代码/10_二值信号量/Core/Src/freertos_demo.c ```c #include "semphr.h" /* 信号量操作头文件 */ QueueHandle_t semphore_handle; /* 二值信号量句柄 */ void FreeRTOS_Start(void) { /* 动态创建二值信号量,初始值为0 */ semphore_handle = xSemaphoreCreateBinary(); if (semphore_handle != NULL) { printf("二值信号量创建成功\r\n"); } xTaskCreate((TaskFunction_t)Start_Task, (char *)"Start_Task", (configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH, (void *)NULL, (UBaseType_t)START_TASK_PRIORITY, (TaskHandle_t *)&start_task_handler); vTaskStartScheduler(); } /* task1:KEY1按下时释放信号量 */ void Task1(void *pvParameters) { uint8_t key = 0; BaseType_t err; while (1) { key = Key_Detect(); if (key == KEY1_PRESS) { if (semphore_handle != NULL) { /* 释放(Give)二值信号量,计数值变为1 */ err = xSemaphoreGive(semphore_handle); if (err == pdPASS) { printf("信号量释放成功\r\n"); } else printf("信号量释放失败\r\n"); } } vTaskDelay(10); } } /* task2:获取信号量,永久等待 */ void Task2(void *pvParameters) { uint32_t i = 0; BaseType_t err; while (1) { /* 获取(Take)信号量,无信号时永久阻塞 */ err = xSemaphoreTake(semphore_handle, portMAX_DELAY); if (err == pdTRUE) { printf("获取信号量成功\r\n"); } else { printf("已超时%d\r\n", ++i); } } } ``` ### 实验2:计数型信号量 (P11) **Requirements**: KEY1 释放计数信号量,task2 每秒获取一次并打印计数值。 **代码来源:**03_代码/11_计数型信号量/Core/Src/freertos_demo.c ```c #include "semphr.h" QueueHandle_t count_semphore_handle; /* 计数型信号量句柄 */ void FreeRTOS_Start(void) { /* 创建计数信号量:最大100,初始0 */ count_semphore_handle = xSemaphoreCreateCounting(100, 0); if (count_semphore_handle != NULL) { printf("计数型信号量创建成功\r\n"); } xTaskCreate((TaskFunction_t)Start_Task, (char *)"Start_Task", (configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH, (void *)NULL, (UBaseType_t)START_TASK_PRIORITY, (TaskHandle_t *)&start_task_handler); vTaskStartScheduler(); } /* task1:KEY1按下释放一次计数信号量 */ void Task1(void *pvParameters) { uint8_t key = 0; while (1) { key = Key_Detect(); if (key == KEY1_PRESS) { if (count_semphore_handle != NULL) { xSemaphoreGive(count_semphore_handle); } } vTaskDelay(10); } } /* task2:每秒获取一次计数信号量,打印剩余计数值 */ void Task2(void *pvParameters) { BaseType_t err = 0; while (1) { err = xSemaphoreTake(count_semphore_handle, portMAX_DELAY); if (err == pdTRUE) { /* uxSemaphoreGetCount() 获取当前计数值 */ printf("信号量的计数值=%d\r\n", (int)uxSemaphoreGetCount(count_semphore_handle)); } vTaskDelay(1000); } } ``` ### 实验3:优先级翻转 (P12) **Requirements**: 模拟优先级翻转场景,观察使用二值信号量时的"倒反天罡"现象。 **代码来源:**03_代码/12_优先级翻转/Core/Src/freertos_demo.c ```c #include "semphr.h" QueueHandle_t semphore_handle; /* Task1 (prio 2): 低优先级 — 先获取信号量,然后HAL_Delay(3000)模拟长时间占用 */ /* Task2 (prio 3): 中优先级 — 只打印,证明它能抢占低优先级 */ /* Task3 (prio 4): 高优先级 — 等待信号量,但在低优先级释放前被中优先级"插队" */ void FreeRTOS_Start(void) { /* 创建二值信号量,并主动释放一次使其初始值为1 */ semphore_handle = xSemaphoreCreateBinary(); if (semphore_handle != NULL) { printf("二值信号量创建成功\r\n"); } xSemaphoreGive(semphore_handle); /* 初始释放一次 */ xTaskCreate((TaskFunction_t)Start_Task, (char *)"Start_Task", (configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH, (void *)NULL, (UBaseType_t)START_TASK_PRIORITY, (TaskHandle_t *)&start_task_handler); vTaskStartScheduler(); } /* 低优先级任务 Task1 (prio=2):获取信号量后占用3秒 */ void Task1(void *pvParameters) { while (1) { printf("低优先级Task1获取信号量\r\n"); xSemaphoreTake(semphore_handle, portMAX_DELAY); /* 获取信号量 */ printf("低优先级Task1正在运行\r\n"); HAL_Delay(3000); /* 模拟长时间占用资源 */ printf("低优先级Task1释放信号量\r\n"); xSemaphoreGive(semphore_handle); /* 释放信号量 */ vTaskDelay(1000); } } /* 中优先级任务 Task2 (prio=3):纯打印,抢占低优先级 */ void Task2(void *pvParameters) { while (1) { printf("中优先级的Task2正在执行\r\n"); HAL_Delay(1500); printf("Task2 执行完成一次.....\r\n"); vTaskDelay(1000); } } /* 高优先级任务 Task3 (prio=4):等待信号量,被中优先级"插队"阻塞 */ void Task3(void *pvParameters) { while (1) { printf("高优先级Task3获取信号量\r\n"); xSemaphoreTake(semphore_handle, portMAX_DELAY); /* 等待信号量 */ printf("高优先级Task3正在运行\r\n"); HAL_Delay(1000); printf("高优先级Task3释放信号量\r\n"); xSemaphoreGive(semphore_handle); /* 释放信号量 */ vTaskDelay(1000); } } ``` ### 实验4:互斥信号量 (P13) **Requirements**: 与 P12 相同实验场景,但使用互斥信号量 `xSemaphoreCreateMutex()` 观察优先级继承解决翻转。 **代码来源:**03_代码/13_互斥信号量/Core/Src/freertos_demo.c ```c #include "semphr.h" QueueHandle_t mutex_semphore_handle; /* 互斥信号量句柄 */ void FreeRTOS_Start(void) { /* 创建互斥信号量(已自动释放一次,初始值为1) */ mutex_semphore_handle = xSemaphoreCreateMutex(); if (mutex_semphore_handle != NULL) { printf("互斥信号量创建成功\r\n"); } xTaskCreate((TaskFunction_t)Start_Task, (char *)"Start_Task", (configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH, (void *)NULL, (UBaseType_t)START_TASK_PRIORITY, (TaskHandle_t *)&start_task_handler); vTaskStartScheduler(); } /* 低优先级任务:使用互斥信号量,优先级被临时提升 */ void Task1(void *pvParameters) { while (1) { printf("低优先级Task1获取信号量\r\n"); xSemaphoreTake(mutex_semphore_handle, portMAX_DELAY); printf("低优先级Task1正在运行\r\n"); HAL_Delay(3000); /* 此时若高优先级等待,Task1优先级被提升到与Task3相同 */ printf("低优先级Task1释放信号量\r\n"); xSemaphoreGive(mutex_semphore_handle); vTaskDelay(1000); } } /* 中优先级任务:由于低优先级已被提升,无法抢占 */ void Task2(void *pvParameters) { while (1) { printf("中优先级的Task2正在执行\r\n"); HAL_Delay(1500); printf("Task2 for循环完毕.....\r\n"); vTaskDelay(1000); } } /* 高优先级任务:等待互斥信号量 */ void Task3(void *pvParameters) { while (1) { printf("高优先级Task3获取信号量\r\n"); xSemaphoreTake(mutex_semphore_handle, portMAX_DELAY); printf("高优先级Task3正在运行\r\n"); HAL_Delay(1000); printf("高优先级Task3释放信号量\r\n"); xSemaphoreGive(mutex_semphore_handle); vTaskDelay(1000); } } ``` ### 关键配置 ```c /* FreeRTOSConfig.h */ #define configUSE_COUNTING_SEMAPHORES 1 /* 使能计数信号量 */ #define configUSE_MUTEXES 1 /* 使能互斥信号量 */ #define configUSE_RECURSIVE_MUTEXES 1 /* 使能递归互斥信号量 */ ``` ## 核心函数速查表 | 函数 | 参数 | 返回值 | 功能简述 | | ------------------------------------------------------------------------- | ------------------ | -------------- | ----------------------------------- | | `SemaphoreHandle_t xSemaphoreCreateBinary()` | 无 | NULL=失败 | 创建二值信号量(初始0) | | `SemaphoreHandle_t xSemaphoreCreateCounting(uxMaxCount, uxInitialCount)` | 最大计数值, 初始值 | NULL=失败 | 创建计数信号量 | | `SemaphoreHandle_t xSemaphoreCreateMutex()` | 无 | NULL=失败 | 创建互斥信号量(初始1,含优先级继承) | | `BaseType_t xSemaphoreGive(xSemaphore)` | 信号量句柄 | pdPASS/pdFALSE | 释放信号量(+1) | | `BaseType_t xSemaphoreTake(xSemaphore, xTicksToWait)` | 信号量句柄, 超时 | pdPASS/pdFALSE | 获取信号量(-1) | | `BaseType_t xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)` | 同上+唤醒标记 | pdPASS/pdFALSE | 中断中释放 | | `UBaseType_t uxSemaphoreGetCount(xSemaphore)` | 信号量句柄 | 当前计数值 | 查询当前计数 | | `BaseType_t xSemaphoreCreateRecursiveMutex()` | 无 | 句柄/NULL | 创建递归互斥信号量 | ## 常见问题与避坑 1. **二值信号量初始值为 0**:创建后需先 Give 一次才能被 Take。常见错误是忘记初始 Give 导致第一个 Take 永远阻塞。 2. **互斥信号量不能在中断中使用**:因为优先级继承机制依赖任务上下文。ISR 中使用 `xSemaphoreGiveFromISR()` 仅对二值/计数信号量有效。 3. **优先级翻转的隐蔽性**:低→中→高三优先级任务存在时才有可能出现。调试时可通过 `vTaskList()` 观察任务状态。 4. **互斥信号量与二值信号量的选择**:纯同步用二值信号量,保护共享资源用互斥信号量。 5. **递归互斥信号量**:同一个任务可多次 Take 同一个互斥信号量(嵌套使用),适用于递归函数中保护临界区。