概念
互斥量是二进制信号量的一个变种,开启互斥量需要在头文件FreeRTOSConfig.h设置configUSE_MUTEXES 为1。互斥量和信号量的主要区别如下
必须被独占地使用的全局变量、函数代码,它们被称为临界资源。
互斥量也被称为互斥锁,使用过程如下:
互斥量初始值为 1
任务 A 想访问临界资源,先获得并占有互斥量,然后开始访问
任务 B 也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
任务 A 使用完毕,释放互斥量;任务 B 被唤醒、得到并占有互斥量,然后开始访问临界资源
任务 B 使用完毕,释放互斥量
互斥型信号量必须是同一个任务申请,同一个任务释放,其他任务释放无效。同一个任务可以递归申请。
二进制信号量,一个任务申请成功后,可以由另一个任务释放。
二进制信号量实现任务互斥:
打印机资源只有一个,abc三个任务共享,当a取得使用权后,为了防止其他任务错误地释放了信号量(),必须将打印机房的门关起来(进入临界段),用完后,释放信号量,再把门打开(出临界段),其他任务再进去打印。(而互斥型信号量由于必须由取得信号量的那个任务释放,故不会出现其他任务错误地释放了信号量的情况出现,故不需要有临界段。互斥型信号量是二进制信号量的子集。)
二进制信号量实现任务同步:
a任务一直等待信号量,b任务定时释放信号量,完成同步功能
互斥量操作的相关函数
要想使用互斥量,需要在配置文件 FreeRTOSConfig.h 中定义:
##define configUSE_MUTEXES 1
创建互斥量
动态创建一个互斥量:
SemaphoreHandle_txSemaphoreCreateMutex( void )
xSemaphoreCreateMutex()函数用于创建互斥量
静态创建互斥量:
SemaphoreHandle_txSemaphoreCreateMutexStatic( StaticSemaphore_t*pxMutexBuffer );
创建一个互斥量,返回它的句柄。
此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t 结构体,并传入它的指针
返回值: 返回句柄,非 NULL 表示成功
要注意的是,互斥量不能在 ISR 中使用。
其他函数
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
*/
voidvSemaphoreDelete( SemaphoreHandle_txSemaphore );
/* 释放 */
BaseType_txSemaphoreGive( SemaphoreHandle_txSemaphore );
/* 释放(ISR 版本) */
BaseType_txSemaphoreGiveFromISR(
SemaphoreHandle_txSemaphore,BaseType_t*pxHigherPriorityTaskWoken
);
/* 获得 */
BaseType_txSemaphoreTake(
SemaphoreHandle_txSemaphore,
TickType_txTicksToWait
);
/* 获得(ISR 版本) */
xSemaphoreGiveFromISR(
SemaphoreHandle_txSemaphore,
BaseType_t*pxHigherPriorityTaskWoken
);
互斥量的应用伪代码
使用互斥量时有如下特点:
首先申明个互斥量的全局变量
SemaphoreHandle_t xMutex;
然后在main函数中创建互斥量
intmain(void)
{
...
xMutex=xSemaphoreCreateMutex();
...
}
voidFunction_Resource(voidconst*argument)
{
//要保护的资源函数
...
xSemaphoreTake( xMutex, portMAX_DELAY );
{
//对资源的处理
...
}
xSemaphoreGive( xMutex );
...
}
在上段代码中,如果有多个任务要调用资源函数的话,通过资源函数内部的互斥量机制可以保护资源不被其他任务打断。关于互斥量如何解决优先级倒置的问题,FreeRTOS为互斥量赋予了优先级继承(priority inheritance)的特性。优先级继承会暂时提高获得互斥量的任务的优先级,使得含有互斥量的任务的优先级和想要获取互斥量的任务中的最高优先级一样。互斥量无法彻底避免优先级倒置的问题,但能显著降低优先级倒置发生的概率。
死锁
假设有 2 个互斥量 M1、 M2, 2 个任务 A、 B:
A 获得了互斥量 M1
B 获得了互斥量 M2
A 还要获得互斥量 M2 才能运行,结果 A 阻塞
B 还要获得互斥量 M1 才能运行,结果 B 阻塞
A、 B 都阻塞,再无法释放它们持有的互斥量
死锁发生!
自我死锁
任务 A 获得了互斥锁 M
它调用一个库函数
库函数要去获取同一个互斥锁 M,于是它阻塞:任务 A 休眠,等待任务 A来释放互斥锁!
死锁发生!
递归锁
递归锁 | 一般互斥量 | |
创建 | xSemaphoreCreateRecursiveMutex | xSemaphoreCreateMutex |
获得 | xSemaphoreTakeRecursive | xSemaphoreTake |
释放 | xSemaphoreGiveRecursive | xSemaphoreGive |
/* 创建一个递归锁,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_txSemaphoreCreateRecursiveMutex( void );
/* 释放 */
BaseType_txSemaphoreGiveRecursive( SemaphoreHandle_txSemaphore );
/* 获得 */
BaseType_txSemaphoreTakeRecursive(
SemaphoreHandle_txSemaphore,
TickType_txTicksToWait
);
资源管理
资源管理(Resource Management)顾名思义,就是对资源的管理。资源在FreeRTOS中可以表现为一个负责保存数据的全局变量,一个队列上的数据等需要在任务之间共享的数据或者对UART串口的操作资源等。
资源管理主要包括两个方面内容--数据的同步机制和资源的保护机制。
资源的保护与互斥量
资源的保护是指,如何保护资源不被多个任务同时操作。如果一个保存数据的全局变量在被一个任务操作的过程中,又被多个更高优先级的任务抢占并处理,那么最后这个支离破碎的数据将会毫无意义,破坏了数据的独立完整性;如果一个任务在使用uart串口发送数据的时候,又被多个更高优先级的任务抢占串口资源并发送数据,那么最后串口发送的数据将只是一堆乱码毫无意义,会导致系统的不稳定。
信号量用于资源保护
在上图中,有个低优先级的任务(LP)和高优先级的任务(HP),两个任务都可以对一个资源进行操作。为了对资源进行保护采用了信号量的机制。LP首先获得信号量可以对资源进行操作,在时刻1,HP因为优先级高在内核调度中抢占了LP,在时刻2,HP想获得信号量但失败因此进入了阻塞状态。之后LP可以继续对资源操作,在时刻3执行完毕后归还了信号量。在时刻4,HP因为优先级高在内核调度中抢占了LP并获取了信号量可以对资源可以操作。
优先级倒置
在上面这个示例中,不同优先级的任务采用信号量机制可以独立地使用资源并不会被打断,因此保护了资源。但采用信号量保护资源的话会有个弊端,有时会产生一种现象叫做优先级倒置(Priority inversion )。下图将介绍这个现象。
这个示例和上图示例基本相同,就是多了一个优先级介于LP和HP之间的任务(MP),问题发生在时刻3。此时低优先级的任务获得了信号量和对资源的操作权,但被优先级更高的MP抢占了并执行。最高优先级的任务HP等待低优先级任务LP返还信号量,而低优先级因为被中等优先级的任务MD抢占无法返回信号量。从外部的视角看,此时中等优先级的任务MD相当于抢占了高优先级的任务HP,而高优先级的任务HP表现起来却又像是有最低的优先级。这种不正常的现象就被称为优先级倒置。优先级倒置是采用信号量保护资源可能会产生的负面效果。
关键区
关键区(Critical Section)是资源保护的另一种方法。在FreeRTOS有两个宏定义,分别是taskENTER_CRITICAL()和taskEXIT_CRITICAL()。
代码如下
taskENTER_CRITICAL();
...
taskEXIT_CRITICAL();
taskENTER_CRITICAL()宏定义会关闭所有中断包括内核切换所在的中断,taskEXIT_CRITICAL()宏定义会再次打开中断。所以处于宏定义之间的代码可以被独占地执行,这是保护资源的一种比较暴力的方式。