11-信号量机制.md 13 KB


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

#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

#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

#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

#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);
    }
}

关键配置

/* 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 同一个互斥信号量(嵌套使用),适用于递归函数中保护临界区。