百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | 掘金 | harmony >
读本篇之前建议先读鸿蒙内核源码分析(总目录)之自旋锁篇.
图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能.
自旋锁 和 互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争.
自旋锁的特点是死守共享资源,拿不到锁,CPU选择循环等待,等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克.
互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题:
第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表.
第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式.
第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办?
答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被打回原形.所以一个任务的优先级会看情况时高时低.
第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级.
带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明.
enum {
LOS_MUX_PRIO_NONE = 0, //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队.
LOS_MUX_PRIO_INHERIT = 1, //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。
//当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级
LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义
};
enum {
LOS_MUX_NORMAL = 0, //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测)
LOS_MUX_RECURSIVE = 1, //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。
LOS_MUX_ERRORCHECK = 2, //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。
LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁
};
typedef struct {
//互斥锁的属性
UINT8 protocol; //协议
UINT8 prioceiling; //优先级上限
UINT8 type; //类型属性
UINT8 reserved; //保留字段
} LosMuxAttr;
typedef struct OsMux {
//互斥锁结构体
UINT32 magic; /**< magic number */ //魔法数字
LosMuxAttr attr; /**< Mutex attribute */ //互斥锁属性
LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上
LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别.
VOID *owner; /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务
UINT16 muxCount; /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次
} 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 如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。 本实例实现如下流程。 1.互斥锁解决的是任务间竞争共享内存的问题. 2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁. 3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务. 4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数. v44.03 (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | harmony | 掘金 > v43.03 (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | harmony | 掘金 > v42.03 (中断切换篇) | 中断切换到底在切换什么? < csdn | harmony | 掘金 > v41.03 (任务切换篇) | 汇编逐行注解分析任务上下文 < csdn | harmony | 掘金 > v40.03 (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | harmony | 掘金 > v39.03 (异常接管篇) | 社会很单纯,复杂的是人 < csdn | harmony | 掘金 > v38.03 (寄存器篇) | ARM所有寄存器一网打尽,不再神秘 < csdn | harmony | 掘金 > v37.03 (系统调用篇) | 全盘解剖系统调用实现过程 < csdn | harmony | 掘金 > v36.03 (工作模式篇) | CPU是韦小宝,有哪七个老婆? < csdn | harmony | 掘金 > v35.03 (时间管理篇) | Tick是操作系统的基本时间单位 < csdn | harmony | 掘金 > v34.03 (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | harmony | 掘金 > v33.03 (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | harmony | 掘金 > v32.03 (CPU篇) | 内核是如何描述CPU的? < csdn | harmony | 掘金 > v31.03 (定时器篇) | 内核最高优先级任务是谁? < csdn | harmony | 掘金 > v30.03 (事件控制篇) | 任务间多对多的同步方案 < csdn | harmony | 掘金 > v29.03 (信号量篇) | 信号量解决任务同步问题 < csdn | harmony | 掘金 > v28.03 (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | harmony | 掘金 > v27.03 (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | harmony | 掘金 > v26.03 (自旋锁篇) | 真的好想为自旋锁立贞节牌坊! < csdn | harmony | 掘金 > v25.03 (并发并行篇) | 怎么记住并发并行的区别? < csdn | harmony | 掘金 > v24.03 (进程概念篇) | 进程在管理哪些资源? < csdn | harmony | 掘金 > v23.02 (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | harmony | 掘金 > v22.02 (汇编基础篇) | CPU在哪里打卡上班? < csdn | harmony | 掘金 > v21.02 (线程概念篇) | 是谁在不断的折腾CPU? < csdn | harmony | 掘金 > v20.02 (用栈方式篇) | 栈是构建底层运行的基础 < csdn | harmony | 掘金 > v19.02 (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | harmony | 掘金 > v18.02 (源码结构篇) | 内核500问你能答对多少? < csdn | harmony | 掘金 > v17.02 (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | harmony | 掘金 > v16.02 (内存规则篇) | 内存管理到底在管什么? < csdn | harmony | 掘金 > v15.02 (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | harmony | 掘金 > v14.02 (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | harmony | 掘金 > v13.02 (源码注释篇) | 热爱是所有的理由和答案 < csdn | harmony | 掘金 > v12.02 (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | harmony | 掘金 > v11.02 (内存分配篇) | 内存有哪些分配方式? < csdn | harmony | 掘金 > v10.02 (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | harmony | 掘金 > v09.02 (调度故事篇) | 用故事说内核调度 < csdn | harmony | 掘金 > v08.02 (总目录) | 百万汉字注解 百篇博客分析 < csdn | harmony | 掘金 > v07.02 (调度机制篇) | 任务是如何被调度执行的? < csdn | harmony | 掘金 > v06.02 (调度队列篇) | 就绪队列对调度的作用 < csdn | harmony | 掘金 > v05.02 (任务管理篇) | 谁在让CPU忙忙碌碌? < csdn | harmony | 掘金 > v04.02 (任务调度篇) | 任务是内核调度的单元 < csdn | harmony | 掘金 > v03.02 (时钟任务篇) | 触发调度最大的动力来自哪里? < csdn | harmony | 掘金 > v02.02 (进程管理篇) | 进程是内核资源管理单元 < csdn | harmony | 掘金 > v01.09 (双向链表篇) | 谁是内核最重要结构体? < csdn | harmony | 掘金 > 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 关注「鸿蒙内核源码分析」公众号,百万汉字注解 + 百篇博客分析 => 深挖鸿蒙内核源码 各大站点搜 “鸿蒙内核源码分析” .欢迎转载,请注明出处.
如果没有任务阻塞于该互斥锁,则互斥锁释放成功。申请互斥锁主函数 OsMuxPendOp
//互斥锁的主体函数,由OsMuxlockUnsafe调用,互斥锁模块最重要的几个函数之一
//最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行.
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;//锁计数器清0
mutex->owner = NULL;//锁没有归属任务
LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去
}
if (mutex->muxCount == 0) {
//无task用锁时,肯定能拿到锁了.在里面返回
mutex->muxCount++; //互斥锁计数器加1
mutex->owner = (VOID *)runTask; //当前任务拿到锁
LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);//持有锁的任务改变了,节点挂到当前task的锁链表
if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {
//看保护协议的做法是怎样的?
LOS_BitmapSet(&runTask->priBitMap, runTask->priority);//1.priBitMap是记录任务优先级变化的位图,这里把任务当前的优先级记录在priBitMap
OsTaskPriModify(runTask, mutex->attr.prioceiling);//2.把高优先级的mutex->attr.prioceiling设为当前任务的优先级.
}//注意任务优先级有32个, 是0最高,31最低!!!这里等于提高了任务的优先级,目的是让其在下次调度中继续提高被选中的概率,从而快速的释放锁.
return LOS_OK;
}
//递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了
if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {
//第一种情况 runtask是锁持有方
mutex->muxCount++; //递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE)
return LOS_OK; //成功退出
}
//到了这里说明锁在别的任务那里,当前任务只能被阻塞了.
if (!timeout) {
//参数timeout表示等待多久再来拿锁
return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock
}
//自己要被阻塞,只能申请调度,让出CPU core 让别的任务上
if (!OsPreemptableInSched()) {
//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)
return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有
}
OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);//设置锁位图,尽可能的提高锁持有任务的优先级
owner = (LosTaskCB *)mutex->owner; //记录持有锁的任务
runTask->taskMux = (VOID *)mutex; //记下当前任务在等待这把锁
node = OsMuxPendFindPos(runTask, mutex);//在等锁链表中找到一个优先级比当前任务更低的任务
ret = OsTaskWait(node, timeout, TRUE);//task陷入等待状态 TRUE代表需要调度
if (ret == LOS_ERRNO_TSK_TIMEOUT) {
//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文
runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了
ret = LOS_ETIMEDOUT;//返回超时
}
if (timeout != LOS_WAIT_FOREVER) {
//不是永远等待的情况
OsMuxBitmapRestore(mutex, runTask, owner);//恢复锁的位图
}
return ret;
}
释放锁的主体函数 OsMuxPostOp
//是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的
//OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由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) {
//参数任务bitmap中最低的优先级不等于等待锁的任务优先级
LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);//把等待任务锁的任务的优先级记录在参数任务的bitmap中
}
} else if (taskCB->priBitMap != 0) {
//如果bitmap不等于0说明参数任务至少有任务调度的优先级
OsMuxPostOpSub(taskCB, mutex);//
}
}
mutex->muxCount = 1;//互斥锁数量为1
mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了
resumedTask->taskMux = NULL;//resumedTask不再等锁了
LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去
LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录
OsTaskWake(resumedTask);//resumedTask有了锁就唤醒它,因为当初在没有拿到锁时处于了pend状态
if (needSched != NULL) {
//如果不为空
*needSched = TRUE;//就走起再次调度流程
}
return LOS_OK;
}
编程实例
/* 互斥锁句柄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");
/* 任务休眠100Ticks */
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();
/* 创建任务1 */
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;
}
/* 创建任务2 */
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();
/* 休眠300Ticks */
LOS_TaskDelay(300);
/* 删除互斥锁 */
LOS_MuxDelete(g_testMux);
/* 删除任务1 */
ret = LOS_TaskDelete(g_testTaskId01);
if (ret != LOS_OK) {
printf("task1 delete failed .\n");
return LOS_NOK;
}
/* 删除任务2 */
ret = LOS_TaskDelete(g_testTaskId02);
if (ret != LOS_OK) {
printf("task2 delete failed .\n");
return LOS_NOK;
}
return LOS_OK;
}
结果验证
task2 try to get mutex, wait forever.
task2 get mutex g_testMux and suspend 100 ticks.
task1 try to get mutex, wait 10 ticks.
task1 timeout and try to get mutex, wait forever.
task2 resumed and post the g_testMux
task1 wait forever,get mutex g_testMux.
总结
鸿蒙源码百篇博客 往期回顾
参与贡献
喜欢请大方 点赞+关注+收藏 吧