互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”
摘要:本文带领大家一起剖析鸿蒙轻内核的互斥锁模块的源代码,包含互斥锁的结构体、互斥锁池初始化、互斥锁创建删除、申请释放等。
本文分享自华为云社区《鸿蒙轻内核M核源码分析系列十 互斥锁Mutex》,原文作者:zhushy 。
多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,只能被独占使用。鸿蒙轻内核使用互斥锁来避免这种冲突,互斥锁是一种特殊的二值性信号量,用于实现对临界资源的独占式处理。另外,互斥锁可以解决信号量存在的优先级翻转问题。用互斥锁处理临界资源的同步访问时,如果有任务访问该资源,则互斥锁为加锁状态。此时其他任务如果想访问这个临界资源则会被阻塞,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的完整性。
本文我们来一起学习下鸿蒙轻内核互斥锁模块的源代码,本文中所涉及的源码,以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定义的,互斥锁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/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。
互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”的更多相关文章
- 线程锁(互斥锁Mutex)及递归锁
一.线程锁(互斥锁) 在一个程序内,主进程可以启动很多个线程,这些线程都可以访问主进程的内存空间,在Python中虽然有了GIL,同一时间只有一个线程在运行,可是这些线程的调度都归系统,操作系统有自身 ...
- 带你熟悉鸿蒙轻内核Kconfig使用指南
摘要:本文介绍了Kconfig的基础知识,和鸿蒙轻内核的图形化配置及进阶的使用方法. 本文分享自华为云社区<鸿蒙轻内核Kconfig使用笔记>,作者: zhushy. 1. Kconfig ...
- 深入理解Solaris内核中互斥锁(mutex)与条件变量(condvar)之协同工作原理
在Solaris上写内核模块总是会用到互斥锁(mutex)与条件变量(condvar), 光阴荏苒日月如梭弹指一挥间,Solaris的大船说沉就要沉了,此刻心情不是太好(Orz).每次被年轻的有才华的 ...
- Linux内核互斥锁--mutex
一.定义: /linux/include/linux/mutex.h 二.作用及访问规则: 互斥锁主要用于实现内核中的互斥访问功能.内核互斥锁是在原子 API 之上实现的,但这对于内核用户是不可见 ...
- 鸿蒙轻内核源码分析:文件系统LittleFS
摘要:本文先介绍下LFS文件系统结构体的结构体和全局变量,然后分析下LFS文件操作接口. 本文分享自华为云社区<# 鸿蒙轻内核M核源码分析系列二一 02 文件系统LittleFS>,作者: ...
- 鸿蒙轻内核源码分析:文件系统FatFS
摘要:本文为大家介绍FatFS文件系统结构体的结构体和全局变量,并分析FatFS文件操作接口. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列二一 03 文件系统FatFS>,作者:zh ...
- 一文带你剖析LiteOS互斥锁Mutex源代码
摘要:多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,只能被独占使用.LiteOS使用互斥锁来避免这种冲突,互斥锁是一种特殊的二值性信号量,用于实现对临界资源的独占 ...
- 互斥锁Mutex与信号量Semaphore的区别
转自互斥锁Mutex与信号量Semaphore的区别 多线程编程中,常常会遇到这两个概念:Mutex和Semaphore,两者之间区别如下: 有人做过如下类比: Mutex是一把钥匙,一个人拿了就可进 ...
- 线程锁(互斥锁Mutex)
线程锁(互斥锁Mutex) 一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况? # -*- cod ...
- Golang 读写锁RWMutex 互斥锁Mutex 源码详解
前言 Golang中有两种类型的锁,Mutex (互斥锁)和RWMutex(读写锁)对于这两种锁的使用这里就不多说了,本文主要侧重于从源码的角度分析这两种锁的具体实现. 引子问题 我一般喜欢带着问题去 ...
随机推荐
- 探索CPU的黑盒子:解密指令执行的秘密
引言 在我们之前的章节中,我们着重讲解了CPU内部的处理过程,以及与之密切相关的数据总线知识.在这个基础上,我们今天将继续深入探讨CPU执行指令的相关知识,这对于我们理解计算机的工作原理至关重要. C ...
- 虹科案例 | Redis企业版数据库帮助金融机构满足客户需求
如今,传统银行与新兴银行正在进行激烈的竞争.随着苹果.亚马逊.谷歌等科技巨头正凭借其数字化.移动应用程序和云体验打入金融服务行业.为了进行公平竞争,传统银行也需要通过个性化的全渠道客户体验来实现交互式 ...
- 2022/7/26 暑期集训 pj组第6次%你赛
个人第3次 又是下午打,旁边那帮 不知好歹的 入门组小孩们又在吵吵... T1 老师是不是放反了? T1 是蓝题诶 理所应当地 跳过 然后就忘了写了,连样例也没打...样例可是有7分诶! 到现在也没写 ...
- java 处理常量字符串过长 & springboot 项目读取 resouces 文件夹下的文件内容
长字符串起因 项目里面有一长串的加密字符串(最长的万多个字符),需要拼接作为参数发送给第三方. 如果我们使用 枚举 定义的话,idea 编译的时候就会出现编译报错 Error: java:常量字符串过 ...
- 20.7 OpenSSL 套接字SSL加密传输
OpenSSL 中的 SSL 加密是通过 SSL/TLS 协议来实现的.SSL/TLS 是一种安全通信协议,可以保障通信双方之间的通信安全性和数据完整性.在 SSL/TLS 协议中,加密算法是其中最核 ...
- .net下功能强大的HTML解析库HtmlAgilityPack,数据抓取必备
HtmlAgilityPack是一个.NET平台下的HTML解析库,它可以将HTML文本转换为DOM文档对象,方便我们对HTML文本进行操作和分析.HtmlAgilityPack支持XPath语法,可 ...
- 【Android】做一个简单的每日打卡app-day01【还没做好】
任务: 第一阶段目标: 1.用户注册:用户注册信息包括用户ID(学号).用户名(姓名),手机号码,用户单位(班级),用户班级四项基本信息,用户第一次注册后,用户姓名不用每次输入 . 2.每日总结打卡: ...
- 时间复杂度为 O(n^2) 的排序算法
对于小规模数据,我们可以选用时间复杂度为 O(n2) 的排序算法.因为时间复杂度并不代表实际代码的执行时间,它省去了低阶.系数和常数,仅代表的增长趋势,所以在小规模数据情况下, O(n2) 的排序算法 ...
- Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
如果你对shiro有问题的话,请看这篇文章:Springboot+shiro,完整教程,带你学会shiro-CSDN博客 第一步,先准备数据库: 数据库需要准备三个表,一个user表,一个role表, ...
- 吉特日化MES & SQL Server中的数据类型
一. 整数数据类型 1.bit bit数据类型是整型,其值只能是0.1或空值.这种数据类型用于存储只有两种可能值的数据,如Yes 或No.True 或False .On 或Off.注意:很省空间的一种 ...