鸿蒙轻内核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

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

Java中最全最基础的IO流概述和简介案例分析

《Java中最全最基础的IO流概述和简介案例分析》JavaIO流用于程序与外部设备的数据交互,分为字节流(InputStream/OutputStream)和字符流(Reader/Writer),处理... 目录IO流简介IO是什么应用场景IO流的分类流的超类类型字节文件流应用简介核心API文件输出流应用文

java 恺撒加密/解密实现原理(附带源码)

《java恺撒加密/解密实现原理(附带源码)》本文介绍Java实现恺撒加密与解密,通过固定位移量对字母进行循环替换,保留大小写及非字母字符,由于其实现简单、易于理解,恺撒加密常被用作学习加密算法的入... 目录Java 恺撒加密/解密实现1. 项目背景与介绍2. 相关知识2.1 恺撒加密算法原理2.2 Ja

Nginx屏蔽服务器名称与版本信息方式(源码级修改)

《Nginx屏蔽服务器名称与版本信息方式(源码级修改)》本文详解如何通过源码修改Nginx1.25.4,移除Server响应头中的服务类型和版本信息,以增强安全性,需重新配置、编译、安装,升级时需重复... 目录一、背景与目的二、适用版本三、操作步骤修改源码文件四、后续操作提示五、注意事项六、总结一、背景与

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码