鸿蒙轻内核M核源码分析系列九 互斥锁Mutex

2024-06-06 16:52

本文主要是介绍鸿蒙轻内核M核源码分析系列九 互斥锁Mutex,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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


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

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定义的,互斥锁muxIdUINT32类型的,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
⑷循环每一个互斥锁进行初始化,为每一个互斥锁节点指定索引muxIDmuxStat为未使用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;
}

小结

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

如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

这篇关于鸿蒙轻内核M核源码分析系列九 互斥锁Mutex的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1036679

相关文章

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

你的华为手机升级了吗? 鸿蒙NEXT多连推5.0.123版本变化颇多

《你的华为手机升级了吗?鸿蒙NEXT多连推5.0.123版本变化颇多》现在的手机系统更新可不仅仅是修修补补那么简单了,华为手机的鸿蒙系统最近可是动作频频,给用户们带来了不少惊喜... 为了让用户的使用体验变得很好,华为手机不仅发布了一系列给力的新机,还在操作系统方面进行了疯狂的发力。尤其是近期,不仅鸿蒙O

如何安装HWE内核? Ubuntu安装hwe内核解决硬件太新的问题

《如何安装HWE内核?Ubuntu安装hwe内核解决硬件太新的问题》今天的主角就是hwe内核(hardwareenablementkernel),一般安装的Ubuntu都是初始内核,不能很好地支... 对于追求系统稳定性,又想充分利用最新硬件特性的 Ubuntu 用户来说,HWEXBQgUbdlna(Har

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结