一、基本概念
互斥量又称互斥信号量,是一种特殊的二值信号量
特殊之处在于:支持互斥量所有权、递归访问以及防止优先级翻转
互斥量有两种状态:开锁和闭锁状态;互斥量被任务持有时,处于闭锁状态,任务释放互斥量时,处于开锁状态
递归互斥量:持有该互斥量的任务能够再次获得这个锁而不被挂起
二、互斥量的优先级继承机制
1.优先级翻转:高优先级任务无法运行而低优先级可以运行的现象叫“优先级翻转”;
举例:有三个任务H任务、M任务、L任务,优先级高低分别为H>M>L,假设系统有一个受保护资源被L任务使用,某时刻H资源需要使用该资源,进入阻塞等待状态,L任务继续执行的过程中唤醒M任务,由于M任务比L任务高,M任务会标调用,M任务执行完后,L继续执行,最后L释放资源后,H才能执行
2.优先级继承机制
为了尽量避免优先级翻转的现象,采用一种优先级继承的机制:暂时将正在占有某种资源的低优先级任务的优先级提高,使得该任务的优先级与所有等待该资源的任务中最高优先级相等
举例:有三个任务H任务、M任务、L任务,优先级高低分别为H>M>L,假设系统有一个受保护资源被L任务使用,某时刻H资源需要使用该资源,进入阻塞等待状态,现将L任务优先级调高到H,即使L任务继续执行的过程中唤醒M任务,但是由于M任务优先级是M,L任务优先级是H,因此M任务被进入等待,L任务继续执行,最后L释放资源后,优先级还原为L,进行任务切换,H任务继续执行,任务切换,调用M任务,从而避免了优先级翻转的现象
三、互斥量的用用场景
由于互斥量以锁的形式存在,因此更适用于可能引起优先级翻转的情况
四、互斥量的运作机制
任务获得互斥量,互斥量立即变为闭锁状态,其他任务访问时,根据自定义的等待时间进行等待,直到互斥量被释放。
五、互斥量控制块
FreeRTOS的互斥量控制块结构体与消息队列结构体一样,只是其中某些成员变量代表的意义有所不一样
相比信号量的结构体多用了uxRecursiveCallCount,该成员变量用于表示递归互斥量被调用的次数
六、互斥量函数
1.互斥量创建函数xSemaphoreCreateMutex()
(1)创建队列长度为1,消息大小为0,消息类型为MUTEX的队列
(2)初始化互斥量
针对队列的成员变量进行了一下重定义替换工作,便于阅读,并将递归记录次数uxRecursiveCallCount置0,创建时先释放互斥量,使得互斥量为开锁状态
2.递归互斥量创建函数xSemorphoreCreateRecursiveMutex
非递归互斥量只能被同一任务获取一次,同一任务再获取一次会失败;递归互斥变量可以被同一任务获取很多次
递归互斥量创建与非递归互斥量创建函数基本一样,只是类型有所改变
3.互斥量删除函数
与普通信号量删除函数一样,调用vSemophoreDelete()函数即可
4.互斥量获取函数xSemaphoreTak()
主要调用消息队列里的通用接收函数xQueueGenericReceive函数
其中在消息队列获取函数里有针对互斥信号量做特殊处理的地方:
(1)互斥量获取成功,把当前任务用于统计获取互斥量的个数的变量uxMutexesHeld加1,将当前控制块pxCurrentTCB赋值给互斥量的pxMutexHolder成员变量
(2)任务获取互斥量失败,即互斥量是关锁无效状态,任务进入阻塞,进行优先级继承
优先级继承的思路是:判断互斥量拥有者任务优先级是否比当前任务优先级低,如果是,提升互斥量拥有者的优先级,即优先级继承,分两部分:一部分要提升的是互斥量拥有者在等待时间列表中的优先级需要调整,如果拥有者处于就绪列表中,需要先从就绪列表中移除,更新优先级之后,再插入就绪列表
5.递归互斥量获取函数xSemaphoreTakeRecursive()
当前获取递归互斥量的任务如果与该递归互斥量拥有者是同一个任务,则递归计数uxRecursiveCallCount加1,如果不是同一个任务,则与非递归互斥量获取一样,调用消息通用获取函数xQueueGenericReceive(),获取成功后,递归计数uxRecursiveCallCount加1,获取失败自动进入阻塞状态
递归互斥量与非递归互斥量的获取差别就在于,需要对uxRecursiveCallCount进行加1,如果是当前获取人与互斥量拥有任务不相同,那么都会进入任务阻塞状态
6.互斥量释放函数xSemaphoreGive()
互斥量释放函数与信号量释放函数一样,最终调用的都是消息队列通用发送函数xQueueGenericSend,在函数中针对互斥量做了特殊的处理:
即恢复任务的初始优先级,也就是去优先级继承
去优先级处理的思路:,将互斥量拥有者任务的互斥量计数变量uxMutexsHeld减1,优先级有进行过继承,则从状态列表中删除,恢复优先级,添加到就绪列表
7.递归互斥量释放函数xSemaphoreGiveRecursive()
递归互斥量释放函数,比非递归互斥量释放函数多一个递归计数uxRecursiveCallCount减1的动作,当该计数减为0的时候,就与非递归互斥量释放的逻辑是一样的,执行的是消息队列的通用消息释放函数xQueueGenericSend
学习心得:
1.理解互斥变量,一种特殊的二值信号量,创建的时候自动释放信号量,使得互斥信号量为开锁状态,另外任务控制块有一个用于统计拥有互斥量个数的成员变量uxMutexesHeld,在获取互斥信号量和释放互斥信号量的时候,需要针对该变量进行加减操作
2.优先级翻转现象:高优先级任务得不到运行,反而是低优先级的任务先运行
3.优先级继承:为了解决优先级翻转的现象,在互斥量获取的时候,将互斥量拥有者的任务优先级临时提高,待释放互斥量的时候,再还原回原来的优先级
4.递归互斥变量与非递归互斥变量:递归优先级允许同一任务多次获取递归互斥量,用uxRecursiveCallCount变量来统计