互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”

Posted 华为云开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”相关的知识,希望对你有一定的参考价值。

摘要:本文带领大家一起剖析鸿蒙轻内核的互斥锁模块的源代码,包含互斥锁的结构体、互斥锁池初始化、互斥锁创建删除、申请释放等。

本文分享自华为云社区《鸿蒙轻内核M核源码分析系列十 互斥锁Mutex》,原文作者:zhushy 。

多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,只能被独占使用。鸿蒙轻内核使用互斥锁来避免这种冲突,互斥锁是一种特殊的二值性信号量,用于实现对临界资源的独占式处理。另外,互斥锁可以解决信号量存在的优先级翻转问题。用互斥锁处理临界资源的同步访问时,如果有任务访问该资源,则互斥锁为加锁状态。此时其他任务如果想访问这个临界资源则会被阻塞,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的完整性。

本文我们来一起学习下鸿蒙轻内核互斥锁模块的源代码,本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/openharmony... 获取。

接下来,我们看下互斥锁的结构体,互斥锁初始化,互斥锁常用操作的源代码。

1、互斥锁结构体定义和常用宏定义

1.1 互斥锁结构体定义

在文件kernel\\include\\los_mux.h定义的互斥锁控制块结构体LosMuxCB,源代码如下,结构体成员的解释见注释部分。

typedef struct {
    UINT8 muxStat;       /**< 互斥锁状态:OS_MUX_UNUSED, OS_MUX_USED */
    UINT16 muxCount;     /**< 锁被持有的次数 */
    UINT32 muxID;        /**< 互斥锁Id */
    LOS_DL_LIST muxList; /**< 互斥锁双向链表 */
    LosTaskCB *owner;    /**< 当前持有锁的任务 */
    UINT16 priority;     /**< 当前持有锁的任务的优先级,为避免优先级翻转,可能会更改任务的优先级,此时有备份的作用 */
} LosMuxCB;

1.2 互斥锁常用宏定义

系统支持创建多少互斥锁是根据开发板情况使用宏LOSCFG_BASE_IPC_MUX_LIMIT定义的,互斥锁muxId是UINT32类型的,muxId取值为[0,LOSCFG_BASE_IPC_MUX_LIMIT),表示互斥锁池中各个的互斥锁的编号。

⑴处、⑵处的宏表示互斥锁的未使用、使用状态值。⑶处从互斥锁池中获取指定互斥锁muxid对应的互斥锁控制块。⑷处根据互斥锁双向链表中的链表节点指针ptr获取互斥锁控制块结构体指针。

⑴    #define OS_MUX_UNUSED 0

⑵    #define OS_MUX_USED   1

⑶    #define GET_MUX(muxid) (((LosMuxCB *)g_allMux) + (muxid))

⑷    #define GET_MUX_LIST(ptr) LOS_DL_LIST_ENTRY(ptr, LosMuxCB, muxList)

2、互斥锁初始化

互斥锁在内核中默认开启,用户可以通过宏LOSCFG_BASE_IPC_MUX进行关闭。开启互斥锁的情况下,在系统启动时,在kernel\\src\\los_init.c中调用OsMuxInit()进行互斥锁模块初始化。

下面,我们分析下互斥锁初始化的代码。

⑴初始化双向循环链表g_unusedMuxList,维护未使用的互斥锁。⑵处如果没有设置宏LOSCFG_BASE_IPC_MUX,则返回错误码。⑶为互斥锁申请内存,如果申请失败,则返回错误LOS_ERRNO_MUX_NO_MEMORY
⑷循环每一个互斥锁进行初始化,为每一个互斥锁节点指定索引muxID,muxStat为未使用OS_MUX_UNUSED,并把互斥锁节点插入未使用互斥锁双向链表g_unusedMuxList。
⑷如果开启了互斥锁调测开关,则调用函数UINT32 OsMuxDbgInit(VOID)进行初始化。

LITE_OS_SEC_TEXT_INIT UINT32 OsMuxInit(VOID)
{
    LosMuxCB *muxNode = NULL;
    UINT32 index;

⑴  LOS_ListInit(&g_unusedMuxList);

⑵  if (LOSCFG_BASE_IPC_MUX_LIMIT == 0) {
        return LOS_ERRNO_MUX_MAXNUM_ZERO;
    }

⑶  g_allMux = (LosMuxCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_MUX_LIMIT * sizeof(LosMuxCB)));
    if (g_allMux == NULL) {
        return LOS_ERRNO_MUX_NO_MEMORY;
    }

⑷  for (index = 0; index < LOSCFG_BASE_IPC_MUX_LIMIT; index++) {
        muxNode = ((LosMuxCB *)g_allMux) + index;
        muxNode->muxID = index;
        muxNode->muxStat = OS_MUX_UNUSED;
        LOS_ListTailInsert(&g_unusedMuxList, &muxNode->muxList);
    }
    return LOS_OK;
}

3、互斥锁常用操作

3.1 互斥锁创建

我们可以使用函数UINT32 LOS_MuxCreate(UINT32 *muxHandle)来创建互斥锁,下面通过分析源码看看如何创建互斥锁的。

⑴判断未使用互斥锁链表g_unusedMuxList是否为空,如果没有可以使用的互斥锁,跳转到错误码。⑵处如果g_unusedMuxList不为空,则获取第一个可用的互斥锁节点,接着从双向链表g_unusedMuxList中删除,然后调用GET_MUX_LIST宏函数获取LosMuxCB muxCreated,接着初始化创建的互斥锁信息,包含持有锁的次数、状态、优先级等信息。⑶初始化双向链表&muxCreated->muxList,阻塞在这个互斥锁上的任务会挂在这个链表上。⑷赋值给输出参数muxHandle,后续程序使用这个互斥锁Id对互斥锁进行其他操作。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_MuxCreate(UINT32 *muxHandle)
{
    UINT32 intSave;
    LosMuxCB *muxCreated = NULL;
    LOS_DL_LIST *unusedMux = NULL;
    UINT32 errNo;
    UINT32 errLine;

    if (muxHandle == NULL) {
        return LOS_ERRNO_MUX_PTR_NULL;
    }

    intSave = LOS_IntLock();
⑴  if (LOS_ListEmpty(&g_unusedMuxList)) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_ALL_BUSY);
    }

⑵  unusedMux = LOS_DL_LIST_FIRST(&(g_unusedMuxList));
    LOS_ListDelete(unusedMux);
    muxCreated = (GET_MUX_LIST(unusedMux));
    muxCreated->muxCount = 0;
    muxCreated->muxStat = OS_MUX_USED;
    muxCreated->priority = 0;
    muxCreated->owner = (LosTaskCB *)NULL;
⑶  LOS_ListInit(&muxCreated->muxList);
⑷  *muxHandle = (UINT32)muxCreated->muxID;
    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_MUX_CREATE, muxCreated);
    return LOS_OK;
ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}

3.2 互斥锁删除

我们可以使用函数LOS_MuxDelete(UINT32 muxHandle)来删除互斥锁,下面通过分析源码看看如何删除互斥锁的。

⑴处判断互斥锁muxHandle是否超过LOSCFG_BASE_IPC_MUX_LIMIT,如果超过则返回错误码。⑵获取互斥锁控制块LosMuxCB *muxDeleted。⑶如果要删除的互斥锁处于未使用状态,跳转到错误标签进行处理。⑷如果互斥锁的持有者数量不为空,不允许删除,跳转到错误标签进行处理。⑸把删除的互斥锁回收到未使用互斥锁双向链表g_unusedMuxList,然后更新为未使用状态。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_MuxDelete(UINT32 muxHandle)
{
    UINT32 intSave;
    LosMuxCB *muxDeleted = NULL;
    UINT32 errNo;
    UINT32 errLine;

⑴  if (muxHandle >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) {
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_INVALID);
    }

⑵  muxDeleted = GET_MUX(muxHandle);
    intSave = LOS_IntLock();
⑶  if (muxDeleted->muxStat == OS_MUX_UNUSED) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_INVALID);
    }

⑷  if ((!LOS_ListEmpty(&muxDeleted->muxList)) || muxDeleted->muxCount) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_PENDED);
    }

⑸  LOS_ListAdd(&g_unusedMuxList, &muxDeleted->muxList);
    muxDeleted->muxStat = OS_MUX_UNUSED;

    LOS_IntRestore(intSave);

    OsHookCall(LOS_HOOK_TYPE_MUX_DELETE, muxDeleted);
    return LOS_OK;
ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}

3.3 互斥锁申请

我们可以使用函数UINT32 LOS_MuxPend(UINT32 muxHandle, UINT32 timeout)来请求互斥锁,需要的2个参数分别是互斥锁Id和等待时间timeout,单位Tick,取值范围为[0, LOS_WAIT_FOREVER]。

下面通过分析源码看看如何请求互斥锁的。

申请互斥锁时首先会进行互斥锁Id、参数的合法性校验,这些比较简单。⑴处代码获取当前运行的任务,⑵如果互斥锁没有被持有,更新互斥锁的持有次数、持有者信息和优先级,完成互斥锁的申请。⑶处如果互斥锁的持有次数不为0,并且被当前任务持有,可以持有次数加1,再次嵌套持有,完成互斥锁的申请。如果代码执行到⑷,说明申请的互斥锁被其他任务持有着,此时如果等待时间为0,则申请失败返回。⑸处更新当前任务阻塞在申请的互斥锁上。

⑹处代码表示在当前申请互斥锁的任务优先级高于持有互斥锁的任务优先级时,修改持有互斥锁的优先级为当前任务的优先级。通过这样的修改,可以避免优先级翻转。⑺处调用函数OsSchedTaskWait()更新当前任务的状态,设置等待时间,然后调用函数LOS_Schedule触发任务调度。后续程序暂时不再执行,需要等到可以获取互斥锁或者时间超时。

如果时间超时或者申请到互斥锁,系统重新调度到执行此任务,程序从⑻处继续执行。如果是时间超时,⑼处更新任务状态并返回码,申请互斥锁失败。如果成功申请到互斥锁,执行⑽,返回成功。

LITE_OS_SEC_TEXT UINT32 LOS_MuxPend(UINT32 muxHandle, UINT32 timeout)
{
    UINT32 intSave;
    LosMuxCB *muxPended = NULL;
    UINT32 retErr;
    LosTaskCB *runningTask = NULL;

    if (muxHandle >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) {
        OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
    }

    muxPended = GET_MUX(muxHandle);
    intSave = LOS_IntLock();
    retErr = OsMuxValidCheck(muxPended);
    if (retErr) {
        goto ERROR_MUX_PEND;
    }

⑴  runningTask = (LosTaskCB *)g_losTask.runTask;
⑵  if (muxPended->muxCount == 0) {
        muxPended->muxCount++;
        muxPended->owner = runningTask;
        muxPended->priority = runningTask->priority;
        LOS_IntRestore(intSave);
        goto HOOK;
    }

⑶  if (muxPended->owner == runningTask) {
        muxPended->muxCount++;
        LOS_IntRestore(intSave);
        goto HOOK;
    }

⑷  if (!timeout) {
        retErr = LOS_ERRNO_MUX_UNAVAILABLE;
        goto ERROR_MUX_PEND;
    }

⑸  runningTask->taskMux = (VOID *)muxPended;

⑹  if (muxPended->owner->priority > runningTask->priority) {
        (VOID)OsSchedModifyTaskSchedParam(muxPended->owner, runningTask->priority);
    }

⑺  OsSchedTaskWait(&muxPended->muxList, timeout);

    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_MUX_PEND, muxPended);
    LOS_Schedule();

⑻  intSave = LOS_IntLock();
    if (runningTask->taskStatus & OS_TASK_STATUS_TIMEOUT) {
⑼      runningTask->taskStatus &= (~OS_TASK_STATUS_TIMEOUT);
        retErr = LOS_ERRNO_MUX_TIMEOUT;
        goto ERROR_MUX_PEND;
    }

    LOS_IntRestore(intSave);
⑽  return LOS_OK;

HOOK:
    OsHookCall(LOS_HOOK_TYPE_MUX_PEND, muxPended);
    return LOS_OK;

ERROR_MUX_PEND:
    LOS_IntRestore(intSave);
    OS_RETURN_ERROR(retErr);
}

3.4 互斥锁释放

我们可以使用函数UINT32 LOS_MuxPost(UINT32 muxHandle)来释放互斥锁,下面通过分析源码看看如何释放互斥锁的。

释放互斥锁时首先会进行互斥锁Id、参数的合法性校验,这些比较简单,自行阅读即可。⑴处如果要释放的互斥锁没有被持有、或者不是被当前任务持有,返回错误码。⑵互斥锁的持有数量减1,如果不为0,当前任务嵌套持有该互斥锁,不需要调度,返回释放互斥锁成功。如果释放一次后,当前任务不再持有互斥锁,则执行⑶,如果持有互斥锁任务的优先级不等于互斥锁的备份优先级低,需要恢复当前任务的优先级。

⑷如果互斥锁上还有其他任务阻塞着,获取阻塞的任务resumedTask,该任务成功获取到互斥锁,然后执行⑸更新互斥锁的持有信息。执行⑹更新任务resumedTask的状态,然后调用函数LOS_Schedule触发调度。

LITE_OS_SEC_TEXT UINT32 LOS_MuxPost(UINT32 muxHandle)
{
    UINT32 intSave;
    LosMuxCB *muxPosted = GET_MUX(muxHandle);
    LosTaskCB *resumedTask = NULL;
    LosTaskCB *runningTask = NULL;

    intSave = LOS_IntLock();

    if ((muxHandle >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) ||
        (muxPosted->muxStat == OS_MUX_UNUSED)) {
        LOS_IntRestore(intSave);
        OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
    }

    runningTask = (LosTaskCB *)g_losTask.runTask;
⑴  if ((muxPosted->muxCount == 0) || (muxPosted->owner != runningTask)) {
        LOS_IntRestore(intSave);
        OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
    }

⑵  if (--(muxPosted->muxCount) != 0) {
        LOS_IntRestore(intSave);
        OsHookCall(LOS_HOOK_TYPE_MUX_POST, muxPosted);
        return LOS_OK;
    }

⑶  if ((muxPosted->owner->priority) != muxPosted->priority) {
        (VOID)OsSchedModifyTaskSchedParam(muxPosted->owner, muxPosted->priority);
    }

⑷  if (!LOS_ListEmpty(&muxPosted->muxList)) {
        resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(muxPosted->muxList)));

⑸      muxPosted->muxCount = 1;
        muxPosted->owner = resumedTask;
        muxPosted->priority = resumedTask->priority;
        resumedTask->taskMux = NULL;

⑹      OsSchedTaskWake(resumedTask);

        LOS_IntRestore(intSave);
        OsHookCall(LOS_HOOK_TYPE_MUX_POST, muxPosted);
        LOS_Schedule();
    } else {
        LOS_IntRestore(intSave);
    }

    return LOS_OK;
}

小结

本文带领大家一起剖析了鸿蒙轻内核的互斥锁模块的源代码,包含互斥锁的结构体、互斥锁池初始化、互斥锁创建删除、申请释放等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony... 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony... ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。

点击关注,第一时间了解华为云新鲜技术~

以上是关于互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”的主要内容,如果未能解决你的问题,请参考以下文章

一文带你剖析LiteOS互斥锁Mutex源代码

临界区(critical section 每个线程中访问 临界资源 的那段代码)和互斥锁(mutex)的区别(进程间互斥量共享内存虚拟地址)

LiteOS内核教程05 | 互斥锁(共享资源保护)

「技术干货」Linux内核中的互斥量——Mutex锁

线程同步方式之互斥量Mutex

理解互斥量和信号量