什么是互斥量 互斥量的应用场合 互斥量的API函数 基本代码结构 互斥量使用举例 递归锁 递归锁举例 总结 什么是互斥量 在freeRTOS中,多个任务访问一块共享资源,会产生竞争现象。 比如马路上只有一个很早以前的电话亭,A、B都想要打电话,然后他们就开始打架了。但是如果A先进去了然后把门锁了,B想进去打电话的话只能在外面等,必须等到A把门锁打开。 互斥量的应用场合 像freeRTOS的多任务系统,任务A正在使用某个资源,还没用完的时候,任务B也来使用,就可能会导致问题。 就比如串口,任务A正在用串口发送数据,此时任务B也来用这个串口发送数据,这样就会导致数据混乱。 简而言之,多任务使用共享资源的情况下,就需要使用互斥量了。 这里有个特别强调的注意点 :按照正常的情况来说,只要任务A获取了互斥量,其他任务都无法释放互斥量才对。但是freeRTOS中并没有实现,其他任务也可释放互斥量。所以在freeRTOS中,大家默认谁获取互斥量,就该谁释放。 互斥量的API函数 创建互斥量 /* 返回值:正确返回SemaphoreHandle_t 变量,错误返回NULL */ SemaphoreHandle_t xSemaphoreCreateMutex( void ); 获取 /* 参数: xSemaphore : 创建的互斥量 xBlockTime : 获取锁等待时间,超时获取失败 返回值: 获取到,返回pdTRUE,反之,pdFALSE */ SemaphoreHandle_t xSemaphoreTake( xSemaphore, xBlockTime ) 释放 /* 参数: xSemaphore : 创建的互斥量 返回值: 释放成功,返回pdTRUE,反之,pdFALSE */ SemaphoreHandle_t xSemaphoreGive( xSemaphore, xBlockTime ) 基本代码结构 if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ /*资源处理代码,这里可以称之为临界区*/ xSemaphoreGive(xSemaphore); } 互斥量使用举例 首先举一个错误例子,这样会有个对比 void taskA(void* pvPram) { while(1){ for(int i=0;i5;i++){ num++; ESP_LOGI("taskA","num is %d",num); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void* pvPram) { while(1){ num++; ESP_LOGI("taskB","num is %d",num); vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateMutex(); xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1); } 打印结果如下:代码目的是taskA执行过程中,num值是连续增加的,然而打印中会发现,taskA打印并不连续,会被taskB抢占。 使用mutex函数 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-05-07 * @version v0.1 * * @revision history: * 2025-05-07 - Initial version. */ #include stdio.h #include freertos/FreeRTOS.h #include freertos/task.h #include freertos/semphr.h #include esp_log.h SemaphoreHandle_t xSemaphore; BaseType_t num=0; void taskA(void* pvPram) { while(1){ /*如果在1S获取到互斥量,那么执行num++五次并打印 */ if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ for(int i=0;i5;i++){ num++; ESP_LOGI("taskA","num is %d",num); } xSemaphoreGive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void taskB(void* pvPram) { while(1){ if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ num++; ESP_LOGI("taskB","num is %d",num); xSemaphoreGive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateMutex(); /*为了产生竞争,两个任务优先级设置一样 */ xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); xTaskCreatePinnedToCore(taskB,"taskB",2048,NULL,4,NULL,1); } 打印结果: 分析: 从打印看,当任务A执行完五次num++后,才会轮到任务B执行一次。完全符合执行逻辑。 递归锁 递归锁的引出 假设有两个互斥量M1、M2,taskA获取了M1,taskB获取了M2。然而taskA需要再获取M2才能执行下一步,且taskB也需要再获取M1才能执行下一步。这样就导致了taskA、taskB都无法释放互斥量。导致了死锁。 再假设有一个互斥量M1,任务A获取了M1后,执行一个函数,然而此函数中也获取了M1,此时任务A还没释放M1,这将导致任务A自己把自己锁了。 然而递归锁,可以多次获取,可以解决以上问题 递归锁的函数 /*创建递归锁*/ SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void ); /*获得 */ BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait); /*释放 */ BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore ); 递归锁举例 死锁举例 SemaphoreHandle_t xSemaphore; BaseType_t num=0; void func(BaseType_t *a) { //获取互斥量等待时间设置为portMAX_DELAY,因为taskA为释放互斥量,导致此处一直等待 if(xSemaphoreTakeRecursive(xSemaphore,portMAX_DELAY)==pdTRUE){ *a=*a+1; xSemaphoreGiveRecursive(xSemaphore); } } void taskA(void* pvPram) { while(1){ /*如果在1S获取到互斥量,那么执行num++五次并打印 */ if(xSemaphoreTake(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ func(num); ESP_LOGI("taskA","num is %d",num); xSemaphoreGive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateMutex(); /*为了产生竞争,两个任务优先级设置一样 */ xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); } 打印如下:程序将一直锁着,无法执行 使用递归锁 SemaphoreHandle_t xSemaphore; BaseType_t num=0; void func(BaseType_t *a) { //获取互斥量等待时间设置为portMAX_DELAY,因为taskA为释放互斥量,导致此处一直等待 if(xSemaphoreTakeRecursive(xSemaphore,portMAX_DELAY)==pdTRUE){ *a=*a+1; xSemaphoreGiveRecursive(xSemaphore); } } void taskA(void* pvPram) { while(1){ /*如果在1S获取到互斥量,那么执行num++五次并打印 */ if(xSemaphoreTakeRecursive(xSemaphore,pdMS_TO_TICKS(1000))==pdTRUE){ func(num); ESP_LOGI("taskA","num is %d",num); xSemaphoreGiveRecursive(xSemaphore); } vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { xSemaphore=xSemaphoreCreateRecursiveMutex(); /*为了产生竞争,两个任务优先级设置一样 */ xTaskCreatePinnedToCore(taskA,"taskA",2048,NULL,4,NULL,1); } 打印如下:发现完全可以正常打印了 总结 为了避免死锁,在中断中不要使用互斥量 如果有嵌套使用互斥量,请使用递归锁,递归的层数由queueQUEUE_TYPE_RECURSIVE_MUTEX决定 虽然freeRTOS没有实现谁锁谁开,但是开发过程中还是默认这么操作了