EidosOS实现(5)-互斥锁

Posted by WHX on April 6, 2026

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(&currentTCB->stateListItem);
        // 将当前任务事件节点添加到事件的等待链表中,按照优先级排序
        vListInsert((vList *)&mutex->waitingList, &currentTCB->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中参考的,里面最核心的思路就是提高获取互斥锁的低优先级任务。