FreeRTOS信号量

1 信号量

1.1 概念

信号量可理解为一种资源计数器,常用于以下场景:

复制代码
同步任务:一个任务等待另一个任务完成某操作后再继续执行。
互斥访问:确保同一时间只有一个任务访问共享资源(如变量、外设、内存区等)。

比喻理解:停车场模型

复制代码
空车位:信号量的计数值,表示可用资源数。
停车(占用车位):任务获取信号量,计数值减1。
驶离(让出车位):任务释放信号量,计数值加1。
无空车位:任务等待(阻塞)直到有资源释放。

1.2 队列与信号量对比

1.目的不同

复制代码
信号量的核心是状态或资源计数。
它是一个计数器,用于管理对共享资源的访问权或标识某个事件是否发生。
信号量传递的是一种权限或通知,而不是具体的数据内容。
可以把它理解为钥匙或通行证。


队列的核心是数据传递。
它是一个用于在任务之间或任务与中断之间安全传递数据的通道。
队列传递的是具有实际意义的消息(数据包、命令、结构体等)。
可以把它理解为管道或信箱。

2.关键行为差异

复制代码
数据承载
信号量:不承载任何用户数据。xSemaphoreGive 和 xSemaphoreTake 操作不涉及数据的拷贝。
队列:每次操作都涉及数据的拷贝。xQueueSend 会将数据复制到队列存储区,xQueueReceive 会从存储区复制数据出来。


发送/释放的阻塞行为
信号量:xSemaphoreGive 操作几乎从不阻塞(除非是某些特殊类型的信号量且队列已满,对于二值和计数信号量,通常不阻塞)。
队列:xQueueSend 操作可能会阻塞。如果队列已满,发送任务可以选择等待,直到队列有空间。


所有权
信号量:通常没有发送者和接收者的固定关系。任何一个任务都可以释放一个信号量,而任何另一个等待该信号量的任务都可以获取它。它是一种广播或匿名的通知机制。
队列:具有明确的生产者-消费者关系。数据从一端进入,从另一端取出。虽然多个任务可以向同一个队列发送或接收,但每个数据项只被一个消费者取走。


中断服务程序(ISR)中的使用
两者都可以在中断中使用(使用 FromISR 结尾的API函数)。
关键区别:互斥信号量由于其优先级继承机制与任务相关,因此绝对不能在中断中使用。而普通信号量和队列则可以在中断中安全地释放或发送。

2 二值信号量

2.1 概念

2.1.1 核心特性

复制代码
计数值只有两种状态:0(无效)或1(有效)
基于队列实现:本质是一个队列长度为1的队列
主要用于任务同步:通知事件发生
可能引起优先级翻转:不适用于严格互斥访问

2.1.2 工作状态

复制代码
初始状态(创建后):通常为0(无效)
释放后:变为1(有效)
获取后:变为0(无效)
再次释放:如果当前为0则变为1,如果当前已为1则释放失败

2.1.3 典型应用场景

复制代码
中断与任务同步
任务间简单事件通知
多任务启动顺序控制

2.2 相关函数

2.2.1 xSemaphoreCreateBinary()

复制代码
SemaphoreHandle_t xSemaphoreCreateBinary(void);
功能:动态创建二值信号量,FreeRTOS自动分配内存。
返回值:
    成功:返回信号量句柄
    失败:返回NULL(内存不足)
初始状态:创建后信号量为无效状态(计数值为0)

2.2.2 xSemaphoreCreateBinaryStatic()

复制代码
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);
功能:静态创建二值信号量,用户提供内存空间。
参数:
    pxSemaphoreBuffer:指向StaticSemaphore_t类型变量的指针,用于存储信号量结构
返回值:
    成功:返回信号量句柄
    失败:返回NULL(参数为NULL)
初始状态:创建后信号量为无效状态

2.2.3 xSemaphoreGive()

复制代码
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
功能:在任务中释放二值信号量,使计数值从0变为1。
参数:
    xSemaphore:信号量句柄
返回值:
    pdPASS:释放成功
    pdFAIL:释放失败(信号量已为1,或参数无效)
工作逻辑:
    检查信号量当前状态
    如果为0,则设置为1,唤醒等待任务
    如果为1,则返回失败(因为二值信号量不能超过1)

2.2.4 xSemaphoreGiveFromISR()

复制代码
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, 
                                BaseType_t *pxHigherPriorityTaskWoken);
功能:在中断服务程序中释放二值信号量。
参数:
    xSemaphore:信号量句柄
    pxHigherPriorityTaskWoken:用于通知是否有更高优先级任务被唤醒
        初始值设为pdFALSE
        如果释放操作唤醒了更高优先级的任务,会被设为pdTRUE
返回值:
    pdPASS:释放成功
    pdFAIL:释放失败

2.2.5 xSemaphoreTake()

复制代码
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
功能:在任务中获取二值信号量,使计数值从1变为0。
参数:
    xSemaphore:信号量句柄
    xTicksToWait:等待超时时间
        0:立即返回,不等待
        portMAX_DELAY:无限等待(需配置INCLUDE_vTaskSuspend为1)
        具体节拍数:等待指定时间
返回值:
    pdTRUE:获取成功
    pdFALSE:获取失败(超时或信号量无效)
工作逻辑:
    检查信号量状态
    如果为1:立即获取,计数值置0,返回成功
    如果为0:根据等待时间决定是否阻塞
    阻塞时:任务进入阻塞态,等待信号量释放
    超时:返回失败

2.2.6 xSemaphoreTakeFromISR()

复制代码
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, 
                                BaseType_t *pxHigherPriorityTaskWoken);
功能:在中断服务程序中获取二值信号量(使用较少)。
参数:
    xSemaphore:信号量句柄
    pxHigherPriorityTaskWoken:同xSemaphoreGiveFromISR
返回值:
    pdTRUE:获取成功
    pdFALSE:获取失败(信号量为0)
注意:中断中获取信号量通常不推荐,因为中断应该尽可能短。常见做法是在中断中释放信号量,在任务中获取。

2.3 示例实验

复制代码
/* 二值信号量句柄,全局变量 */
QueueHandle_t semphore_handle;

/* FreeRTOS 演示任务入口函数 */
void freertos_demo(void)
{    
    /* 1. 创建二值信号量 */
    semphore_handle = xSemaphoreCreateBinary();
    
    /* 2. 检查信号量是否创建成功 */
    if(semphore_handle != NULL)
    {
        printf("二值信号量创建成功\r\n");
    }
    
    /* 3. 创建启动任务(start_task) */
    xTaskCreate((TaskFunction_t         )   start_task,           // 任务函数指针
                (char *                 )   "start_task",         // 任务名称
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,// 任务堆栈大小
                (void *                 )   NULL,                 // 任务参数
                (UBaseType_t            )   START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t *         )   &start_task_handler );// 任务句柄指针
    
    /* 4. 启动FreeRTOS调度器 */
    vTaskStartScheduler();
}

/* 启动任务:负责创建其他三个任务 */
void start_task( void * pvParameters )
{
    /* 进入临界区,防止任务创建过程被中断打断 */
    taskENTER_CRITICAL();
    
    /* 创建任务1:按键扫描与信号量释放任务 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    /* 创建任务2:信号量获取与处理任务 */
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
                
    /* 创建任务3:备用任务(代码中未具体实现) */
    xTaskCreate((TaskFunction_t         )   task3,
                (char *                 )   "task3",
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &task3_handler );
    
    /* 5. 删除启动任务自身(任务创建完成后不再需要) */
    vTaskDelete(NULL);
    
    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/* 任务1:按键扫描与信号量释放任务 */
void task1( void * pvParameters )
{
    uint8_t key = 0;
    BaseType_t err;
    
    while(1)
    {
        /* 扫描按键(0表示不支持连续按键) */
        key = key_scan(0);
        
        /* 如果检测到KEY0被按下 */
        if(key == KEY0_PRES)
        {
            /* 检查信号量句柄是否有效 */
            if(semphore_handle != NULL)
            {
                /* 释放二值信号量 */
                err = xSemaphoreGive(semphore_handle);
                
                /* 检查释放结果 */
                if(err == pdPASS)
                {
                    printf("二值信号量释放成功\r\n");
                }
                else 
                {
                    printf("二值信号量释放失败\r\n");
                }
            }
        }
        
        /* 延时500个系统节拍(通常为500ms) */
        vTaskDelay(500);
    }
}

/* 任务2:信号量获取与处理任务 */
void task2( void * pvParameters )
{
    uint32_t i = 0;      // 失败计数器
    BaseType_t err;
    
    while(1)
    {
        /* 尝试获取二值信号量,portMAX_DELAY表示无限等待直到获取成功 */
        err = xSemaphoreTake(semphore_handle, portMAX_DELAY);
        
        /* 检查获取结果 */
        if(err == pdTRUE)
        {
            printf("二值信号量获取成功\r\n");
        }
        else
        {
            /* 理论上不会执行到这里,因为使用了无限等待 */
            printf("二值信号量获取失败,失败次数:%d\r\n", ++i);
        }
    }
}

**实现功能:**系统启动时创建一个二值信号量,然后建立三个任务:启动任务(负责创建其他任务)、任务1(按键扫描)和任务2(信号量等待处理)。当用户按下KEY0按键时,任务1会释放信号量;任务2则一直等待该信号量,一旦获取到就打印提示信息。

3 计数型信号量

3.1 概念

3.1.1 核心特性

复制代码
计数值可大于1:表示可用资源的数量
资源池管理:适合管理有限数量的同类资源
事件计数:记录事件发生的次数
无数据传递:只计数,不传递数据

3.1.2 工作状态

复制代码
创建时:指定最大计数值和初始计数值
释放(Give):计数值+1(不超过最大值)
获取(Take):计数值-1(不小于0)
计数值>0:有可用资源
计数值=0:无可用资源

3.1.3 典型应用场景

复制代码
资源池管理:UART端口池、内存块池
事件计数:记录中断发生次数
生产者-消费者:缓冲区空/满状态管理
限流控制:限制同时执行的任务数量

3.2 相关函数

3.2.1 xSemaphoreCreateCounting()

复制代码
SemaphoreHandle_t xSemaphoreCreateCounting(
    UBaseType_t uxMaxCount,      // 最大计数值
    UBaseType_t uxInitialCount   // 初始计数值
);
功能:动态创建计数型信号量,FreeRTOS自动分配内存。
参数:
    uxMaxCount:信号量能达到的最大值(资源总数)
    uxInitialCount:创建时的初始计数值(初始可用资源数)
返回值:
    成功:返回信号量句柄
    失败:返回NULL(内存不足)
关键特性:
    计数值范围:0 ≤ 计数值 ≤ uxMaxCount
    初始值可以设为0(表示无资源)或任意≤最大值的数
    如果初始值=最大值,表示所有资源初始可用

3.2.2 xSemaphoreCreateCountingStatic()

复制代码
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
    UBaseType_t uxMaxCount,           // 最大计数值
    UBaseType_t uxInitialCount,       // 初始计数值
    StaticSemaphore_t *pxSemaphoreBuffer  // 静态内存缓冲区
);
功能:静态创建计数型信号量,使用用户提供的静态内存。
参数:
    uxMaxCount:最大计数值
    uxInitialCount:初始计数值
    pxSemaphoreBuffer:指向StaticSemaphore_t类型变量的指针
返回值:
    成功:返回信号量句柄
    失败:返回NULL(参数无效)

3.2.3 uxSemaphoreGetCount()

复制代码
UBaseType_t uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore);
功能:获取计数型信号量当前的计数值。
参数:
    xSemaphore:信号量句柄
返回值:
    当前信号量的计数值(UBaseType_t类型)
注意:
    这是一个宏定义,实际上调用的是uxQueueMessagesWaiting()
    返回值表示当前可用的资源数量
    可用于调试、监控资源使用情况

3.3 示例实验

复制代码
/* 计数型信号量句柄,全局变量 */
QueueHandle_t Count_semphore_handle;

/* FreeRTOS 演示任务入口函数 */
void freertos_demo(void)
{    
    /* 1. 创建计数型信号量:最大计数值100,初始计数值0 */
    Count_semphore_handle = xSemaphoreCreateCounting(100, 0);
    
    /* 2. 检查信号量是否创建成功 */
    if(Count_semphore_handle != NULL)
    {
        printf("信号量创建成功\r\n");
    }
    
    /* 3. 创建启动任务(start_task) */
    xTaskCreate((TaskFunction_t         )   start_task,           // 任务函数指针
                (char *                 )   "start_task",         // 任务名称
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,// 任务堆栈大小
                (void *                 )   NULL,                 // 任务参数
                (UBaseType_t            )   START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t *         )   &start_task_handler );// 任务句柄指针
    
    /* 4. 启动FreeRTOS调度器 */
    vTaskStartScheduler();
}

/* 启动任务:负责创建其他三个任务 */
void start_task( void * pvParameters )
{
    /* 进入临界区,防止任务创建过程被中断打断 */
    taskENTER_CRITICAL();
    
    /* 创建任务1:按键扫描与信号量释放任务 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    /* 创建任务2:信号量获取与处理任务 */
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
                
    /* 创建任务3:备用任务(代码中未具体实现) */
    xTaskCreate((TaskFunction_t         )   task3,
                (char *                 )   "task3",
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &task3_handler );
    
    /* 5. 删除启动任务自身(任务创建完成后不再需要) */
    vTaskDelete(NULL);
    
    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/* 任务1:按键扫描与信号量释放任务 */
void task1( void * pvParameters )
{
    uint8_t key = 0;
    BaseType_t err;
    
    while(1)
    {
        /* 扫描按键(0表示不支持连续按键) */
        key = key_scan(0);
        
        /* 如果检测到KEY0被按下 */
        if(key == KEY0_PRES)
        {
            /* 检查信号量句柄是否有效 */
            if(Count_semphore_handle != NULL)
            {
                /* 释放计数型信号量(计数值+1) */
                err = xSemaphoreGive(Count_semphore_handle);
                
                /* 检查释放结果 */
                if(err == pdPASS)
                {
                    printf("二值信号量释放成功\r\n");  // 注意:这里打印有误,应为"计数型信号量"
                }
                else 
                {
                    printf("二值信号量释放失败\r\n");  // 注意:这里打印有误,应为"计数型信号量"
                }
            }
        }
        
        /* 延时500个系统节拍(通常为500ms) */
        vTaskDelay(500);
    }
}

/* 任务2:信号量获取与处理任务 */
void task2( void * pvParameters )
{
    BaseType_t err;
    
    while(1)
    {
        /* 尝试获取计数型信号量,portMAX_DELAY表示无限等待直到获取成功 */
        err = xSemaphoreTake(Count_semphore_handle, portMAX_DELAY);
        
        /* 检查获取结果 */
        if(err == pdTRUE)
        {
            /* 获取成功后,打印当前信号量计数值 */
            printf("信号量获取成功%d\r\n", (int)uxSemaphoreGetCount(Count_semphore_handle));
        }        
    }
}

**实现功能:**系统启动时创建了一个最大计数值为100、初始值为0的计数型信号量,随后建立三个任务:启动任务(负责创建其他任务后自删除)、任务1(按键扫描)和任务2(信号量获取与计数显示)。当用户按下KEY0按键时,任务1会释放信号量(计数值+1);任务2则一直等待获取信号量,一旦成功获取(计数值-1),就打印当前信号量的剩余计数值。这样系统实现了对按键事件次数的计数和显示,每次按键都会增加计数值,任务2每处理一个事件就减少计数值并显示当前未处理的事件数。

4 优先级翻转

4.1 概念

4.1.1 定义

优先级翻转(Priority Inversion)是实时操作系统中一个经典问题,指高优先级任务被低优先级任务阻塞,导致低优先级任务或中等优先级任务先于高优先级任务执行的现象。

4.1.2 核心矛盾

在实时系统中,任务通常按优先级进行调度,但当多个任务共享资源(如信号量保护的临界区)时,就可能出现优先级翻转,破坏系统的实时性保证。

4.1.3 问题严重性

在硬实时系统中,优先级翻转可能导致:

复制代码
任务截止时间错过(Deadline Miss)
系统响应时间不可预测
可能导致系统故障或安全风险

4.2 示例实验

复制代码
/* 二值信号量句柄,全局变量,用于任务间同步 */
QueueHandle_t semphore_handle;

/* FreeRTOS 演示任务入口函数 */
void freertos_demo(void)
{    
    /* 1. 创建二值信号量 */
    semphore_handle = xSemaphoreCreateBinary();
    
    /* 2. 检查信号量是否创建成功 */
    if(semphore_handle != NULL)
    {
        printf("二值信号量创建成功\r\n");
    }
    
    /* 3. 立即释放一次信号量,使得初始状态为"有效",以便后续任务可以获取 */
    xSemaphoreGive(semphore_handle);
    
    /* 4. 创建启动任务(start_task) */
    xTaskCreate((TaskFunction_t         )   start_task,           // 任务函数指针
                (char *                 )   "start_task",         // 任务名称
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,// 任务堆栈大小
                (void *                 )   NULL,                 // 任务参数
                (UBaseType_t            )   START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t *         )   &start_task_handler );// 任务句柄指针
    
    /* 5. 启动FreeRTOS调度器 */
    vTaskStartScheduler();
}

/* 启动任务:负责创建三个不同优先级的任务 */
void start_task( void * pvParameters )
{
    /* 进入临界区,防止任务创建过程被中断打断 */
    taskENTER_CRITICAL();
    
    /* 创建低优先级任务(low_task) */
    xTaskCreate((TaskFunction_t         )   low_task,
                (char *                 )   "low_task",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &low_handler );
                
    /* 创建中优先级任务(middle_task) */
    xTaskCreate((TaskFunction_t         )   middle_task,
                (char *                 )   "middle_task",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &middle_handler );
                
    /* 创建高优先级任务(high_task) */
    xTaskCreate((TaskFunction_t         )   high_task,
                (char *                 )   "task3",  // 注意:任务名称为"task3",应为"high_task"
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &high_handler );
    
    /* 6. 删除启动任务自身(任务创建完成后不再需要) */
    vTaskDelete(NULL);
    
    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/* 低优先级任务:获取信号量后长时间占用,模拟资源持有 */
void low_task( void * pvParameters )
{
    while(1)
    {
        printf("低优先级任务获取\r\n");
        /* 获取二值信号量(无限等待) */
        xSemaphoreTake(semphore_handle, portMAX_DELAY);
        printf("低优先级任务正在执行\r\n");
        /* 模拟长时间占用资源(3000ms) */
        delay_ms(3000);
        printf("低优先级任务释放信号量\r\n");
        /* 释放信号量 */
        xSemaphoreGive(semphore_handle);
        /* 延时1000个系统节拍 */
        vTaskDelay(1000);
    }
}

/* 中优先级任务:不依赖信号量,周期性执行 */
void middle_task (void * pvParameters )
{
    while(1)
    {
        printf("中优先级任务获取\r\n");
        /* 延时1000个系统节拍 */
        vTaskDelay(1000);
    }
}

/* 高优先级任务:获取信号量后短时间占用 */
void high_task( void * pvParameters )
{
    while(1)
    {
        printf("高优先级任务获取\r\n");
        /* 获取二值信号量(无限等待) */
        xSemaphoreTake(semphore_handle, portMAX_DELAY);
        printf("高优先级任务正在执行\r\n");
        /* 模拟短时间占用资源(1000ms) */
        delay_ms(1000);
        printf("高优先级任务释放信号量\r\n");
        /* 释放信号量 */
        xSemaphoreGive(semphore_handle);
        /* 延时1000个系统节拍 */
        vTaskDelay(1000);
    }
}

**实现功能:**系统创建了三个不同优先级的任务(低、中、高),它们共享一个二值信号量(初始化为有效状态)。低优先级任务首先获取信号量并长时间占用(3秒),在此期间,如果高优先级任务就绪并尝试获取同一个信号量,它将被阻塞;而此时中优先级任务(不依赖信号量)如果就绪,将抢占CPU并运行,导致高优先级任务无法及时执行,从而形成优先级翻转------即中优先级任务先于高优先级任务执行,违背了任务的优先级设定。

5 互斥信号量

5.1 概念

互斥信号量是一种特殊的同步机制,其核心是具有优先级继承的二值信号量。它专为多任务环境中的互斥访问而设计,可以有效减轻优先级翻转问题,但不适合用于中断环境。

核心特性:

复制代码
二值状态:与二值信号量类似,只有0(被占用)和1(可用)两种状态
优先级继承:当高优先级任务等待低优先级任务释放信号量时,低优先级任务的优先级会临时提升到与高优先级任务相同
任务专用:只能在任务中使用,不能在中断服务函数中使用
自动初始化:创建后信号量自动处于释放状态(可用)

5.2 优先级继承机制

当一个互斥信号量被低优先级任务持有时,如果高优先级任务尝试获取这个信号量:

复制代码
高优先级任务被阻塞
低优先级任务的优先级临时提升到与高优先级任务相同
低优先级任务快速执行并释放信号量
低优先级任务恢复原优先级
高优先级任务获取信号量并执行

5.3 相关函数

5.3.1 动态创建函数

复制代码
SemaphoreHandle_t xSemaphoreCreateMutex(void);
功能:动态分配内存创建互斥信号量
返回值:成功返回信号量句柄,失败返回NULL
特点:创建后信号量自动处于释放状态(可用)

5.3.2 静态创建函数

复制代码
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);
功能:使用用户提供的静态内存创建互斥信号量
参数:pxMutexBuffer - 指向StaticSemaphore_t类型变量的指针
返回值:成功返回信号量句柄,失败返回NULL

5.4 为什么互斥信号量不能用于中断

复制代码
无任务优先级:中断没有任务优先级,而互斥信号量的优先级继承机制依赖于任务优先级
中断不能阻塞:中断服务函数必须快速执行,不能设置阻塞时间等待信号量

5.5 示例实验

复制代码
/* 互斥信号量句柄,全局变量,用于任务间互斥访问共享资源 */
QueueHandle_t mutex_semphore_handle;

/* FreeRTOS 演示任务入口函数 */
void freertos_demo(void)
{    
    /* 1. 创建互斥信号量 */
    mutex_semphore_handle = xSemaphoreCreateMutex();
    
    /* 2. 检查信号量是否创建成功 */
    if(mutex_semphore_handle != NULL)
    {
        printf("二值信号量创建成功\r\n");  // 注意:这里打印有误,应为"互斥信号量"
    }
    
    /* 3. 释放信号量(实际上互斥信号量创建后已自动释放,此操作多余但无害) */
    xSemaphoreGive(mutex_semphore_handle);
    
    /* 4. 创建启动任务(start_task) */
    xTaskCreate((TaskFunction_t         )   start_task,           // 任务函数指针
                (char *                 )   "start_task",         // 任务名称
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,// 任务堆栈大小
                (void *                 )   NULL,                 // 任务参数
                (UBaseType_t            )   START_TASK_PRIO,      // 任务优先级
                (TaskHandle_t *         )   &start_task_handler );// 任务句柄指针
    
    /* 5. 启动FreeRTOS调度器 */
    vTaskStartScheduler();
}

/* 启动任务:负责创建三个不同优先级的任务 */
void start_task( void * pvParameters )
{
    /* 进入临界区,防止任务创建过程被中断打断 */
    taskENTER_CRITICAL();
    
    /* 创建低优先级任务(low_task) */
    xTaskCreate((TaskFunction_t         )   low_task,
                (char *                 )   "low_task",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &low_handler );
                
    /* 创建中优先级任务(middle_task) */
    xTaskCreate((TaskFunction_t         )   middle_task,
                (char *                 )   "middle_task",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &middle_handler );
                
    /* 创建高优先级任务(high_task) */
    xTaskCreate((TaskFunction_t         )   high_task,
                (char *                 )   "task3",  // 注意:任务名称为"task3",应为"high_task"
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &high_handler );
    
    /* 6. 删除启动任务自身(任务创建完成后不再需要) */
    vTaskDelete(NULL);
    
    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/* 低优先级任务:获取互斥信号量后长时间占用,模拟资源持有 */
void low_task( void * pvParameters )
{
    while(1)
    {
        printf("低优先级任务获取\r\n");
        /* 获取互斥信号量(无限等待) */
        xSemaphoreTake(mutex_semphore_handle, portMAX_DELAY);
        printf("低优先级任务正在执行\r\n");
        /* 模拟长时间占用资源(3000ms) */
        delay_ms(3000);
        printf("低优先级任务释放信号量\r\n");
        /* 释放互斥信号量 */
        xSemaphoreGive(mutex_semphore_handle);
        /* 延时1000个系统节拍 */
        vTaskDelay(1000);
    }
}

/* 中优先级任务:不依赖信号量,周期性执行 */
void middle_task (void * pvParameters )
{
    while(1)
    {
        printf("中优先级任务获取\r\n");
        /* 延时1000个系统节拍 */
        vTaskDelay(1000);
    }
}

/* 高优先级任务:获取互斥信号量后短时间占用 */
void high_task( void * pvParameters )
{
    while(1)
    {
        printf("高优先级任务获取\r\n");
        /* 获取互斥信号量(无限等待) */
        xSemaphoreTake(mutex_semphore_handle, portMAX_DELAY);
        printf("高优先级任务正在执行\r\n");
        /* 模拟短时间占用资源(1000ms) */
        delay_ms(1000);
        printf("高优先级任务释放信号量\r\n");
        /* 释放互斥信号量 */
        xSemaphoreGive(mutex_semphore_handle);
        /* 延时1000个系统节拍 */
        vTaskDelay(1000);
    }
}

实现功能: 系统创建了三个不同优先级的任务(低、中、高),它们共享一个互斥信号量。当低优先级任务获取互斥信号量并长时间占用(3秒)时,如果高优先级任务尝试获取同一个信号量,它将被阻塞,但此时互斥信号量的优先级继承机制会启动:低优先级任务的优先级会被临时提升到与高优先级任务相同,从而防止中优先级任务抢占CPU。这样,低优先级任务可以快速完成并释放信号量,然后高优先级任务立即获得执行权。

相关推荐
Zeku2 小时前
借助通用驱动spidev实现SPI全双工通信
stm32·freertos·linux驱动开发·linux应用开发
单片机系统设计2 小时前
基于STM32的宠物智能喂食系统
c语言·stm32·单片机·嵌入式硬件·物联网·毕业设计·宠物
雾削木2 小时前
STM32 HAL DS1302时钟模块
stm32·单片机·嵌入式硬件
沪漂的码农3 小时前
FlexCAN寄存器完全解读
stm32·单片机·嵌入式硬件·can
申克Lab15 小时前
STM32 FreeRTOS 消息队列
java·stm32·嵌入式硬件
Digitally20 小时前
如何通过 5 种方法轻松格式化 USB 驱动器
stm32·单片机·嵌入式硬件
BT-BOX21 小时前
STM32各系列芯片编译支持包 Pack下载
stm32·单片机·嵌入式硬件
GUET_一路向前21 小时前
【 在有返回值的函数里直接 return;到底返回值是多少?】
stm32
云山工作室1 天前
基于单片机的飞机客舱窗帘控制系统(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计