我们看看创建信号量的函数OsSemCreate()
,需要3个参数,创建的信号量的数量,最大数量,以及信号量编号。
⑴判断g_unusedSemList
是否为空,还有可以使用的信号量资源?如果没有可以使用的信号量,调用函数OsSemInfoGetFullDataHook()
做些调测相关的检测,这个函数需要开启调测开关,后续系列专门分析。 ⑵处如果g_unusedSemList
不为空,则获取第一个可用的信号量节点,接着从双向链表g_unusedSemList
中删除,然后调用宏GET_SEM_LIST
获取LosSemCB *semCreated
,初始化创建的信号量信息,包含信号量的状态、信号量数量,信号量最大数量等信息。⑶初始化双向链表&semCreated->semList
,阻塞在这个信号量上的任务会挂在这个链表上。⑷赋值给输出参数*semHandle
,后续程序使用这个信号量编号对信号量进行其他操作。
LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
{
UINT32 intSave;
LosSemCB *semCreated = NULL;
LOS_DL_LIST *unusedSem = NULL;
UINT32 errNo;
UINT32 errLine;
if (semHandle == NULL) {
return LOS_ERRNO_SEM_PTR_NULL;
}
if (count > maxCount) {
OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_OVERFLOW);
}
intSave = LOS_IntLock();
⑴ if (LOS_ListEmpty(&g_unusedSemList)) {
LOS_IntRestore(intSave);
OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_ALL_BUSY);
}
⑵ unusedSem = LOS_DL_LIST_FIRST(&(g_unusedSemList));
LOS_ListDelete(unusedSem);
semCreated = (GET_SEM_LIST(unusedSem));
semCreated->semCount = count;
semCreated->semStat = OS_SEM_USED;
semCreated->maxSemCount = maxCount;
⑶ LOS_ListInit(&semCreated->semList);
⑷ *semHandle = (UINT32)semCreated->semID;
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_SEM_CREATE, semCreated);
return LOS_OK;
ERR_HANDLER:
OS_RETURN_ERROR_P2(errLine, errNo);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
3.2 信号量删除
我们可以使用函数LOS_semDelete(UINT32 semHandle)
来删除信号量,下面通过分析源码看看如何删除信号量的。
⑴处判断信号量semHandle
是否超过LOSCFG_BASE_IPC_SEM_LIMIT
,如果超过则返回错误码。如果信号量编号没有问题,获取信号量控制块LosSemCB *semDeleted
。⑵处判断要删除的信号量的状态,如果处于未使用状态,则跳转到错误标签ERR_HANDLER:
进行处理。⑶如果信号量的阻塞任务列表不为空,不允许删除,跳转到错误标签进行处理。⑷处如果信号量可用删除,则会把.semStat
设置为未使用OS_SEM_UNUSED
,并把信号量节点插入未使用信号量双向链表g_unusedSemList
。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemDelete(UINT32 semHandle)
{
UINT32 intSave;
LosSemCB *semDeleted = NULL;
UINT32 errNo;
UINT32 errLine;
⑴ if (semHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
}
semDeleted = GET_SEM(semHandle);
intSave = LOS_IntLock();
⑵ if (semDeleted->semStat == OS_SEM_UNUSED) {
LOS_IntRestore(intSave);
OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
}
⑶ if (!LOS_ListEmpty(&semDeleted->semList)) {
LOS_IntRestore(intSave);
OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_PENDED);
}
⑷ LOS_ListAdd(&g_unusedSemList, &semDeleted->semList);
semDeleted->semStat = OS_SEM_UNUSED;
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_SEM_DELETE, semDeleted);
return LOS_OK;
ERR_HANDLER:
OS_RETURN_ERROR_P2(errLine, errNo);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
3.3 信号量申请
我们可以使用函数UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
来请求信号量,需要的2个参数分别是信号量semHandle
和等待时间timeout
,取值范围为[0, LOS_WAIT_FOREVER]
,单位为Tick
。下面通过分析源码看看如何请求信号量的。
申请信号量时首先会进行信号量编号、参数的合法性校验。⑴处代码表示信号量如果大于配置的最大值,则返回错误码。⑵处获取要申请的信号量控制块semPended
。⑶处调用函数对信号量控制块进行校验,如果信号量未创建,处于中断处理期间,处于锁任务调度期间,则返回错误码。⑷处如果校验不通过,跳转到ERROR_SEM_PEND:
标签停止信号量的申请。
⑸如果信号量计数大于0,信号量计数减1,返回申请成功的结果。⑹如果信号量计数等于0,并且零等待时间timeout
,则返回结果码LOS_ERRNO_SEM_UNAVAILABLE
。⑺如果申请的信号量被全部占用,需要等待时,把当前任务阻塞的信号量.taskSem
标记为申请的信号量,然后调用函数OsSchedTaskWait()
,该函数详细代码上文已分析,把当前任务状态设置为阻塞状态,加入信号量的阻塞链表.semList
。如果不是永久等待LOS_WAIT_FOREVER
,还需要更改任务状态为OS_TASK_STATUS_PEND_TIME
,并且设置waitTimes
等待时间。⑻处触发任务调度进行任务切换,暂时不执行后续代码。
如果等待时间超时,信号量还不可用,本任务获取不到信号量时,继续执行⑼,更改任务状态,返回错误码。如果信号量可用,执行⑽,本任务获取到信号量,返回申请成功。
LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
{
UINT32 intSave;
LosSemCB *semPended = NULL;
UINT32 retErr;
LosTaskCB *runningTask = NULL;
⑴ if (semHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
}
⑵ semPended = GET_SEM(semHandle);
intSave = LOS_IntLock();
⑶ retErr = OsSemValidCheck(semPended);
if (retErr) {
⑷ goto ERROR_SEM_PEND;
}
⑸ if (semPended->semCount > 0) {
semPended->semCount--;
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runningTask);
return LOS_OK;
}
⑹ if (!timeout) {
retErr = LOS_ERRNO_SEM_UNAVAILABLE;
goto ERROR_SEM_PEND;
}
⑺ runningTask = (LosTaskCB *)g_losTask.runTask;
runningTask->taskSem = (VOID *)semPended;
OsSchedTaskWait(&semPended->semList, timeout);
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runningTask);
⑻ LOS_Schedule();
intSave = LOS_IntLock();
⑼ if (runningTask->taskStatus & OS_TASK_STATUS_TIMEOUT) {
runningTask->taskStatus &= (~OS_TASK_STATUS_TIMEOUT);
retErr = LOS_ERRNO_SEM_TIMEOUT;
goto ERROR_SEM_PEND;
}
LOS_IntRestore(intSave);
⑽ return LOS_OK;
ERROR_SEM_PEND:
LOS_IntRestore(intSave);
OS_RETURN_ERROR(retErr);
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
3.4 信号量释放
我们可以使用函数UINT32 LOS_semPost(UINT32 semHandle)
来释放信号量,下面通过分析源码看看如何释放信号量的。
释放信号量时首先会进行信号量编号、参数的合法性校验,这些比较简单,自行阅读即可。⑴处验判断是否信号量溢出。⑵如果信号量的任务阻塞链表不为空,执行⑶从阻塞链表中获取第一个任务,设置.taskSem
为NULL
,不再阻塞信号量。执行⑷把获取到信号量的任务调整其状态,并加入就行队列。⑸触发任务调度进行任务切换。⑹如果信号量的任务阻塞链表为空,则把信号量的计数加1。
LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
{
UINT32 intSave;
LosSemCB *semPosted = GET_SEM(semHandle);
LosTaskCB *resumedTask = NULL;
if (semHandle >= LOSCFG_BASE_IPC_SEM_LIMIT) {
return LOS_ERRNO_SEM_INVALID;
}
intSave = LOS_IntLock();
if (semPosted->semStat == OS_SEM_UNUSED) {
LOS_IntRestore(intSave);
OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
}
⑴ if (semPosted->maxSemCount == semPosted->semCount) {
LOS_IntRestore(intSave);
OS_RETURN_ERROR(LOS_ERRNO_SEM_OVERFLOW);
}
⑵ if (!LOS_ListEmpty(&semPosted->semList)) {
⑶ resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));
resumedTask->taskSem = NULL;
⑷ OsSchedTaskWake(resumedTask);
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);
⑸ LOS_Schedule();
} else {
⑹ semPosted->semCount++;
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);
}
return LOS_OK;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
4、信号量使用总结
4.1 计数信号量、二值信号量和互斥锁
计数信号量和二值信号量唯一的区别就是信号量的初始数量不一致,二值信号量初始数量只能为0和1,计数信号量的初始值可以为0和大于1的整数。
互斥锁可以理解为一种特性的二值信号量,在实现实现对临界资源的独占式处理、互斥场景时,没有本质的区别。比对下二值的结构体,互斥锁的成员变量.muxCount
表示加锁的次数,信号量的成员变量.semCount
表示信号量的计数,含义稍有不同。
4.2 信号量的互斥和同步
信号量可用用于互斥和同步两种场景,以同步为目的的信号量和以互斥为目的的信号量在使用上,有如下不同:
用于互斥的信号量
初始信号量计数值不为0,表示可用的共享资源 个数。在需要使用共享资源前,先获取信号量,然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。对信号量的申请和释放,需要成对出现,在同一个任务里完成申请和释放。
用于同步的信号量
多任务同时访问同一份共享资源时,会导致冲突,这时候就需要引入任务同步机制使得各个任务按业务需求一个一个的对共享资源进行有序访问操作。任务同步的实质就是任务按需进行排队。
用于同步的信号量,初始信号量计数值为0。任务1申请信号量而阻塞,直到任务2或者某中断释放信号量,任务1才得以进入Ready
或Running
态,从而达到了任务间的同步。信号量的能不能申请成功,依赖其他任务是否释放信号量,申请和释放在不同的任务里完成。
小结
本文带领大家一起剖析了鸿蒙轻内核的信号量模块的源代码,包含信号量的结构体、信号量池初始化、信号量创建删除、申请释放等。
如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:
搭建开发环境 Windows 开发环境的搭建 Ubuntu 开发环境搭建 Linux 与 Windows 之间的文件共享 ……
构建子系统 启动流程 子系统 分布式任务调度子系统 分布式通信子系统 驱动子系统 ……
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/maniuT/article/details/139506682","extend1":"pc","ab":"new"}">>
id="blogExtensionBox" style="width:400px;margin:auto;margin-top:12px" class="blog-extension-box"> class="blog_extension blog_extension_type2" id="blog_extension">
class="extension_official" data-report-click="{"spm":"1001.2101.3001.6471"}" data-report-view="{"spm":"1001.2101.3001.6471"}">
class="blog_extension_card_left">
class="blog_extension_card_cont">
鸿蒙开发学习资料领取!!!
class="blog_extension_card_cont_r">
微信名片
评论记录:
回复评论: