EidosOS实现(4)-互斥锁
1、摘要
在前文中,我们通过信号量机制实现了任务之间的等待与唤醒,并利用二值信号量对临界资源进行了简单保护。然而,这种方式并未对资源的“所有权”进行约束,也无法处理优先级反转等问题。在实际实时系统中,这些问题会直接影响系统的正确性与实时性。为此,引入了互斥量(Mutex)机制,对临界资源访问进行更加严格的管理。
2、互斥锁的实现
互斥量结构体由三部分组成:
locked:判断释放锁定owner:拥有该锁的任务(与信号量的差别,实现所有权)waitingList:等待互斥锁的链表
加锁操作中,先判断是不是递归锁定,若当前任务一直请求加锁则返回。然后判断锁定状态,若未锁定则获取锁,其他任务则无法获取。若锁定,则把任务自身添加到事件链表中,等待其他任务释放锁从该链表中提取任务并执行。
解锁操作中,先判断所有权是否是该任务的,不能出现其他任务解当前任务锁。然后从等待链表中唤醒等待的任务,若没有等待任务,锁变成未锁定状态。
typedef struct mutex
{
int locked; // 0表示未锁定,1表示已锁定
TCB_t *owner; // 当前拥有该互斥锁的任务
vList waitingList;; // 等待该互斥锁的任务链表
}Mutex_t;
void mutexLock(Mutex_t *mutex)
{
if (mutex->locked && mutex->owner == currentTCB)
{
// 递归锁定,直接返回
return;
}
__disable_irq(); // 进入临界区,禁止中断
if (mutex->locked == 0)
{
// 获取互斥锁
mutex->locked = 1;
mutex->owner = currentTCB;
}
else
{
// 删除当前任务在就绪链表中的状态节点
vListRemove(¤tTCB->stateListItem);
// 将当前任务事件节点添加到事件的等待链表中,按照优先级排序
vListInsert((vList *)&mutex->waitingList, ¤tTCB->eventListItem, 0);
// 阻塞当前任务
currentTCB->state = BLOCKED;
// 切换到下一个任务
taskYield();
}
__enable_irq(); // 退出临界区,允许中断
}
void mutexUnlock(Mutex_t *mutex)
{
TCB_t *wakeTask = NULL;
if (mutex->owner != currentTCB)
{
// 只有拥有互斥锁的任务才能解锁
return;
}
__disable_irq(); // 进入临界区,禁止中断
if (mutex->waitingList.itemNumber > 0)
{
// 从事件的等待链表中取出一个任务
ListItem_t *waitingItem = mutex->waitingList.end.next;
TCB_t *task = (TCB_t *)waitingItem->pvOwner;
// 将任务事件节点从等待链表中移除
vListRemove(waitingItem);
if (vListIsInList(&task->stateListItem)) // 如果任务状态节点还在延时链表中,先移除
{
vListRemove(&task->stateListItem);
}
// 设置任务状态为就绪
task->state = READY;
task->stateListItem.value = task->priority; // 更新节点值为优先级,便于就绪链表排序
// 将任务状态节点添加到就绪链表中
vListInsert(&readyList, &task->stateListItem, 0);
wakeTask = task;
}
if (wakeTask != NULL)
{
// 将互斥锁所有权转移给下一个任务
mutex->owner = wakeTask;
}
else
{
// 没有等待的任务,直接解锁
mutex->locked = 0;
mutex->owner = NULL;
}
__enable_irq(); // 退出临界区,允许中断
}
3、优先级反转
互斥锁中存在一个很著名的问题——优先级反转。意思是由于资源互斥的原因,有可能出现高优先级无法抢占低优先级的任务的情况,导致实时性受到影响,从而使得应用出现不符合预期的情况。
具体的情况假设由三个任务对应不同的优先级,任务A(最高优先级)、任务B(中等优先级)、任务C(最低优先级),任务A与任务C存在互斥资源竞争的关系,在某些条件下,C先获取到互斥锁,执行任务,A与B由于其他原因阻塞。下一时刻B从阻塞态切换到就绪态,这时发生抢占行为,B任务抢占C任务的CPU使用权,而C由于抢占还未释放互斥锁。下一时刻A从阻塞切换到就绪态,但是由于互斥资源被锁,于是只能继续阻塞,这时就出现A与B同时就绪的情况下,A却无法抢占B的情况,因为它无法获得互斥锁。所以看起来像是B的优先级高于A,出现了优先级反转的情况。
解决该问题的方法也比较成熟,对于获取到互斥锁的任务,当有其他更高优先级任务因为互斥锁的原因而阻塞时,提高其任务优先级到与这个更高优先级的任务相等,或是最大优先级,然后重新插入到就绪链表中,这时上述问题就变成A与C优先级最高,B最低,问题得以解决。
void mutexLock(Mutex_t *mutex)
{
//...
if (currentTCB->priority > mutex->owner->priority)
{
// 优先级继承
mutex->owner->stateListItem.value = mutex->owner->priority; // 更新节点值为新的优先级,便于就绪链表排序
// 如果互斥锁拥有者在就绪链表中,重新排序
if (vListIsInList(&mutex->owner->stateListItem))
{
vListRemove(&mutex->owner->stateListItem);
vListInsert(&readyList, &mutex->owner->stateListItem, 0);
}
//...
}
4、总结
本文主要介绍了互斥锁的实现以及它的优先级反转问题和解决方案,与上一篇信号量的实现很相似,只是它的结构体中的值表示是否锁定以及它的所有权归属问题,每次操作时都要判断所有权问题,以防其他任务对当前任务的锁进行操作。优先级反转问题及解决方案都是从比较成熟的RTOS中参考的,里面最核心的思路就是提高获取互斥锁的低优先级任务。