【FreeRtos教程六】STM32 CubeMx——Mutexes And Recursive Mutexes(互斥量与递归互斥量)

文章介绍了互斥量在多任务系统中的作用,防止资源竞争问题,通过一个按键任务和两个打印任务的示例展示了FreeRTOS中互斥量如何解决优先级反转。在示例中,按键任务控制打印任务的执行,分析了启用和禁用osMutexWait和osMutexRelease对任务执行顺序的影响,强调了互斥量在优先级继承中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 互斥量

2 示例程序

2.1例程功能

2.2步骤

2.3实验结果

2.4函数讲解

2.5程序源码


1 互斥量

1.都是为什么要有互斥量

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。 比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信 息混杂在一起。

所以我们希望某一任务在在某一时刻单独占有某一硬件资源,这时候便引入了互斥量。例如,我们怎么独享厕所呢?我们可以上厕所然后把门锁了,完事了自己开锁。使用队列、信号量都可以实现互斥访问,以信号量为例:

  • 信号量初始值为1
  • 任务A想上厕所,"take"信号量成功,它进入厕所
  • 任务B也想上厕所,"take"信号量不成功,等待
  • 任务A用完厕所,"give"信号量;轮到任务B使用

2.既然队列、信号量都可以实现互斥访问,为什么还要引入互斥量

假设任务A、B都想使用串口,A优先级比较低:

  • 任务A获得了串口的信号量
  • 任务B也想使用串口,它将会阻塞、等待A释放信号量
  • 高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)

互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是FreeRTOS中互斥量和二 进制信号量的差别。这点在下面的例程中会被介绍。

3.FreeRtos的互斥锁是代码上约定的习惯

使用互斥量的核心在于:谁上锁,就只能由谁开锁。但是FreeRTOS的互斥锁,并没有在代码上实现这点,即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。所以为了实现互斥锁,就约定程序员在写代码的时候,那个任务上锁,就由那个任务解锁。

4.递归互斥量

互斥量能被多次获取,同时也要被多次释放。其功能就不在详细介绍。

2 示例程序

2.1 例程功能

按优先级由高到底分别创建一个按键任务(priority:High)、打印任务1(priority:Normal)、打印任务2(priority:Low),打印任务1和2先挂起,由按键任务来解挂起,观察打印任务2在添加获取互斥量程序前后,打印任务1和2执行的先后顺序。

2.2 步骤

配置按键IO口

 配置三个动态任务,优先级依次为High、Normal、Low

 配置一个互斥量

按键按下,唤醒打印任务1和打印任务2,并获取释放互斥量

/* USER CODE END Header_StartTask_KEY0 */
void StartTask_KEY0(void const * argument)
{
  /* USER CODE BEGIN StartTask_KEY0 */
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==0)
    {
        osDelay(10);
        if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==0)
        {
            osThreadSuspendAll();
            printf("\nKEY_0按下\n");
            osThreadResumeAll();
            osThreadResume(myTask01Handle);
            osThreadResume(myTask02Handle);
            osMutexWait(myMutex01Handle,osWaitForever);//获得互斥量
            osMutexRelease(myMutex01Handle);//释放互斥量
            while(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==0)
            {
                osDelay(10);
            }
        }
    }
    osDelay(1);
 }

循环开始时任务挂起,等待按键按下后任务解挂起并打印字符串

void StartTask01(void const * argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */
  for(;;)
  {
      osThreadSuspend(NULL);
      osThreadSuspendAll();
      printf("嗨,我是打印任务 1\n");
      osThreadResumeAll();
      osDelay(1);
  }
  /* USER CODE END StartTask01 */
}

循环开始时任务挂起,等待按键按下后任务解挂起并打印字符串。程序osMutexWait()和osMutexRelease()屏蔽与不屏蔽,程序执行的结果有所不同。

/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  {
      //osMutexWait(myMutex01Handle,osWaitForever);//获得互斥量
      osThreadSuspend(NULL);
      osThreadSuspendAll();
      printf("嗨,我是打印任务 2\n");
      osThreadResumeAll();
      //osMutexRelease(myMutex01Handle);//释放互斥量
      osDelay(1);
  }
  /* USER CODE END StartTask02 */
}

2.3 实验结果

程序osMutexWait()和osMutexRelease()屏蔽时,当按键按下后,打印任务1和打印任务2按照优先级顺序先后执行

程序osMutexWait()和osMutexRelease()不屏蔽时,当按键按下后,打印任务2发生优先级继承先打印

实验评析

FreeRtos实现互斥的核心概念是:谁上锁,就由谁来解锁,那为什么不用信号量来实现一定要用互斥量来实现呢?关键在于使用信号量实现互斥会发生优先级的反转,而使用互斥量能通过优先级继承的方式来解决优先级反转的问题。(详情见韦东山的讲解)

板子上电,程序初步运行时,打印任务2获取互斥量后然后挂起,当按键按下,按键任务在解挂打印任务1、2后也想获取互斥量,但此时互斥量被打印任务2占有,这时候FreeRtos就会将打印任务2的优先级提升到和按键任务一样高(Priority:High),使互斥量被尽快释放。

2.4 函数讲解

1 osStatus osMutexWait (osMutexId mutex_id, uint32_t millisec)

/**
@brief Wait until a Mutex becomes available
@param mutex_id      mutex ID obtained by \ref osMutexCreate.
@param millisec      timeout value or 0 in case of no time-out.
@retval  status code that indicates the execution status of the function.
@note   MUST REMAIN UNCHANGED: \b osMutexWait shall be consistent in every CMSIS-RTOS.
*/
osStatus osMutexWait (osMutexId mutex_id, uint32_t millisec)
  • 函数功能:获取某一互斥量
  • 传入参数:1.mutex_id (要获取互斥量量的句柄) 2.millisec(超时等待时间)

2 osStatus osMutexRelease (osMutexId mutex_id)

/**
@brief Release a Mutex that was obtained by \ref osMutexWait
@param mutex_id      mutex ID obtained by \ref osMutexCreate.
@retval  status code that indicates the execution status of the function.
@note   MUST REMAIN UNCHANGED: \b osMutexRelease shall be consistent in every CMSIS-RTOS.
*/
osStatus osMutexRelease (osMutexId mutex_id)
  • 函数功能:释放某一互斥量
  • 传入参数:mutex_id (要释放互斥量量的句柄)

2.5 程序源码

github链接:HaoJosephWen/FreeRtos_personal (github.com)

<think>我们正在讨论FreeRTOS互斥信号的创建和使用。根据引用内容,我们可以找到一些关键信息:1.引用[1]展示了在FreeRTOS初始化函数中创建互斥的代码:使用osMutexCreate函数创建了一个名为myMutex01的互斥。2.引用[2]展示了获取递归互斥的宏xSemaphoreTakeRecursive,它用于递归互斥的获取(当配置为启用递归互斥时)。3.引用[3]说明了FreeRTOS从9.0版本开始支持静态和动态两种内存分配方式创建对象,包括互斥。并列举了创建互斥的函数:xSemaphoreCreateMutex(动态)和xSemaphoreCreateMutexStatic(静态)。用户问题:如何在FreeRTOS中创建和使用互斥信号?回答步骤:1.创建互斥信号:分为动态创建和静态创建两种方式。2.获取(Take)互斥信号:使用xSemaphoreTake函数。3.释放(Give)互斥信号:使用xSemaphoreGive函数。4.递归互斥的特殊处理(如果需要)。注意:互斥信号是一种特殊的二进制信号,用于互斥访问,具有优先级继承机制,以避免优先级反转问题。具体实现:###1.创建互斥信号####动态创建使用函数`xSemaphoreCreateMutex()`,该函数返回一个信号句柄(SemaphoreHandle_t)。如果返回NULL,表示创建失败(通常因为内存不足)。```cSemaphoreHandle_txMutex;xMutex=xSemaphoreCreateMutex();if(xMutex!=NULL){//创建成功}```####静态创建使用函数`xSemaphoreCreateMutexStatic()`,需要传入一个静态分配的Semaphore_t类型的内存空间(由用户提供)。```cStaticSemaphore_txMutexBuffer;SemaphoreHandle_txMutex;xMutex=xSemaphoreCreateMutexStatic(&xMutexBuffer);```###2.获取互斥信号使用`xSemaphoreTake()`函数获取互斥。如果互斥已经被其他任务占用,则任务可能会被阻塞(根据指定的阻塞时间)。```cif(xSemaphoreTake(xMutex,pdMS_TO_TICKS(100))==pdTRUE){//成功获取互斥,可以安全访问共享资源//...执行操作...//操作完成后,释放互斥xSemaphoreGive(xMutex);}else{//在指定的100ms内未能获取互斥}```###3.释放互斥信号使用`xSemaphoreGive()`函数释放互斥,以便其他任务可以获取。```cxSemaphoreGive(xMutex);```###4.递归互斥递归互斥允许同一个任务多次获取而不被阻塞(但获取多少次就必须释放多少次)。创建递归互斥使用`xSemaphoreCreateRecursiveMutex()`(动态)或`xSemaphoreCreateRecursiveMutexStatic()`(静态)。获取和释放使用专门的递归函数:-获取递归互斥:`xSemaphoreTakeRecursive()`-释放递归互斥:`xSemaphoreGiveRecursive()````c//创建递归互斥(动态)SemaphoreHandle_txRecursiveMutex;xRecursiveMutex=xSemaphoreCreateRecursiveMutex();//使用xSemaphoreTakeRecursive(xRecursiveMutex,portMAX_DELAY);//...可以重复获取而不会死锁...xSemaphoreTakeRecursive(xRecursiveMutex,portMAX_DELAY);//...操作...xSemaphoreGiveRecursive(xRecursiveMutex);xSemaphoreGiveRecursive(xRecursiveMutex);//必须释放相同的次数```###注意事项-互斥用于保护共享资源,确保任务独占访问。-获取和释放互斥必须成对出现,避免死锁。-在中断服务程序中不能使用互斥(因为可能阻塞),可以使用二进制信号或其他同步机制。###示例代码以下是一个简单的互斥使用示例,两个任务共享一个串口打印资源:```cSemaphoreHandle_txPrintMutex;voidvTask1(void*pvParameters){while(1){if(xSemaphoreTake(xPrintMutex,portMAX_DELAY)==pdTRUE){printf("Task1isprinting...\n");xSemaphoreGive(xPrintMutex);}vTaskDelay(pdMS_TO_TICKS(1000));}}voidvTask2(void*pvParameters){while(1){if(xSemaphoreTake(xPrintMutex,portMAX_DELAY)==pdTRUE){printf("Task2isprinting...\n");xSemaphoreGive(xPrintMutex);}vTaskDelay(pdMS_TO_TICKS(1000));}}voidmain(){//创建互斥xPrintMutex=xSemaphoreCreateMutex();//创建任务xTaskCreate(vTask1,"Task1",configMINIMAL_STACK_SIZE,NULL,1,NULL);xTaskCreate(vTask2,"Task2",configMINIMAL_STACK_SIZE,NULL,1,NULL);//启动调度器vTaskStartScheduler();}```###引用说明在FreeRTOS中,互斥的创建和使用可以参考官方文档和示例代码[^1][^2][^3]。§§相关问题§§1.互斥信号和二进制信号有什么区别?2.如何在FreeRTOS中避免优先级反转?3.递归互斥适用于什么场景?</think>### FreeRTOS互斥信号的创建使用 #### 1. **创建互斥信号** FreeRTOS支持**动态**和**静态**两种创建方式: - **动态创建**(自动分配内存): ```c #include "FreeRTOS.h" #include "semphr.h" SemaphoreHandle_t xMutex = xSemaphoreCreateMutex(); // 创建互斥信号 if (xMutex == NULL) { // 创建失败(内存不足) } ``` 引用[3]指出,动态创建需确保配置`configSUPPORT_DYNAMIC_ALLOCATION=1`[^3]。 - **静态创建**(用户预分配内存): ```c StaticSemaphore_t xMutexBuffer; // 静态内存缓冲区 SemaphoreHandle_t xMutex = xSemaphoreCreateMutexStatic(&xMutexBuffer); ``` 适用于内存受限场景,需配置`configSUPPORT_STATIC_ALLOCATION=1`[^3]。 #### 2. **使用互斥信号** 互斥信号用于**保护共享资源**,防止多任务并发访问冲突: ```c void vTaskExample(void *pvParameters) { while (1) { // 尝试获取互斥(等待10ms) if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(10)) == pdTRUE) { // 成功获取,访问共享资源 // ... 执行临界区代码 ... // 释放互斥 xSemaphoreGive(xMutex); } else { // 获取失败(超时或错误) } vTaskDelay(pdMS_TO_TICKS(100)); } } ``` 关键函数: - `xSemaphoreTake()`:获取互斥(可设置阻塞时间)。 - `xSemaphoreGive()`:释放互斥。 #### 3. **递归互斥信号** 若任务可能**多次获取同一互斥**(如递归函数),需使用递归互斥: ```c // 创建递归互斥(动态) SemaphoreHandle_t xRecursiveMutex = xSemaphoreCreateRecursiveMutex(); // 使用 xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); // 可嵌套获取 xSemaphoreGiveRecursive(xRecursiveMutex); // 需相同次数释放 ``` 引用[2]显示,递归互斥需配置`configUSE_RECURSIVE_MUTEXES=1`[^2]。 #### 4. **关键注意事项** - **优先级继承**:FreeRTOS互斥自动实现优先级继承,避免优先级反转问题。 - **禁止在中断中使用**:`xSemaphoreTake()`不能在中断服务例程(ISR)调用,需用带`FromISR`后缀的专用函数。 - **成对操作**:每次`Take`后必须对应一次`Give`,否则会导致死锁。 - **超时设置**:合理设置`xSemaphoreTake()`的阻塞时间,避免任务永久挂起。 #### 5. **完整示例** ```c // 创建互斥 SemaphoreHandle_t xUartMutex = xSemaphoreCreateMutex(); void vUartWriteTask(void *pvParameters) { while (1) { if (xSemaphoreTake(xUartMutex, portMAX_DELAY)) { // 安全访问UART HAL_UART_Transmit(&huart1, "Hello", 5, 100); xSemaphoreGive(xUartMutex); } vTaskDelay(pdMS_TO_TICKS(500)); } } ``` 此例保护UART外设,防止多任务同时发送数据冲突[^1]。 --- ### 相关问题 1. **互斥信号二进制信号有何区别?** (提示:互斥有优先级继承机制,二进制信号无此特性) 2. **如何在FreeRTOS中避免优先级反转问题?** (提示:互斥的优先级继承机制如何工作?) 3. **递归互斥的典型应用场景是什么?** (提示:涉及递归调用或多次访问同一资源的任务) 4. **动态创建静态创建互斥在内存管理上有何优劣?** (提示:动态创建更灵活但可能碎片化,静态创建更安全但需预分配内存[^3])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值