FreeRTOS——互斥量
文章目录
一、互斥量是什么
本质上就是二值信号量,只不过互斥量用于资源保护,而且具有“优先级继承”的特性。
提示:不能用在中断服务函数。
我们在学习二值信号量的时候知道,该类型信号量的计数值只有0和1,但是他有个特点是:任何任务都可以生产信号量和拿走信号量。举个不太恰当的例子:假设有一个停车位,现在有一辆车停在车位上,按理来说肯定是停车的人自己把车开走后别人才可以停在这吧?但如果把车位看作二值信号量的话,这时候别的车可以把停在车位的车推走,自己将车位空出来,然后给自己停或给别人停。这似乎不太合理吧?
所以,对于一些临界资源()的使用问题,我们引入互斥量。
互斥量有一个特性,谁拿走这个互斥量只能由他释放,也就是只有进行take的任务才可以give互斥量,别的任务不能give。
互斥量也被称为互斥锁,使用过程如下:
1.在创建一个互斥量时,初始值为1
2.当任务A想访问临界资源,先获得并占有互斥量,然后开始访问
3.如果任务B也想访问临界资源,也要先获得互斥量。但已经被别人占有了,于是只能进行阻塞
4.当任务A使用完毕,释放互斥量。这时任务B被唤醒、得到并占有互斥量,然后开始访问
5.任务B使用完毕,释放互斥量
那么,正常来说在任务A占有互斥量的过程中,其他任务都无法释放互斥量。但是经过多次尝试后发现,任务A占有互斥量的情况下,其他任务竟然也可释放互斥量???“谁take谁give”的特点似乎看起来只是程序员在写代码的时候自己需要注意的。
这时候就有小伙伴会问了:“那说到底这个功能二值信号量也可以完成啊,为什么还要多一个互斥量???”
OK!接下来让我们来看看它的专属特点。
二、优先级翻转和优先级继承
1.优先级翻转
假设有一个低优先级任务正在使用一个资源,然后一个高优先级的任务也想使用同一个资源,如果不进行give释放操作的话,这个高优先级任务是无法打断低优先级任务执行的,只能进入阻塞。这时候如果有一个中优先级的任务在执行代码,它会抢占低优先级任务的执行霸占CPU,这样就有一个情况:低优先级任务被阻塞,高优先级任务在等待资源也在执行不了,但中优先级的任务却在执行。也就是高优先级任务反而比中等优先级任务更晚执行,这就是优先级翻转。
2.优先级继承
为了解决优先级翻转的问题,互斥量具有一个它专属特性:优先级继承。
当一个高优先级任务被一个低优先级任务持有的互斥量阻塞时,低优先级任务临时提升到与高优先级任务相同的优先级。
这样做的目的是:
1.减少阻塞时间:确保低优先级任务可以快速完成对共享资源的使用,并释放互斥量。
2.让高优先级任务尽快执行:一旦低优先级任务释放了互斥量,高优先级任务就可以立即执行。
三、常用的API函数
1.创建
动态创建方法:
代码如下(示例):
SemaphoreHandle_t xSemaphoreCreateMutex( void );
参数:无
返回值:SemaphoreHandle_t :互斥量的句柄,便于操作它
静态创建方法:
代码如下(示例):
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
参数:需要先有一个StaticSemaphore_t结构体,并传入它的指针
返回值:SemaphoreHandle_t :互斥量的句柄,便于操作它
提示:要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:
#define configUSE_MUTEXES 1
2.give操作
其实和信号量用的giveAPI函数是一样的,不过这里也再写一次。
代码如下(示例):
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
3.take操作
也是和信号量一样的API函数。
代码如下(示例):
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait);
提示:互斥量不可以用在ISR中断服务程序!!!
4.删除操作
也是和信号量一样的API函数。
代码如下(示例):
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
四、互斥量使用示例
1.观察是否使用互斥量的区别
未使用互斥量:
代码如下(示例):
int main( void ){
prvSetupHardware();
xTaskCreate( vTask1, "vTask1", 1000, (void *)1, 1, NULL );
xTaskCreate( vTask2, "vTask2", 1000, (void *)2, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
return 0;
}
static void vTask1( void *pvParameters ){
/* 无限循环 */
for( ;; )
{
for (int i = 1; i <= 30; i++){
printf("%d ", i);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
static void vTask2( void *pvParameters ){
/* 无限循环 */
for( ;; )
{
for (char c = 'a'; c <= 'z'; c++){
printf("%c ", c);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
观察串口
此时不难看出,两个任务都需要在串口打印,没引入互斥量的情况下会出现混杂的现象发生。
使用互斥量:
代码如下(示例):
SemaphoreHandle_t xMutex;
int main( void ){
prvSetupHardware();
xMutex = xSemaphoreCreateMutex( );
if( xMutex != NULL )
{
xTaskCreate( vTask1, "vTask1", 1000, (void *)1, 1, NULL );
xTaskCreate( vTask2, "vTask2", 1000, (void *)2, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
printf("创建失败!");
}
return 0;
}
static void vTask1( void *pvParameters ){
/* 无限循环 */
for( ;; )
{
xSemaphoreTake(xMutex, portMAX_DELAY);
for (int i = 1; i <= 30; i++){
printf("%d ", i);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
xSemaphoreGive(xMutex);
}
static void vTask2( void *pvParameters ){
/* 无限循环 */
for( ;; )
{
xSemaphoreTake(xMutex, portMAX_DELAY);
for (char c = 'a'; c <= 'z'; c++){
printf("%c ", c);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
xSemaphoreGive(xMutex);
}
观察串口
引入互斥量后,一个任务take互斥量,然后把所有的信息发送完毕且give释放互斥量后,另一个任务才能进行take互斥量然后进行打印。
2.死锁问题
互斥量死锁是指两个或多个任务在等待彼此持有的资源时产生的一种情况,导致它们都无法继续执行。这通常发生在多个任务尝试以不同的顺序获取相同的一组互斥量时。
代码如下(示例):
SemaphoreHandle_t xMutex1;
SemaphoreHandle_t xMutex2;
int main( void ){
prvSetupHardware();
xMutex1 = xSemaphoreCreateMutex( );
xMutex2 = xSemaphoreCreateMutex( );
if( xMutex1&&xMutex2 != NULL )
{
xTaskCreate( vTask1, "vTaskA", 1000, NULL, 1, NULL );
xTaskCreate( vTask2, "vTaskB", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
printf("创建失败!");
}
return 0;
}
static void vTaskA( void *pvParameters ){
/* 无限循环 */
for( ;; )
{
xSemaphoreTake(mutex1, portMAX_DELAY); // 获取Mutex1
printf("ask A took Mutex1 /n");
vTaskDelay(100 / portTICK_PERIOD_MS);
xSemaphoreTake(mutex2, portMAX_DELAY); // 尝试获取Mutex2
printf("ask A took Mutex2 /n");
}
// 释放互斥量
xSemaphoreGive(mutex2);
xSemaphoreGive(mutex1);
}
static void vTask2( void *pvParameters ){
xSemaphoreTake(mutex2, portMAX_DELAY); // 获取Mutex2
printf("ask B took Mutex2 /n");
vTaskDelay(100 / portTICK_PERIOD_MS);
xSemaphoreTake(mutex1, portMAX_DELAY); // 尝试获取Mutex1
printf("ask B took Mutex1 /n");
// 释放互斥量
xSemaphoreGive(mutex1);
xSemaphoreGive(mutex2);
}
观察串口:
这个示例中,两个任务尝试以不同的顺序获取两个互斥量。如果您运行这个代码,您可能会发现系统最终陷入了死锁,因为每个任务都在等待另一个任务释放它需要的互斥量。
3.自我死锁
假设有一个任务A,他正在占用一个互斥量。假设在往下执行的代码中它调用一个函数,这个函数也要去获取同一个互斥量,于是这个函数无法执行,任务A也就进入阻塞休眠。如果任务A此时想要恢复,需要它自己来释放这个互斥量才行,但显然这是不可能的,所以出现了自我死锁的问题。
4.递归锁
如何解决自我死锁的问题?对此我们引出“递归锁”的概念:
它是一种特殊的互斥量,它允许同一个任务多次获取同一个互斥量,但需要注意的是,一个任务Take了几次就需要Give几次才能释放。
创建递归锁
代码如下(示例):
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
释放与获取
代码如下(示例):
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );
BaseType_t xSemaphoreTakeRecursive(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait);
总结
本文主要记录了FreeRTOS中的互斥量相关知识。