HarmonyOS鸿蒙Next内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01
HarmonyOS鸿蒙Next内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01 本篇说清楚互斥锁
读本篇之前建议先读鸿蒙内核源码分析(总目录)之自旋锁篇.
内核中哪些地方会用到互斥锁?看图:
概述
自旋锁和互斥锁虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争.
互斥锁长什么样?
enum {
LOS_MUX_PRIO_NONE = 0,
LOS_MUX_PRIO_INHERIT = 1,
LOS_MUX_PRIO_PROTECT = 2
};
enum {
LOS_MUX_NORMAL = 0,
LOS_MUX_RECURSIVE = 1,
LOS_MUX_ERRORCHECK = 2,
LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE
};
typedef struct { //互斥锁的属性
UINT8 protocol;
UINT8 prioceiling;
UINT8 type;
UINT8 reserved;
} LosMuxAttr;
typedef struct OsMux { //互斥锁结构体
UINT32 magic;
LosMuxAttr attr;
LOS_DL_LIST holdList;
LOS_DL_LIST muxList;
VOID *owner;
UINT16 muxCount;
} 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<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。
如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。如果没有任务阻塞于该互斥锁,则互斥锁释放成功。
申请互斥锁主函数 OsMuxPendOp
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;
mutex->owner = NULL;
LOS_ListInit(&mutex->muxList);
}
if (mutex->muxCount == 0) {
mutex->muxCount++;
mutex->owner = (VOID *)runTask;
LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);
if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {
LOS_BitmapSet(&runTask->priBitMap, runTask->priority);
OsTaskPriModify(runTask, mutex->attr.prioceiling);
}
return LOS_OK;
}
if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {
mutex->muxCount++;
return LOS_OK;
}
if (!timeout) {
return LOS_EINVAL;
}
if (!OsPreemptableInSched()) {
return LOS_EDEADLK;
}
OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);
owner = (LosTaskCB *)mutex->owner;
runTask->taskMux = (VOID *)mutex;
node = OsMuxPendFindPos(runTask, mutex);
ret = OsTaskWait(node, timeout, TRUE);
if (ret == LOS_ERRNO_TSK_TIMEOUT) {
runTask->taskMux = NULL;
ret = LOS_ETIMEDOUT;
}
if (timeout != LOS_WAIT_FOREVER) {
OsMuxBitmapRestore(mutex, runTask, owner);
}
return ret;
}
释放锁的主体函数 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) {
LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);
}
} else if (taskCB->priBitMap != 0) {
OsMuxPostOpSub(taskCB, mutex);
}
}
mutex->muxCount = 1;
mutex->owner = (VOID *)resumedTask;
resumedTask->taskMux = NULL;
LOS_ListDelete(&mutex->holdList);
LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);
OsTaskWake(resumedTask);
if (needSched != NULL) {
*needSched = TRUE;
}
return LOS_OK;
}
编程实例 本实例实现如下流程。
- 任务Example_TaskEntry创建一个互斥锁,锁任务调度,创建两个任务Example_MutexTask1、Example_MutexTask2。Example_MutexTask2优先级高于Example_MutexTask1,解锁任务调度,然后Example_TaskEntry任务休眠300Tick。
- Example_MutexTask2被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick,Example_MutexTask2挂起,Example_MutexTask1被唤醒。
- Example_MutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。10Tick超时时间到达后,Example_MutexTask1被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。
- 100Tick休眠时间到达后,Example_MutexTask2被唤醒, 释放互斥锁,唤醒Example_MutexTask1。Example_MutexTask1成功获取到互斥锁后,释放锁。
- 300Tick休眠时间到达后,任务Example_TaskEntry被调度运行,删除互斥锁,删除两个任务。
/* 互斥锁句柄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");
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();
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;
}
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();
LOS_TaskDelay(300);
LOS_MuxDelete(g_testMux);
ret = LOS_TaskDelete(g_testTaskId01);
if (ret != LOS_OK) {
printf("task1 delete failed .\n");
return LOS_NOK;
}
ret = LOS_TaskDelete(g_testTaskId02);
if (ret != LOS_OK) {
printf("task2 delete failed .\n");
return LOS_NOK;
}
return LOS_OK;
}
结果验证
task2 to get mutex wait forever
task2 get mutex g_testMux suspend ticks
task1 to get mutex wait ticks
task1 timeout to get mutex wait forever
task2 resumed post the g_testMux
task1 wait foreverget mutex g_testMux
总结
1.互斥锁解决的是任务间竞争共享内存的问题.
2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁.
3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务.
4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数.
涨姿势啦
更多关于HarmonyOS鸿蒙Next内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
学习,必须学习下
找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:BV1S4411E7LY/?p=17
真是牛人写的
拜读了,挺不错
喜欢这样的有深度的内容
我也要跟着一起学习啊,哈哈
受益非浅
百万汉字注解 + 百篇博客分析 -> 鸿蒙内核源码
https://gitee.com/weharmony/kernel_liteos_a_note
在HarmonyOS鸿蒙Next内核中,互斥锁(Mutex)是一种用于线程同步的机制,主要用于保护共享资源,防止多个线程同时访问导致数据不一致。与自旋锁不同,互斥锁在获取锁失败时,线程会进入阻塞状态,释放CPU资源,直到锁被释放后再被唤醒。
鸿蒙Next内核中的互斥锁实现较为复杂,涉及多个内核对象和调度机制。互斥锁的核心数据结构通常包括锁的状态、持有者线程、等待队列等。锁的状态可以是未锁定、锁定或等待状态。持有者线程指向当前持有锁的线程,等待队列则用于存放等待获取锁的线程。
在源码中,互斥锁的操作主要包括初始化、加锁、解锁和销毁。初始化函数用于设置互斥锁的初始状态,加锁函数尝试获取锁,若锁已被其他线程持有,则当前线程进入等待队列并阻塞。解锁函数释放锁,并唤醒等待队列中的线程。销毁函数用于释放互斥锁占用的资源。
鸿蒙Next内核的互斥锁实现还考虑了优先级反转问题,通过优先级继承机制,确保高优先级线程能够尽快获取锁,避免低优先级线程长时间持有锁导致系统性能下降。
总体而言,鸿蒙Next内核的互斥锁实现较为完善,能够有效管理线程间的资源竞争,确保系统的稳定性和高效性。
在HarmonyOS鸿蒙Next内核源码中,互斥锁(Mutex)的实现比自旋锁(Spinlock)更为复杂和全面。互斥锁用于保护共享资源,确保同一时间只有一个线程可以访问。其核心机制包括线程阻塞、唤醒和优先级继承等。源码中通过mutex_lock
和mutex_unlock
函数实现锁的获取和释放,内部使用等待队列管理阻塞线程。中文注解详细解释了每个关键步骤,如锁状态检查、线程调度和死锁预防,帮助开发者深入理解其工作原理。