网站首页 > 技术文章 正文
这是互斥锁系列的第二篇,第一篇请见LiteOS内核源码分析系列七 互斥锁Mutex (1)
3.3 互斥锁申请
我们可以使用函数UINT32 LOS_MuxPend(UINT32 muxHandle, UINT32 timeout)来请求互斥锁,需要的2个参数分别是互斥锁Id和等待时间timeout,单位Tick,取值范围为[0, LOS_WAIT_FOREVER]。
下面通过分析源码看看如何请求互斥锁的。
申请互斥锁时首先会进行互斥锁Id、参数的合法性校验,这些比较简单。⑴处代码判断申请互斥锁的是否系统任务,如果是系统任务输出警告信息。⑵如果互斥锁没有被持有,更新互斥锁的持有次数和持有者信息,完成互斥锁的申请。⑶处如果互斥锁的持有次数不为0,并且被当前任务持有,可以持有次数加1,再次嵌套持有,完成互斥锁的申请。⑷如果等待时间为0,申请失败返回。⑸如果当前锁任务调度,不允许申请互斥锁,打印回溯栈并返回错误码。
能运行到⑹处,表示互斥锁已被其他任务持有。在当前申请互斥锁的任务优先级高于持有互斥锁的任务优先级时,修改持有互斥锁的优先级为当前任务的优先级,持有锁的任务优先级备份到成员变量muxPended->owner->priBitMap。通过这样的修改,可以避免优先级翻转。⑺处的函数OsMuxPendOp()下文继续分析。
LITE_OS_SEC_TEXT UINT32 LOS_MuxPend(UINT32 muxHandle, UINT32 timeout)
{
UINT32 ret;
UINT32 intSave;
LosMuxCB *muxPended = NULL;
LosTaskCB *runTask = NULL;
if (GET_MUX_INDEX(muxHandle) >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) {
OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
}
muxPended = GET_MUX(muxHandle);
LOS_TRACE(MUX_PEND, muxHandle, muxPended->muxCount,
((muxPended->owner == NULL) ? 0xFFFFFFFF : muxPended->owner->taskId), timeout);
SCHEDULER_LOCK(intSave);
ret = OsMuxParaCheck(muxPended, muxHandle);
if (ret != LOS_OK) {
goto OUT_UNLOCK;
}
runTask = OsCurrTaskGet();
⑴ if (runTask->taskFlags & OS_TASK_FLAG_SYSTEM) {
PRINT_DEBUG("Warning: DO NOT recommend to use %s in system tasks.\n", __FUNCTION__);
}
⑵ if (muxPended->muxCount == 0) {
OsMuxDlockNodeInsertHook(runTask->taskId, muxPended);
muxPended->muxCount++;
muxPended->owner = runTask;
goto OUT_UNLOCK;
}
⑶ if (muxPended->owner == runTask) {
muxPended->muxCount++;
goto OUT_UNLOCK;
}
⑷ if (!timeout) {
ret = LOS_ERRNO_MUX_UNAVAILABLE;
goto OUT_UNLOCK;
}
⑸ if (!OsPreemptableInSched()) {
ret = LOS_ERRNO_MUX_PEND_IN_LOCK;
PRINT_ERR("!!!LOS_ERRNO_MUX_PEND_IN_LOCK!!!\n");
OsBackTrace();
goto OUT_UNLOCK;
}
⑹ OsMuxBitmapSet(runTask, (MuxBaseCB *)muxPended);
⑺ ret = OsMuxPendOp(runTask, (MuxBaseCB *)muxPended, timeout, &intSave);
OUT_UNLOCK:
SCHEDULER_UNLOCK(intSave);
return ret;
}
接下来继续分析函数OsMuxPendOp(),⑴处设置申请互斥锁的任务的结构体成员变量runTask->taskMux为申请的互斥锁。⑵处获取互斥锁的双向链表,阻塞在请求这个互斥锁的任务都挂在这个链表上,后文详细分析这个函数。⑶处把申请互斥锁的任务改为非就绪状态、阻塞状态,插入到互斥锁的阻塞任务列表里。如果是非永久等待互斥锁,还需要把任务加入超时排序链表里。⑷触发任务调度,后续程序暂时不再执行,需要等到可以获取互斥锁或者时间超时。
如果时间超时或者申请到互斥锁,系统重新调度到执行此任务,程序从⑸处继续执行。如果是时间超时,⑹处更新任务状态并返回码,申请互斥锁失败。⑺如果成功申请到互斥锁,并且超时时间不等于LOS_WAIT_FOREVER,需要判断是否恢复任务优先级。
LITE_OS_SEC_TEXT UINT32 OsMuxPendOp(LosTaskCB *runTask, MuxBaseCB *muxPended, UINT32 timeout,
UINT32 *intSave)
{
LOS_DL_LIST *node = NULL;
UINT32 ret = LOS_OK;
LosTaskCB *owner = muxPended->owner;
⑴ runTask->taskMux = (VOID *)muxPended;
⑵ node = OsMuxPendFindPos(runTask, muxPended);
⑶ OsTaskWait(node, OS_TASK_STATUS_PEND, timeout);
⑷ OsSchedResched();
SCHEDULER_UNLOCK(*intSave);
⑸ SCHEDULER_LOCK(*intSave);
⑹ if (runTask->taskStatus & OS_TASK_STATUS_TIMEOUT) {
runTask->taskStatus &= ~OS_TASK_STATUS_TIMEOUT;
ret = LOS_ERRNO_MUX_TIMEOUT;
}
⑺ if (timeout != LOS_WAIT_FOREVER) {
OsMuxBitmapRestore(runTask, owner);
}
return ret;
}
接下来,分析下内部函数OsMuxPendFindPos()。LiteOS互斥锁支持2种等待模式,可以通过宏来配置:
- LOSCFG_MUTEX_WAITMODE_PRIO
互斥锁基于任务优先级的等待模式,阻塞在互斥锁的任务里,谁的优先级高,在互斥锁释放时,谁先获取到互斥锁。
- LOSCFG_MUTEX_WAITMODE_FIFO
互斥锁基于FIFO的等待模式,阻塞在互斥锁的任务里,谁先进入阻塞队列,在互斥锁释放时,谁先获取到互斥锁。
在开启宏LOSCFG_MUTEX_WAITMODE_FIFO,互斥锁基于FIFO的等待模式时,函数OsMuxPendFindPos()的源码比较简单,直接获取互斥锁的阻塞链表,在后续的OsTaskWait()函数里,会把任务挂在在获取的阻塞链表的尾部。代码如下:
LITE_OS_SEC_TEXT STATIC LOS_DL_LIST *OsMuxPendFindPos(const LosTaskCB *runTask, MuxBaseCB *muxPended)
{
LOS_DL_LIST *node = NULL;
node = &muxPended->muxList;
return node;
}
我们再来看看开启宏LOSCFG_MUTEX_WAITMODE_PRIO,互斥锁基于任务优先级的等待模式时的函数的代码。⑴如果互斥锁的阻塞链表为空,直接返回链表即可。⑵阻塞链表不为空时,从链表中获取第一个和最后一个链表节点,分别为pendedTask1和pendedTask2。⑶如果阻塞链表第一个任务的优先级低于当前任务的优先级,链表中所有的任务的优先级都会低,返回互斥锁的阻塞链表的第一个节点接口。⑷如果阻塞链表的最后一个任务的优先级大于当前任务的优先级,返回互斥锁阻塞链表的头结点即可。⑸对于其他情况,需要调用函数OsMuxPendFindPosSub()进行处理。
LITE_OS_SEC_TEXT STATIC LOS_DL_LIST *OsMuxPendFindPos(const LosTaskCB *runTask, MuxBaseCB *muxPended)
{
LOS_DL_LIST *node = NULL;
LosTaskCB *pendedTask1 = NULL;
LosTaskCB *pendedTask2 = NULL;
⑴ if (LOS_ListEmpty(&muxPended->muxList)) {
node = &muxPended->muxList;
} else {
⑵ pendedTask1 = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&muxPended->muxList));
pendedTask2 = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_LAST(&muxPended->muxList));
⑶ if ((pendedTask1 != NULL) && (pendedTask1->priority > runTask->priority)) {
node = muxPended->muxList.pstNext;
⑷ } else if ((pendedTask2 != NULL) && (pendedTask2->priority <= runTask->priority)) {
node = &muxPended->muxList;
} else {
⑸ node = OsMuxPendFindPosSub(runTask, muxPended);
}
}
return node;
}
继续分析下函数OsMuxPendFindPosSub()。⑴循环遍历互斥锁阻塞链表,⑵如果链表上任务优先级大于当前任务的优先级,则继续遍历。⑶如果链表上任务优先级小于当前任务的优先级,不需要继续遍历了,返回链表的当前节点。⑷如果优先级相等,返回链表的下一个节点。
LITE_OS_SEC_TEXT STATIC LOS_DL_LIST *OsMuxPendFindPosSub(const LosTaskCB *runTask, const MuxBaseCB *muxPended)
{
LosTaskCB *pendedTask = NULL;
LOS_DL_LIST *node = NULL;
⑴ LOS_DL_LIST_FOR_EACH_ENTRY(pendedTask, &(muxPended->muxList), LosTaskCB, pendList) {
⑵ if (pendedTask->priority < runTask->priority) {
continue;
⑶ } else if (pendedTask->priority > runTask->priority) {
node = &pendedTask->pendList;
break;
} else {
⑷ node = pendedTask->pendList.pstNext;
break;
}
}
return node;
}
3.4 互斥锁释放
我们可以使用函数UINT32 LOS_MuxPost(UINT32 muxHandle)来释放互斥锁,下面通过分析源码看看如何释放互斥锁的。
释放互斥锁时首先会进行互斥锁Id、参数的合法性校验,这些比较简单,自行阅读即可。⑴处如果要释放的互斥锁没有被持有、或者不是被当前任务持有,返回错误码。⑵互斥锁的持有数量减1,如果不为0,当前任务嵌套持有该互斥锁,不需要调度,返回释放互斥锁成功。如果释放一次后,当前任务不再持有互斥锁,则调用⑶处函数OsMuxPostOp(),判断是否有任务阻塞在该互斥锁,是否需要触发任务调度等,下文分析该函数。执行完毕⑶后,执行⑷,如果需要调度则触发任务调度。
LITE_OS_SEC_TEXT UINT32 LOS_MuxPost(UINT32 muxHandle)
{
UINT32 ret;
LosTaskCB *runTask = NULL;
LosMuxCB *muxPosted = GET_MUX(muxHandle);
UINT32 intSave;
if (GET_MUX_INDEX(muxHandle) >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) {
OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
}
LOS_TRACE(MUX_POST, muxHandle, muxPosted->muxCount,
((muxPosted->owner == NULL) ? 0xFFFFFFFF : muxPosted->owner->taskId));
SCHEDULER_LOCK(intSave);
ret = OsMuxParaCheck(muxPosted, muxHandle);
if (ret != LOS_OK) {
SCHEDULER_UNLOCK(intSave);
return ret;
}
runTask = OsCurrTaskGet();
⑴ if ((muxPosted->muxCount == 0) || (muxPosted->owner != runTask)) {
SCHEDULER_UNLOCK(intSave);
OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
}
⑵ if (--muxPosted->muxCount != 0) {
SCHEDULER_UNLOCK(intSave);
return LOS_OK;
}
⑶ ret = OsMuxPostOp(runTask, (MuxBaseCB *)muxPosted);
SCHEDULER_UNLOCK(intSave);
⑷ if (ret == MUX_SCHEDULE) {
LOS_MpSchedule(OS_MP_CPU_ALL);
LOS_Schedule();
}
return LOS_OK;
}
我们继续分析函数OsMuxPostOp()。⑴处如果等到该互斥锁的任务列表为空,则标记没有任务持有该互斥锁,并返回不需要调度。⑵获取等待互斥锁的第一个任务resumedTask。如果开启宏LOSCFG_MUTEX_WAITMODE_PRIO,如果等待互斥锁的任务resumedTask的优先级比当前优先级低,需要恢复当前任务的优先级。如果当前任务优先级runTask->priBitMap不为0,会调用⑷处的OsMuxPostOpSub函数,稍后分析该函数。
⑸处把该互斥锁的持有数量设置为1,持有人设置为等待互斥锁的第一个任务resumedTask,resumedTask持有了互斥锁不再阻塞在该互斥锁resumedTask->taskMux = NULL。然后2个语句,属于调测特性的。⑹处调用OsTaskWake()函数,把resumedTask从互斥锁的阻塞链表中删除,从定时器排序链表中删除。更新任务状态,加入就绪队列,返回任务需要调度。
LITE_OS_SEC_TEXT UINT32 OsMuxPostOp(LosTaskCB *runTask, MuxBaseCB *muxPosted)
{
LosTaskCB *resumedTask = NULL;
⑴ if (LOS_ListEmpty(&muxPosted->muxList)) {
muxPosted->owner = NULL;
OsMuxDlockNodeDeleteHook(runTask->taskId, muxPosted);
return MUX_NO_SCHEDULE;
}
⑵ resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(muxPosted->muxList)));
#ifdef LOSCFG_MUTEX_WAITMODE_PRIO
⑶ if (resumedTask->priority > runTask->priority) {
if (LOS_HighBitGet(runTask->priBitMap) != resumedTask->priority) {
LOS_BitmapClr(&runTask->priBitMap, resumedTask->priority);
}
} else if (runTask->priBitMap != 0) {
⑷ OsMuxPostOpSub(runTask, muxPosted);
}
#else
if (runTask->priBitMap != 0) {
⑷ OsMuxPostOpSub(runTask, muxPosted);
}
#endif
⑸ muxPosted->muxCount = 1;
muxPosted->owner = resumedTask;
resumedTask->taskMux = NULL;
OsMuxDlockNodeDeleteHook(runTask->taskId, muxPosted);
OsMuxDlockNodeInsertHook(resumedTask->taskId, muxPosted);
⑹ OsTaskWake(resumedTask, OS_TASK_STATUS_PEND);
return MUX_SCHEDULE;
}
最后,我们分析函数OsMuxPostOpSub()。
⑴如果互斥锁上还有其他任务阻塞着,获取当前运行任务记录的优先级.priBitMap。⑵处循环遍历挂在互斥锁阻塞链表上的每一个任务,如果阻塞任务的优先级不等于bitMapPri,则执行⑶清理优先级位。
⑷处恢复当前持有互斥锁的任务的优先级。
LITE_OS_SEC_TEXT STATIC VOID OsMuxPostOpSub(LosTaskCB *runTask, MuxBaseCB *muxPosted)
{
LosTaskCB *pendedTask = NULL;
UINT16 bitMapPri;
⑴ if (!LOS_ListEmpty(&muxPosted->muxList)) {
bitMapPri = LOS_HighBitGet(runTask->priBitMap);
⑵ LOS_DL_LIST_FOR_EACH_ENTRY(pendedTask, (&muxPosted->muxList), LosTaskCB, pendList) {
if (bitMapPri != pendedTask->priority) {
⑶ LOS_BitmapClr(&runTask->priBitMap, pendedTask->priority);
}
}
}
⑷ bitMapPri = LOS_LowBitGet(runTask->priBitMap);
LOS_BitmapClr(&runTask->priBitMap, bitMapPri);
OsTaskPriModify(muxPosted->owner, bitMapPri);
}
小结
本文带领大家一起剖析了LiteOS互斥锁模块的源代码,包含互斥锁的结构体、互斥锁池初始化、互斥锁创建删除、申请释放等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/LiteOS/LiteOS/issues 。为了更容易找到LiteOS代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。
- 上一篇: sync包——互斥锁 互斥锁可以用于进程同步吗
- 下一篇: 物联网学习教程——线程同步与互斥:互斥锁
猜你喜欢
- 2024-10-12 漫画 | Linux 并发和竞态问题究竟是什么?
- 2024-10-12 【驱动】串口驱动分析(三)-serial driver
- 2024-10-12 synchronized锁 synchronized锁的是类还是对象
- 2024-10-12 Golang 程序遇到性能问题该怎么办?
- 2024-10-12 线程间通信——互斥锁 线程间互斥方式
- 2024-10-12 【Linux系统编程】互斥锁 linux 互斥锁优先级反转
- 2024-10-12 linux c/c++开发:多线程并发锁:互斥锁、自旋锁、原子操作、CAS
- 2024-10-12 每行代码都带注释,带你看懂Go互斥锁的源码
- 2024-10-12 一文搞懂pprof 一文搞懂伤寒论六经辨证
- 2024-10-12 并发原理系列八:信号量、互斥锁、自旋锁
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)