HarmonyOS鸿蒙Next内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01

HarmonyOS鸿蒙Next内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01 本篇说清楚互斥锁

读本篇之前建议先读鸿蒙内核源码分析(总目录)之自旋锁篇.

内核中哪些地方会用到互斥锁?看图:

概述
自旋锁和互斥锁虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争.

互斥锁长什么样?

enum {
    LOS_MUX_PRIO_NONE = 0,
    LOS_MUX_PRIO_INHERIT = 1,
    LOS_MUX_PRIO_PROTECT = 2
};

enum {
    LOS_MUX_NORMAL = 0,
    LOS_MUX_RECURSIVE = 1,
    LOS_MUX_ERRORCHECK = 2,
    LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE
};

typedef struct { //互斥锁的属性
    UINT8 protocol;
    UINT8 prioceiling;
    UINT8 type;
    UINT8 reserved;
} LosMuxAttr;

typedef struct OsMux { //互斥锁结构体
    UINT32 magic; 
    LosMuxAttr attr;
    LOS_DL_LIST holdList;
    LOS_DL_LIST muxList;
    VOID *owner;
    UINT16 muxCount;
} LosMux;

这互斥锁长的明显的比自旋锁丰满多啦,还记得自旋锁的样子吗,就一个变量,单薄到令人心疼.

初始化

LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr)
{
    SCHEDULER_LOCK(intSave);
    mutex->muxCount = 0;
    mutex->owner = NULL;
    LOS_ListInit(&mutex->muxList);
    mutex->magic = OS_MUX_MAGIC;
    SCHEDULER_UNLOCK(intSave);
    return LOS_OK;
}

留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务.

三种申请模式
申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。

无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。

永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。

定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。

如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。如果没有任务阻塞于该互斥锁,则互斥锁释放成功。

申请互斥锁主函数 OsMuxPendOp

STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux *mutex, UINT32 timeout)
{
    UINT32 ret;
    LOS_DL_LIST *node = NULL;
    LosTaskCB *owner = NULL;

    if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {
        /* This is for mutex macro initialization. */
        mutex->muxCount = 0;
        mutex->owner = NULL;
        LOS_ListInit(&mutex->muxList);
    }

    if (mutex->muxCount == 0) {
        mutex->muxCount++;
        mutex->owner = (VOID *)runTask;
        LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);
        if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {
            LOS_BitmapSet(&runTask->priBitMap, runTask->priority);
            OsTaskPriModify(runTask, mutex->attr.prioceiling);
        }
        return LOS_OK;
    }

    if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {
        mutex->muxCount++;
        return LOS_OK;
    }

    if (!timeout) {
        return LOS_EINVAL;
    }

    if (!OsPreemptableInSched()) {
        return LOS_EDEADLK;
    }

    OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);

    owner = (LosTaskCB *)mutex->owner;
    runTask->taskMux = (VOID *)mutex;
    node = OsMuxPendFindPos(runTask, mutex);
    ret = OsTaskWait(node, timeout, TRUE);
    if (ret == LOS_ERRNO_TSK_TIMEOUT) {
        runTask->taskMux = NULL;
        ret = LOS_ETIMEDOUT;
    }

    if (timeout != LOS_WAIT_FOREVER) {
        OsMuxBitmapRestore(mutex, runTask, owner);
    }

    return ret;
}

释放锁的主体函数 OsMuxPostOp

STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched)
{
    LosTaskCB *resumedTask = NULL;

    if (LOS_ListEmpty(&mutex->muxList)) {
        LOS_ListDelete(&mutex->holdList);
        mutex->owner = NULL;
        return LOS_OK;
    }

    resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));
    if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {
        if (resumedTask->priority > taskCB->priority) {
            if (LOS_HighBitGet(taskCB->priBitMap) != resumedTask->priority) {
                LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);
            }
        } else if (taskCB->priBitMap != 0) {
            OsMuxPostOpSub(taskCB, mutex);
        }
    }
    mutex->muxCount = 1;
    mutex->owner = (VOID *)resumedTask;
    resumedTask->taskMux = NULL;
    LOS_ListDelete(&mutex->holdList);
    LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);
    OsTaskWake(resumedTask);
    if (needSched != NULL) {
        *needSched = TRUE;
    }

    return LOS_OK;
}

编程实例 本实例实现如下流程。

  • 任务Example_TaskEntry创建一个互斥锁,锁任务调度,创建两个任务Example_MutexTask1、Example_MutexTask2。Example_MutexTask2优先级高于Example_MutexTask1,解锁任务调度,然后Example_TaskEntry任务休眠300Tick。
  • Example_MutexTask2被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick,Example_MutexTask2挂起,Example_MutexTask1被唤醒。
  • Example_MutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。10Tick超时时间到达后,Example_MutexTask1被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。
  • 100Tick休眠时间到达后,Example_MutexTask2被唤醒, 释放互斥锁,唤醒Example_MutexTask1。Example_MutexTask1成功获取到互斥锁后,释放锁。
  • 300Tick休眠时间到达后,任务Example_TaskEntry被调度运行,删除互斥锁,删除两个任务。
/* 互斥锁句柄id */
UINT32 g_testMux;
/* 任务ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;

VOID Example_MutexTask1(VOID)
{
    UINT32 ret;

    printf("task1 try to get  mutex, wait 10 ticks.\n");
    ret = LOS_MuxPend(g_testMux, 10);

    if (ret == LOS_OK) {
        printf("task1 get mutex g_testMux.\n");
        LOS_MuxPost(g_testMux);
        return;
    } else if (ret == LOS_ERRNO_MUX_TIMEOUT ) {
        printf("task1 timeout and try to get mutex, wait forever.\n");
        ret = LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);
        if (ret == LOS_OK) {
            printf("task1 wait forever, get mutex g_testMux.\n");
            LOS_MuxPost(g_testMux);
            return;
        }
    }
    return;
}

VOID Example_MutexTask2(VOID)
{
    printf("task2 try to get  mutex, wait forever.\n");
    (VOID)LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);

    printf("task2 get mutex g_testMux and suspend 100 ticks.\n");

    LOS_TaskDelay(100);

    printf("task2 resumed and post the g_testMux\n");
    LOS_MuxPost(g_testMux);
    return;
}

UINT32 Example_TaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S task1;
    TSK_INIT_PARAM_S task2;

    LOS_MuxCreate(&g_testMux);

    LOS_TaskLock();

    memset(&task1, 0, sizeof(TSK_INIT_PARAM_S));
    task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask1;
    task1.pcName      = "MutexTsk1";
    task1.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task1.usTaskPrio  = 5;
    ret = LOS_TaskCreate(&g_testTaskId01, &task1);
    if (ret != LOS_OK) {
        printf("task1 create failed.\n");
        return LOS_NOK;
    }

    memset(&task2, 0, sizeof(TSK_INIT_PARAM_S));
    task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask2;
    task2.pcName      = "MutexTsk2";
    task2.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task2.usTaskPrio  = 4;
    ret = LOS_TaskCreate(&g_testTaskId02, &task2);
    if (ret != LOS_OK) {
        printf("task2 create failed.\n");
        return LOS_NOK;
    }

    LOS_TaskUnlock();
    LOS_TaskDelay(300);

    LOS_MuxDelete(g_testMux);

    ret = LOS_TaskDelete(g_testTaskId01);
    if (ret != LOS_OK) {
        printf("task1 delete failed .\n");
        return LOS_NOK;
    }
    ret = LOS_TaskDelete(g_testTaskId02);
    if (ret != LOS_OK) {
        printf("task2 delete failed .\n");
        return LOS_NOK;
    }

    return LOS_OK;
}

结果验证

task2  to get  mutex wait forever
task2 get mutex g_testMux  suspend ticks
task1  to get  mutex wait ticks
task1 timeout  to get mutex wait forever
task2 resumed  post the g_testMux
task1 wait foreverget mutex g_testMux

总结
1.互斥锁解决的是任务间竞争共享内存的问题.
2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁.
3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务.
4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数.

11 回复

涨姿势啦

更多关于HarmonyOS鸿蒙Next内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


学习,必须学习下

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:BV1S4411E7LY/?p=17

真是牛人写的

拜读了,挺不错

喜欢这样的有深度的内容

我也要跟着一起学习啊,哈哈

受益非浅

百万汉字注解 + 百篇博客分析 -> 鸿蒙内核源码
https://gitee.com/weharmony/kernel_liteos_a_note

在HarmonyOS鸿蒙Next内核中,互斥锁(Mutex)是一种用于线程同步的机制,主要用于保护共享资源,防止多个线程同时访问导致数据不一致。与自旋锁不同,互斥锁在获取锁失败时,线程会进入阻塞状态,释放CPU资源,直到锁被释放后再被唤醒。

鸿蒙Next内核中的互斥锁实现较为复杂,涉及多个内核对象和调度机制。互斥锁的核心数据结构通常包括锁的状态、持有者线程、等待队列等。锁的状态可以是未锁定、锁定或等待状态。持有者线程指向当前持有锁的线程,等待队列则用于存放等待获取锁的线程。

在源码中,互斥锁的操作主要包括初始化、加锁、解锁和销毁。初始化函数用于设置互斥锁的初始状态,加锁函数尝试获取锁,若锁已被其他线程持有,则当前线程进入等待队列并阻塞。解锁函数释放锁,并唤醒等待队列中的线程。销毁函数用于释放互斥锁占用的资源。

鸿蒙Next内核的互斥锁实现还考虑了优先级反转问题,通过优先级继承机制,确保高优先级线程能够尽快获取锁,避免低优先级线程长时间持有锁导致系统性能下降。

总体而言,鸿蒙Next内核的互斥锁实现较为完善,能够有效管理线程间的资源竞争,确保系统的稳定性和高效性。

在HarmonyOS鸿蒙Next内核源码中,互斥锁(Mutex)的实现比自旋锁(Spinlock)更为复杂和全面。互斥锁用于保护共享资源,确保同一时间只有一个线程可以访问。其核心机制包括线程阻塞、唤醒和优先级继承等。源码中通过mutex_lockmutex_unlock函数实现锁的获取和释放,内部使用等待队列管理阻塞线程。中文注解详细解释了每个关键步骤,如锁状态检查、线程调度和死锁预防,帮助开发者深入理解其工作原理。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!