整站优化,网站建设属营改增范围吗,晋中路桥建设集团网站,浙江英文网站建设互斥量概述
在博文“ FreeRTOS_信号量 ”中#xff0c;使用了二进制信号量实现了互斥#xff0c;保护了串口资源。博文链接如下#xff1a;
FreeRTOS_信号量-CSDN博客
但还是要引入互斥量的概念。互斥量与二进制信号量相比#xff0c;能够多实现如下两个功能#xff1a…互斥量概述
在博文“ FreeRTOS_信号量 ”中使用了二进制信号量实现了互斥保护了串口资源。博文链接如下
FreeRTOS_信号量-CSDN博客
但还是要引入互斥量的概念。互斥量与二进制信号量相比能够多实现如下两个功能
防止优先级反转问题解决递归上锁/解锁问题
互斥量有两种
普通互斥量具有优先级继承功能解决优先级反转问题递归锁具有优先级继承功能、能够解决递归上锁/解锁问题、解决谁上锁谁才能解锁的问题
优先级反转
优先级反转问题 假设任务A、B、C的优先级为1、2、3。在程序开始时A进行上锁之后B运行抢断的A之后C运行抢断了B。在C中想获得锁但A已经上锁所以C进入了阻塞释放了CPU。这时B继续运行但不解锁从而导致C被B抢占即高优先级的任务是否能继续执行由低优先级的任务决定。
这种现象称为优先级反转。
优先级继承
使用优先级继承的方法解决优先级反转的问题。 在C获得锁之后进入阻塞状态同时执行上锁的任务A会继承C的优先级3从而进行执行。当A执行完解锁后A优先级的优先级变回原来的优先级1。之后C获得锁不再阻塞C继续执行执行完成之后B运行。
在低优先级任务上锁高优先级任务获得锁阻塞之后低优先级任务继承高优先级任务的优先级的操作叫做优先级继承。
递归上锁/解锁
递归上锁原因
递归上锁的示例代码如下
void fun(){xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* 上锁 *//* 一些功能 */xSemaphoreGive(SemaphoreHandleTest);/* 解锁 */
}
void Task1(void *param){while(1){xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* 上锁 *//* 一些功能 */fun();xSemaphoreGive(SemaphoreHandleTest);/* 解锁 */}
}
在上述代码中任务1首先进行了上锁之后调用了fun函数。在fun函数中又进行了上锁但这时已经上锁所以就阻塞在了fun函数中。最终导致Task1无法执行完成fun函数也就无法执行解锁函数形成死锁。
递归锁
使用递归锁可以解决上述问题。递归锁的作用是如果开始时为A来上锁那么在上锁之后A依旧可以进行调用上锁函数进行上锁而不会进入阻塞状态。但不管怎样上锁之后都要解锁即上锁多少次就要解锁多少次上锁与解锁一 一配对。
除此之外递归锁还可以满足谁上锁谁才有权力解锁的问题。而信号量和普通互斥锁并不能实现这个功能。
相关配置
在使用互斥量之前需要打开宏开关具体的步骤如下 在使用递归锁之前需要打开宏开关具体的步骤如下 互斥量相关函数
创建普通互斥量
函数声明如下
/* 这是一个宏创建普通互斥量 */
xSemaphoreCreateMutex()/* 宏定义 */
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )/* 实际调用函数 */
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
返回值互斥量的句柄
互斥量在创建后与二进制信号量不同互斥量初始值为1二进制信号量初始值为0
获取/释放互斥量
获取和释放互斥量所用的函数与二值信号量的函数一致。
函数声明如下
/* 这是一个宏存入释放信号量/互斥量 */
xSemaphoreGive( xSemaphore )/* 这是一个宏获取信号量/互斥量 */
xSemaphoreTake( xSemaphore, xBlockTime )
存入函数返回值成功返回pdPASS在当前计数值最大计数值时会存入失败
xSemaphore 信号量句柄
xBlockTime 阻塞等待时间portMAX_DELAY为死等
创建递归锁
函数声明如下
/* 这是一个宏创建递归锁 */
xSemaphoreCreateRecursiveMutex()/* 宏定义 */
#define xSemaphoreCreateRecursiveMutex() \xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )/* 实际调用函数 */
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
返回值互斥量的句柄
创建后初始值为1
获取/释放递归锁
获取和释放递归锁的函数用法与互斥量一致只是名字有所差别
函数声明如下
/* 这是一个宏存入释放递归锁 */
xSemaphoreGiveRecursive( xMutex )/* 这是一个宏获取递归锁 */
xSemaphoreTakeRecursive( xMutex, xBlockTime )
存入函数返回值成功返回pdPASS在当前计数值最大计数值时会存入失败
xSemaphore 信号量句柄
xBlockTime 阻塞等待时间portMAX_DELAY为死等
验证实验
1、基本互斥实验
使用互斥量来确保串口的输出不被打断。
与二进制信号量实现该功能相比代码方面的差异只在初始化不同其他代码都没有进行修改
二进制信号量实现该功能的初始化代码如下
/* 创建二进制信号量初始值自动设置为 0 */
SemaphoreHandleTest xSemaphoreCreateBinary();
/* 让信号量为1代表串口资源可用 */
xSemaphoreGive(SemaphoreHandleTest);
互斥量实现该功能的初始化代码如下
/* 创建互斥量初始值自动设置为 1 */
SemaphoreHandleTest xSemaphoreCreateMutex();
实验现象和其他代码实现与博文“ FreeRTOS_信号量 ”中 “ 验证实验 ” 均一致。博文链接如下
FreeRTOS_信号量-CSDN博客
2、优先级继承实验
根据本文 “互斥量概述” 中 “ 优先级反转 ”内容的描述来进行编写代码。这里使用二进制信号量与互斥量进行运行结果的对比。
具体的代码实现如下
QueueHandle_t SemaphoreHandleTest;
char taskA_flag 0;
char taskB_flag 0;
char taskC_flag 0;
void TaskAFunction(void *param){int i0;while(1){/* 上锁 */xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* 执行一个很长的时间的程序 */taskA_flag 1;taskB_flag 0;taskC_flag 0;for(i0;i20;i){printf(%d,%d,%d,taskA_flag,taskB_flag,taskC_flag);taskA_flag 1;taskB_flag 0;taskC_flag 0;}/* 解锁 */xSemaphoreGive(SemaphoreHandleTest);}
}
void TaskBFunction(void *param){vTaskDelay(5);/* B先休眠让A任务执行 */while(1){/* B一直在执行这会导致如果A优先级低于B则A一直不执行 */taskA_flag 0;taskB_flag 1;taskC_flag 0;}
}
void TaskCFunction(void *param){int i0;vTaskDelay(10);/* C先休眠让AB任务执行 */while(1){taskA_flag 0;taskB_flag 0;taskC_flag 1;/* 上锁 */xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* C任务执行一段时间 */for(i0;i10;i){printf(this is taskC\r\n);taskA_flag 0;taskB_flag 0;taskC_flag 1;}/* 解锁 */xSemaphoreGive(SemaphoreHandleTest);}
}
int main( void )
{TaskHandle_t xHandleTask1;TaskHandle_t xHandleTask2;TaskHandle_t xHandleTask3;prvSetupHardware();SerialPortInit();printf(UART TEST\r\n);/* 创建互斥量初始值自动设置为 1 *///SemaphoreHandleTest xSemaphoreCreateMutex();/* 创建二进制信号量初始值自动设置为 0 */SemaphoreHandleTest xSemaphoreCreateBinary();/* 让信号量为1代表资源可用 */xSemaphoreGive(SemaphoreHandleTest);xTaskCreate(TaskAFunction,TaskA,100,(void*)NULL,1,xHandleTask1);xTaskCreate(TaskBFunction,TaskB,100,(void*)NULL,2,xHandleTask2);xTaskCreate(TaskCFunction,TaskC,100,(void*)NULL,3,xHandleTask3);vTaskStartScheduler();return 0;
}
该代码的主要功能就是利用taskA_flag 、taskB_flag、taskC_flag 这三个标志位来判断当前任务是谁在运行。通过逻辑分析仪显示出这三个变量的电平来分析抢占关系。
二值信号量的运行结果如下 可以看到在1 - 2阶段中BC调用延时处于阻塞态因此A运行。在2-3阶段中A运行了一段时间后B的延时结束B开始抢占A进行运行。在2-3阶段中B运行了一段时间后C的延时结束C开始抢占B进行运行。以上是正常的抢占流程。
在C抢占B之后尝试获取信号量但信号量已经被A所获取还未进行释放因此C进入了阻塞状态之后B进行抢占到CPU继续执行。但B的优先级高于AA不能够释放信号量这样就导致了C永远处于阻塞态只有当B释放CPUA才能释放信号量从而C才可以运行这样就产生了优先级反转的问题。
互斥量的运行结果如下 可以看到在1 - 2阶段中BC调用延时处于阻塞态因此A运行。在2-3阶段中A运行了一段时间后B的延时结束B开始抢占A进行运行。在2-3阶段中B运行了一段时间后C的延时结束C开始抢占B进行运行。以上是正常的抢占流程。
在C抢占B之后尝试获取互斥量但互斥量已经被A所获取还未进行释放因此A进行优先级的继承此时A的优先级变成了3即4 - 5阶段。在5-6阶段中A的优先级为3执行完成打印工作后对互斥量进行了解锁这时任务A的优先级变回原来的优先级1C任务抢占A开始执行。
3、谁上锁谁才能解锁实验
普通互斥量和二进制信号量并未实现谁上锁谁才能解锁这里使用普通互斥量与递归锁来进行对比来验证递归锁能够实现谁上锁谁才能解锁的功能。
具体代码实现如下
QueueHandle_t SemaphoreHandleTest;void PrintFunction(void *param){while(1){// /* 以下为互斥量测试 */
// xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);
// printf(%s,(char*)param);
// xSemaphoreGive(SemaphoreHandleTest);/* 以下为递归锁测试 */xSemaphoreTakeRecursive(SemaphoreHandleTest,portMAX_DELAY);printf(%s,(char*)param);xSemaphoreGiveRecursive(SemaphoreHandleTest);vTaskDelay(1);}
}
void BreakSemaphoreTask(void *param){vTaskDelay(1);while(1){// /* 以下为互斥量测试 */
// while(1){
//
// /* 如果获得不到锁就直接往里面放一把锁 */
// if(xSemaphoreTake(SemaphoreHandleTest,0) pdFALSE){
// xSemaphoreGive(SemaphoreHandleTest);
// }else{
// break;
// }
// }
// printf(this is break\r\n);
// xSemaphoreGive(SemaphoreHandleTest);/* 以下为递归锁测试 */while(1){/* 如果获得不到锁就直接往里面放一把锁 */if(xSemaphoreTakeRecursive(SemaphoreHandleTest,0) pdFALSE){xSemaphoreGiveRecursive(SemaphoreHandleTest);}else{break;}}printf(this is break\r\n);xSemaphoreGiveRecursive(SemaphoreHandleTest);vTaskDelay(1);}
}int main( void )
{TaskHandle_t xHandleTask1;TaskHandle_t xHandleTask2;TaskHandle_t xHandleTask3;prvSetupHardware();SerialPortInit();printf(UART TEST\r\n);// /* 创建互斥量初始值自动设置为 1 */
// SemaphoreHandleTest xSemaphoreCreateMutex();/* 创建递归锁初始值自动设置为 1 */SemaphoreHandleTest xSemaphoreCreateRecursiveMutex();xTaskCreate(PrintFunction,TaskA,100,(void*)this is TaskA\r\n,1,xHandleTask1);xTaskCreate(PrintFunction,TaskB,100,(void*)this is TaskB\r\n,1,xHandleTask2);xTaskCreate(BreakSemaphoreTask,TaskC,100,(void*)NULL,1,xHandleTask3);vTaskStartScheduler();return 0;
}
互斥量的运行结果如下 可以看到打印结果乱套了。这是因为在A上锁之后C任务去放进去了一把锁导致B任务原来在阻塞突然获得了锁执行了B任务中的printf。因此互斥量并没有实现谁上锁谁才能够解锁的功能。
递归锁的运行结果如下 可以看到打印变得非常正常。这是因为递归锁可以实现谁上锁谁才能够解锁因此不能够再C中解开A的锁从而执行结果变得正常。
4、递归上锁实验
在“ 谁上锁谁才能解锁实验 ”的代码基础上进行修改将打印函数修改为如下情况
void PrintFunction(void *param){int i0;while(1){// /* 以下为互斥量测试 */
// xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);
// printf(%s,(char*)param);
// xSemaphoreGive(SemaphoreHandleTest);/* 以下为递归锁测试 */xSemaphoreTakeRecursive(SemaphoreHandleTest,portMAX_DELAY);for(i0;i5;i){/* 递归上锁上锁之后再上一次锁 */xSemaphoreTakeRecursive(SemaphoreHandleTest,portMAX_DELAY);printf(lock:%i\r\n,i);xSemaphoreGiveRecursive(SemaphoreHandleTest);}printf(%s,(char*)param);xSemaphoreGiveRecursive(SemaphoreHandleTest);vTaskDelay(1);}
}
运行结果如下 可以看到lock0~4正常打印说明并未产生阻塞的情况递归锁可以递归上锁。