第11章 信号量Semaphore
约 3865 字大约 13 分钟
2026-01-19
FreeRTOS中的信号量是一种用于任务间同步和资源管理的机制。信号量可以是二进制的(只能取0或1)也可以是计数型的(可以是任意正整数)。信号量的基本操作包括“获取”和“释放”。
信号量底层原理是一个队列。
11.1 二进制信号量
11.1.1 核心知识点
特点:
二值信号量(Binary Semaphore)是一种特殊类型的队列,队列长度固定是1;两种状态,有数据或无数据
队列中有数据,二进制信号量值为1;队列中无数据,二进制信号量值为0应用场景:
1. 互斥访问, 确保在同一时刻只有一个任务可以访问共享资源
2. 任务之间的同步,例如一个任务等待另一个任务完成某个操作相关类型:
SemaphoreHandle_t 信号量句柄类型相关函数
xSemaphoreCreateBinary() 使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic() 使用静态方式创建二值信号量
xSemaphoreGive() 释放信号量, 底层原理是 xQueueuSend()
xSemaphoreGiveFromISR() 在中断中释放信号量
xSemaphoreTake() 获取信号量, 底层原理是 xQueueReceive()
xSemaphoreTakeFromISR() 在中断中获取信号量11.1.2 案例
① 需求
task1: 优先级1,当检测到按键 SW3 被按下时,释放二进制信号量。
task2: 优先级2,获取二进制信号量,当成功获取后打印提示信息。② 代码
App_Task.h
添加头文件包含:
#include "semphr.h"App_Task.c
#include "App_Task.h"
// 任务1 ------------------------------------
// 任务1函数的原型
void task1_callback(void *pvParameters);
// 任务1名称
#define TASK1_NAME "task1"
// 任务1堆栈大小
#define TASK1_STACK_SIZE 128
// 任务1的优先级
#define TASK1_PRIORITY 1
// 任务1的句柄
TaskHandle_t task1_handle;
// 任务2 ------------------------------------
// 任务2函数的原型
void task2_callback(void *pvParameters);
// 任务2名称
#define TASK2_NAME "task2"
// 任务2堆栈大小
#define TASK2_STACK_SIZE 128
// 任务2的优先级
#define TASK2_PRIORITY 2
// 任务2的句柄
TaskHandle_t task2_handle;
// 定义二进制信号量的句柄
SemaphoreHandle_t binary_semaphore_handle;
/**
* @brief 启动 FreeRTOS 任务管理
*
*/
void App_Task_Start(void)
{
// 进入临界区
taskENTER_CRITICAL();
// 创建任务1
xTaskCreate(task1_callback, TASK1_NAME, TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle) == pdPASS ? printf("任务1创建成功! \n") : printf("任务1穿件失败! \n");
// 创建任务2
xTaskCreate(task2_callback, TASK2_NAME, TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle) == pdPASS ? printf("任务2创建成功! \n") : printf("任务2穿件失败! \n");
// 创建二进制信号量
binary_semaphore_handle = xSemaphoreCreateBinary();
binary_semaphore_handle != NULL ? printf("二进制信号量创建成功! \n") : printf("二进制信号量创建失败! \n");
// 退出临界区
taskEXIT_CRITICAL();
// 启动任务调度器 ( vTaskStartScheduler() 后面的代码不会被执行)
printf("任务调度器启动... \n");
vTaskStartScheduler();
}
// 任务1函数的实现
void task1_callback(void *pvParameters)
{
printf("任务1启动... \n");
while (1)
{
// 检测按键
switch (Int_Key_IsDetect())
{
case 3:
// 检测到按键SW3,释放二进制信号量
printf("任务1: 检测到按键SW3, 释放二进制信号量... \n");
xSemaphoreGive(binary_semaphore_handle);
break;
default:
break;
}
}
}
// 任务2函数的实现
void task2_callback(void *pvParameters)
{
printf("任务2启动... \n");
while (1)
{
// 获取二进制信号量,如果获取不到就一直阻塞
printf("任务2: 等待获取二进制信号量... \n");
xSemaphoreTake(binary_semaphore_handle, portMAX_DELAY);
printf("任务2: 获取到二进制信号量! \n");
}
}11.2 计数型信号量
11.2.1 核心知识点
特点:
二进制信号量可以被认为是长度为 1 的队列,计数信号量也可以被认为是长度大于 1 的队列。
信号量的用户对存储在队列中的数据不感兴趣,二进制信号量关心队列是否为空,计数型信号量关心队列中有几个。应用场景:
1. 事件计数:
在这一应用场景中,事件处理程序会在每次事件发生时“释放”(give)一个信号量(使信号量计数值递增),而处理任务会在每次处理事件时“获取”(take)一个信号量(使信号量计数值递减)。因此,计数值反映了已发生事件数量与已处理事件数量之间的差值。此时,信号量的初始计数值应设置为零。
2. 资源管理:
在这一应用场景中,信号量的计数值表示当前可用的资源数量。任务需通过获取信号量来占用资源(计数值减1),当计数值降为0时表示资源耗尽;任务使用完资源后需释放信号量归还资源(计数值加1)。此时,信号量的初始计数值应等于最大计数值,表明所有资源初始时均处于空闲状态。相关宏
configUSE_COUNTING_SEMAPHORES 启用或禁用计数信号量(Counting Semaphore)功能相关类型:
SemaphoreHandle_t 信号量句柄类型相关函数
xSemaphoreCreateCounting() 使用动态方法创建计数型信号量。
xSemaphoreCreateCountingStatic() 使用静态方法创建计数型信号量
uxSemaphoreGetCount() 获取信号量的计数值
xSemaphoreGive() 计数型信号量计数值+1
xSemaphoreTake() 计数型信号量计数值-111.2.2 案例
① 需求
task1:当检测到按键 SW3 被按下时,释放计数型信号量(计数值加1);
当检测到按键 SW4 被按下时,获取计数型信号量(计数值减1);
task2:每隔一秒,打印信号量计数值。② 代码
FreeRTOSConfig.h
添加如下配置:
// 启用计数型信号量
#define configUSE_COUNTING_SEMAPHORES 1App_Task.c
#include "App_Task.h"
// 任务1 ------------------------------------
// 任务1函数的原型
void task1_callback(void *pvParameters);
// 任务1名称
#define TASK1_NAME "task1"
// 任务1堆栈大小
#define TASK1_STACK_SIZE 128
// 任务1的优先级
#define TASK1_PRIORITY 1
// 任务1的句柄
TaskHandle_t task1_handle;
// 任务2 ------------------------------------
// 任务2函数的原型
void task2_callback(void *pvParameters);
// 任务2名称
#define TASK2_NAME "task2"
// 任务2堆栈大小
#define TASK2_STACK_SIZE 128
// 任务2的优先级
#define TASK2_PRIORITY 2
// 任务2的句柄
TaskHandle_t task2_handle;
// 定义计数型信号量的句柄
SemaphoreHandle_t count_semaphore_handle;
/**
* @brief 启动 FreeRTOS 任务管理
*
*/
void App_Task_Start(void)
{
// 进入临界区
taskENTER_CRITICAL();
// 创建任务1
xTaskCreate(task1_callback, TASK1_NAME, TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle) == pdPASS ? printf("任务1创建成功! \n") : printf("任务1穿件失败! \n");
// 创建任务2
xTaskCreate(task2_callback, TASK2_NAME, TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle) == pdPASS ? printf("任务2创建成功! \n") : printf("任务2穿件失败! \n");
// 创建计数型信号量,最大计数值为10,初始计数值为0
count_semaphore_handle = xSemaphoreCreateCounting(10, 0);
count_semaphore_handle != NULL ? printf("计数型信号量创建成功! \n") : printf("计数型信号量创建失败! \n");
// 退出临界区
taskEXIT_CRITICAL();
// 启动任务调度器 ( vTaskStartScheduler() 后面的代码不会被执行)
printf("任务调度器启动... \n");
vTaskStartScheduler();
}
// 任务1函数的实现
void task1_callback(void *pvParameters)
{
printf("任务1启动... \n");
while (1)
{
// 检测按键
switch (Int_Key_IsDetect())
{
case 3:
// 检测到按键SW3,释放计数型信号量,计数值加1
printf("任务1: 检测到按键SW3, 释放计数型信号量,计数值加1... \n");
xSemaphoreGive(count_semaphore_handle) == pdTRUE ? printf("任务1: 成功释放计数型信号量,计数值+1 ! \n") : printf("任务1: 释放计数型信号量失败! \n");
break;
case 4:
// 检测到按键SW4,尝试获取计数型信号量,计数值减1
printf("任务1: 检测到按键SW4, 尝试获取计数型信号量,计数值减1... \n");
xSemaphoreTake(count_semaphore_handle, 0) == pdTRUE ? printf("任务1: 成功获取计数型信号量, 计数值-1 ! \n") : printf("任务1: 获取计数型信号量失败! \n");
break;
default:
break;
}
}
}
// 任务2函数的实现
void task2_callback(void *pvParameters)
{
printf("任务2启动... \n");
// 定义变量,用于保存计数型信号量的当前计数值
UBaseType_t count_value = 0;
while (1)
{
// 获取计数型信号量的当前计数值
count_value = uxSemaphoreGetCount(count_semaphore_handle);
printf("任务2: 计数型信号量当前计数值为: %d \n", count_value);
// 阻塞延时1s
vTaskDelay(1000);
}
}11.3 优先级翻转
11.3.1 现象分析
优先级翻转指的是一个较低优先级的任务阻塞了一个较高优先级任务的执行,从而导致高优先级任务无法及时完成。

11.3.2 案例
① 需求
创建一个二进制信号量,并主动释放
task1(taskB):低优先级任务,获取二进制信号量,获取后打印信息,延时3000ms,释放二进制信号量
task2(taskC):中等优先级任务,延时1500ms,阻塞延时100ms
task3(taskA):高优先级任务,获取二进制信号量,获取后打印信息,延时1000ms,释放二进制信号量,阻塞延时100ms② 运行效果

③ 代码
App_Task.c
#include "App_Task.h"
// 任务1 ------------------------------------
// 任务1函数的原型
void task1_callback(void *pvParameters);
// 任务1名称
#define TASK1_NAME "task1"
// 任务1堆栈大小
#define TASK1_STACK_SIZE 128
// 任务1的优先级
#define TASK1_PRIORITY 1
// 任务1的句柄
TaskHandle_t task1_handle;
// 任务2 ------------------------------------
// 任务2函数的原型
void task2_callback(void *pvParameters);
// 任务2名称
#define TASK2_NAME "task2"
// 任务2堆栈大小
#define TASK2_STACK_SIZE 128
// 任务2的优先级
#define TASK2_PRIORITY 2
// 任务2的句柄
TaskHandle_t task2_handle;
// 任务3 ------------------------------------
// 任务3函数的原型
void task3_callback(void *pvParameters);
// 任务3名称
#define TASK3_NAME "task3"
// 任务3堆栈大小
#define TASK3_STACK_SIZE 128
// 任务3的优先级
#define TASK3_PRIORITY 3
// 任务3的句柄
TaskHandle_t task3_handle;
// 定义二进制信号量的句柄
SemaphoreHandle_t binary_semaphore_handle;
/**
* @brief 启动 FreeRTOS 任务管理
*
*/
void App_Task_Start(void)
{
// 进入临界区
taskENTER_CRITICAL();
// 创建任务1
xTaskCreate(task1_callback, TASK1_NAME, TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle) == pdPASS ? printf("任务1创建成功! \n") : printf("任务1穿件失败! \n");
// 创建任务2
xTaskCreate(task2_callback, TASK2_NAME, TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle) == pdPASS ? printf("任务2创建成功! \n") : printf("任务2穿件失败! \n");
// 创建任务3
xTaskCreate(task3_callback, TASK3_NAME, TASK3_STACK_SIZE, NULL, TASK3_PRIORITY, &task3_handle) == pdPASS ? printf("任务3创建成功! \n") : printf("任务3穿件失败! \n");
// 创建二进制信号量,初始状态为未占用
binary_semaphore_handle = xSemaphoreCreateBinary();
binary_semaphore_handle != NULL ? printf("二进制信号量创建成功! \n") : printf("二进制信号量创建失败! \n");
// 主动释放二进制信号量,使其变为可用状态
printf("主动释放二进制信号量... \n");
xSemaphoreGive(binary_semaphore_handle);
// 退出临界区
taskEXIT_CRITICAL();
// 启动任务调度器 ( vTaskStartScheduler() 后面的代码不会被执行)
printf("任务调度器启动... \n");
vTaskStartScheduler();
}
// 任务1函数的实现
void task1_callback(void *pvParameters)
{
printf("任务1启动... \n");
while (1)
{
// 获取二进制信号量, 获取不到阻塞
printf("任务1: 尝试获取二进制信号量... \n");
xSemaphoreTake(binary_semaphore_handle, portMAX_DELAY);
printf("任务1: 成功获取二进制信号量! \n");
// 延时3000ms
HAL_Delay(3000);
// 释放二进制信号量
printf("任务1: 释放二进制信号量... \n");
xSemaphoreGive(binary_semaphore_handle);
}
}
// 任务2函数的实现
void task2_callback(void *pvParameters)
{
printf("任务2启动... \n");
while (1)
{
printf("任务2: 正在运行... \n");
// 延时 1500ms
HAL_Delay(1500);
// 阻塞延时100ms
vTaskDelay(100);
}
}
// 任务3函数的实现
void task3_callback(void *pvParameters)
{
printf("任务3启动... \n");
while (1)
{
// 获取二进制信号量, 获取不到阻塞
printf("任务3: 尝试获取二进制信号量... \n");
xSemaphoreTake(binary_semaphore_handle, portMAX_DELAY);
printf("任务3: 成功获取二进制信号量! \n");
// 延时1000ms
HAL_Delay(1000);
// 释放二进制信号量
printf("任务3: 释放二进制信号量... \n");
xSemaphoreGive(binary_semaphore_handle);
// 阻塞延时100ms
vTaskDelay(100);
}
}11.4 互斥信号量
11.4.1 核心知识点
概述:
互斥信号量是包含优先级继承机制的二进制信号量。二进制信号量能更好实现实现同步(任务间或任务与中断之间), 而互斥信号量有助于更好实现简单互斥(即相互排斥)。
优先级继承是一种解决实时系统中任务调度引起的优先级翻转问题的机制。在具体的任务调度中,当一个高优先级任务等待一个低优先级任务所持有的资源时,系统会提升低优先级任务的优先级,以避免高优先级任务长时间等待的情况。
只有获取(take)互斥信号量的任务才能释放(give)它,这与普通信号量不同,普通信号量任何任务都可以释放
相关宏:
configUSE_MUTEXES相关函数:
xSemaphoreCreateMutex() 使用动态方法创建互斥信号量。
xSemaphoreCreateMutexStatic() 使用静态方法创建互斥信号量。11.4.2 案例
① 需求
在前面优先级翻转实验的案例中,通过互斥信号量来解决优先级翻转问题:
二进制信号量改成互斥信号量② 代码
FreeRTOSConfig.h
添加配置项:
// 启用互斥型信号量
#define configUSE_MUTEXES 1App_Task.c
把上个案例中的二进制信号量替换成互斥型信号量即可!
#include "App_Task.h"
// 任务1 ------------------------------------
// 任务1函数的原型
void task1_callback(void *pvParameters);
// 任务1名称
#define TASK1_NAME "task1"
// 任务1堆栈大小
#define TASK1_STACK_SIZE 128
// 任务1的优先级
#define TASK1_PRIORITY 1
// 任务1的句柄
TaskHandle_t task1_handle;
// 任务2 ------------------------------------
// 任务2函数的原型
void task2_callback(void *pvParameters);
// 任务2名称
#define TASK2_NAME "task2"
// 任务2堆栈大小
#define TASK2_STACK_SIZE 128
// 任务2的优先级
#define TASK2_PRIORITY 2
// 任务2的句柄
TaskHandle_t task2_handle;
// 任务3 ------------------------------------
// 任务3函数的原型
void task3_callback(void *pvParameters);
// 任务3名称
#define TASK3_NAME "task3"
// 任务3堆栈大小
#define TASK3_STACK_SIZE 128
// 任务3的优先级
#define TASK3_PRIORITY 3
// 任务3的句柄
TaskHandle_t task3_handle;
// 定义互斥型信号量的句柄
SemaphoreHandle_t mutex_semaphore_handle;
/**
* @brief 启动 FreeRTOS 任务管理
*
*/
void App_Task_Start(void)
{
// 进入临界区
taskENTER_CRITICAL();
// 创建任务1
xTaskCreate(task1_callback, TASK1_NAME, TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle) == pdPASS ? printf("任务1创建成功! \n") : printf("任务1穿件失败! \n");
// 创建任务2
xTaskCreate(task2_callback, TASK2_NAME, TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle) == pdPASS ? printf("任务2创建成功! \n") : printf("任务2穿件失败! \n");
// 创建任务3
xTaskCreate(task3_callback, TASK3_NAME, TASK3_STACK_SIZE, NULL, TASK3_PRIORITY, &task3_handle) == pdPASS ? printf("任务3创建成功! \n") : printf("任务3穿件失败! \n");
// 创建互斥型信号量
mutex_semaphore_handle = xSemaphoreCreateMutex();
mutex_semaphore_handle != NULL ? printf("互斥型信号量创建成功! \n") : printf("互斥型信号量创建失败! \n");
// 主动释放互斥型信号量,使其变为可用状态
printf("主动释放互斥型信号量... \n");
xSemaphoreGive(mutex_semaphore_handle);
// 退出临界区
taskEXIT_CRITICAL();
// 启动任务调度器 ( vTaskStartScheduler() 后面的代码不会被执行)
printf("任务调度器启动... \n");
vTaskStartScheduler();
}
// 任务1函数的实现
void task1_callback(void *pvParameters)
{
printf("任务1启动... \n");
while (1)
{
// 获取互斥型信号量, 获取不到阻塞
printf("任务1: 尝试获取互斥型信号量... \n");
xSemaphoreTake(mutex_semaphore_handle, portMAX_DELAY);
printf("任务1: 成功获取互斥型信号量! \n");
// 延时3000ms
HAL_Delay(3000);
// 释放互斥型信号量
printf("任务1: 释放互斥型信号量... \n");
xSemaphoreGive(mutex_semaphore_handle);
}
}
// 任务2函数的实现
void task2_callback(void *pvParameters)
{
printf("任务2启动... \n");
while (1)
{
printf("任务2: 正在运行... \n");
// 延时 1500ms
HAL_Delay(1500);
// 阻塞延时100ms
vTaskDelay(100);
}
}
// 任务3函数的实现
void task3_callback(void *pvParameters)
{
printf("任务3启动... \n");
while (1)
{
// 获取互斥型信号量, 获取不到阻塞
printf("任务3: 尝试获取互斥型信号量... \n");
xSemaphoreTake(mutex_semaphore_handle, portMAX_DELAY);
printf("任务3: 成功获取互斥型信号量! \n");
// 延时1000ms
HAL_Delay(1000);
// 释放互斥型信号量
printf("任务3: 释放互斥型信号量... \n");
xSemaphoreGive(mutex_semaphore_handle);
// 阻塞延时100ms
vTaskDelay(100);
}
}