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卡"(优先级提升),赶紧用完让位。

Concept

Basic concept

信号量(Semaphore)是 FreeRTOS 中用于任务间同步和资源管理的机制,底层基于队列实现。

  • 二值信号量:只能取 0 或 1,本质是长度为 1 的队列。用于事件通知、同步
  • 计数信号量:可取 0~N,本质是长度为 N 的队列。用于资源计数
  • 互斥信号量:特殊的二值信号量,具有优先级继承特性,解决优先级翻转问题

Working principle

信号量操作只有两个:Give(释放)Take(获取)

  • Take 成功减 1,Give 成功加 1
  • 计数为 0 时 Take 操作可阻塞等待
  • 阻塞时高优先级任务优先解除阻塞

优先级翻转(Priority Inversion):

  • 低优先级任务持有信号量 → 高优先级任务等待信号量 → 中优先级任务抢占低优先级任务
  • 结果:高优先级任务被中优先级任务间接阻塞,优先级"倒反天罡"

优先级继承(Mutex 解决翻转):

  • 低优先级任务持有互斥信号量时,若有高优先级任务在等待,低优先级任务被临时提升到高优先级任务的优先级
  • 释放后恢复原优先级,中优先级任务无法再抢占

Core API

Function Params Description
xSemaphoreCreateBinary() 动态创建二值信号量
xSemaphoreCreateCounting() uxMaxCount, uxInitialCount 动态创建计数信号量
xSemaphoreCreateMutex() 动态创建互斥信号量
xSemaphoreGive() xSemaphore 释放信号量
xSemaphoreTake() xSemaphore, xTicksToWait 获取信号量
uxSemaphoreGetCount() xSemaphore 获取当前计数值

Comparison Table

Feature Binary Counting Mutex
Initial Value 0 User specified 1
Max Value 1 User specified 1
Priority Inheritance No No Yes
Recursive No No Configurable
Use Case Event notification Resource counting Mutual exclusion

Experiment

Experiment 1: 二值信号量 (P10)

Requirements: KEY1 释放信号量 → task2 获取信号量并打印。

File: P10/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);
        }
    }
}

Experiment 2: 计数型信号量 (P11)

Requirements: KEY1 释放计数信号量,task2 每秒获取一次并打印计数值。

File: P11/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);
    }
}

Experiment 3: 优先级翻转 (P12)

Requirements: 模拟优先级翻转场景,观察使用二值信号量时的"倒反天罡"现象。

File: P12/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);
    }
}

Experiment 4: 互斥信号量 (P13)

Requirements: 与 P12 相同实验场景,但使用互斥信号量 xSemaphoreCreateMutex() 观察优先级继承解决翻转。

File: P13/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);
    }
}

Key Config

/* FreeRTOSConfig.h */
#define configUSE_COUNTING_SEMAPHORES   1   /* 使能计数信号量 */
#define configUSE_MUTEXES               1   /* 使能互斥信号量 */
#define configUSE_RECURSIVE_MUTEXES     1   /* 使能递归互斥信号量 */

Core API Reference Table

Function Parameters Return Description
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 创建递归互斥信号量

Common Issues & Pitfalls

  1. 二值信号量初始值为 0:创建后需先 Give 一次才能被 Take。常见错误是忘记初始 Give 导致第一个 Take 永远阻塞。
  2. 互斥信号量不能在中断中使用:因为优先级继承机制依赖任务上下文。ISR 中使用 xSemaphoreGiveFromISR() 仅对二值/计数信号量有效。
  3. 优先级翻转的隐蔽性:低→中→高三优先级任务存在时才有可能出现。调试时可通过 vTaskList() 观察任务状态。
  4. 互斥信号量与二值信号量的选择:纯同步用二值信号量,保护共享资源用互斥信号量。
  5. 递归互斥信号量:同一个任务可多次 Take 同一个互斥信号量(嵌套使用),适用于递归函数中保护临界区。