当前位置: 首页 > 科技观察

鸿蒙LightKernelM核心源码分析系列之十一信号量

时间:2023-03-21 18:17:45 科技观察

更多内容请访问:鸿蒙技术社区https://harmonyos.51cto.com,信号量(Semaphore)是一种任务间通信的机制,能够实现任务间通信-任务同步或互斥访问共享资源。在信号量的数据结构中,通常有一个计数值,用来统计有效资源的数量,表示剩余的可以使用的共享资源数量。用于同步目的的信号量和用于互斥目的的信号量的使用是不同的。本文分析鸿蒙LightKernel信号量模块源码,把握信号量使用差异。接下来我们看一下信号量的结构,信号量的初始化,以及对信号量的常用操作的源码。1.信号量结构定义和公共宏定义1.1信号量结构定义文件kernel\include\los_sem.h中定义的信号量控制块结构为LosSemCB,该结构源代码如下。信号量status.semStat取值OS_SEM_UNUSED和OS_SEM_USED,其他成员变量的注释参考注释部分。typedefstruct{UINT16semStat;/**<信号量状态*/UINT16semCount;/**<可用信号量个数*/UINT16maxSemCount;/**<最大可用信号量个数*/UINT16semID;/**<信号量Id*/LOS_DL_LISTsemList;/**<信号量中阻塞的任务列表*/}LosSemCB;1.2常用的信号量类型宏定义,取值为[0,LOSCFG_BASE_IPC_SEM_LIMIT),表示每个信号量在信号量池中的个数。⑴处的宏表示二进制信号量的最大值为1,⑵和⑶处的宏表示不使用信号量,使用状态值。(4)根据信号量阻塞任务双向链表中的链表节点指针ptr获取信号量控制块结构体指针。(5)从信号量池中获取指定信号量semId对应的信号量控制块。⑴#defineos_sem_binary_max_count1⑵#defineos_sem_unused0⑶#defineos_sem_used1⑷#defineget_sem_list(ptr)los_dl_list_entry(ptr)信号量使能,系统启动时调用kernel\src\los_init.c中的OsSemInit()初始化信号量模块。接下来分析信号量初始化的代码。(1)初始化双向循环链表g_unusedSemList,维护未使用的信号量池⑵为信号量池申请内存,申请失败返回错误⑶循环每个信号量进行初始化,为每个信号量节点指定索引semID,设置.LITE_OS_SEC_TEXT_INITUINT32OsSemInit(VOID){LosSemCB*semNode=NULL;UINT16index;⑴LOS_ListInit(&g_unusedSemList);if(LOSCFG_BASE_IPC_SEM_LIMIT==0){returnLOS_ERRNO_SEM_MAXNUM_ZERO;}⑵g_allSem=(LosSemCB*)LOS_MemAlloc(m_aucSysMem0,(LOSCFG_BASE_IPC_SEM_LIMIT*sizeof(LosSemCB)));if(g_allSem==NULL){returnLOS_ERRNO_SEM_NO_MEMORY;}⑶for(index=0;indexsemID=index;semNode->semStat=OS_SEM_UNUSED;ails_LOS(&g_unusedSemList,&semNode->semList);}returnLOS_OK;}3.常用的信号量操作3.1信号量创建我们可以使用函数LOS_SemCreate(UINT16count,UINT32*semHandle)创建一个计数信号量,使用UINT32LOS_BinarySemCreate(UINT16count,UINT32*semHandle)创建一个二进制信号量,我们来分析一下源码查看如何创建信号量。两个函数的传入参数相同,需要传入信号量的个数count,以及存放信号量个数的semHandle。计数信号量的最大个数为OS_SEM_COUNTING_MAX_COUNT,二进制信号量的最大个数为OS_SEM_BINARY_MAX_COUNT。还会进一步调用函数OsSemCreate()来实现信号量的创建,下面继续分析。LITE_OS_SEC_TEXT_INITUINT32LOS_SemCreate(UINT16count,UINT32*semHandle){returnOsSemCreate(count,OS_SEM_COUNTING_MAX_COUNT,semHandle);}LITE_OS_SEC_TEXT_INITUINT32LOS_BinarySemCreate(UINT16count,UINT32*semHandle){returnOsSemCreate(count,OS_SEM_BINARY_MAX_COUNT,semHandle);}我们看看创建信号量的函数OsSemCreate(),需要三个参数,创建的信号量个数,最大个数,信号量个数。⑴判断g_unusedSemList是否为空,是否有可以使用的信号量资源?如果没有可以使用的信号量,调用函数OsSemInfoGetFullDataHook()做一些调试相关的检测。该功能需要打开调试开关,后续系列会专门分析。(2)如果g_unusedSemList不为空,获取第一个可用的信号量节点,然后将其从双向链表g_unusedSemList中删除,然后调用宏GET_SEM_LIST获取LosSemCB*semCreated,初始化创建的信号量信息,包括信号量的状态,signalQuantity,最大信号量个数等信息。⑶初始化双向链表&semCreated->semList,阻塞在这个信号量上的任务会挂在这个链表上。(4)给输出参数*semHandle赋值,后续程序使用这个信号量编号对信号量进行其他操作。LITE_OS_SEC_TEXT_INITUINT32OsSemCreate(UINT16count,UINT16maxCount,UINT32*semHandle){UINT32intSave;LosSemCB*semCreated=NULL;LOS_DL_LIST*unusedSem=NULL;UINT32errNo;UINT32errLine;if(semHandle==NULL){returnLOS_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;GULOS_ListInit(&semCreated->semList);⑷*semHandle=(UINT32)semCreated->semID;LOS_IntRestore(intSave);OsHookCall(LOS_HOOK_TYPE_SEM_CREATE,semCreated);返回LOS_OK;ERR_HANDLER:OS_RETURN_ERROR_P2(errLine,errNo);}3.2信号量删除我们可以使用函数LOS_semDelete(UINT32semHandle)来删除信号量。下面我们分析一下源码,看看如何删除信号量。(1)判断信号量semHandle是否超过LOSCFG_BASE_IPC_SEM_LIMIT,超过则返回错误码。如果信号量编号没有问题,则获取信号量控制块LosSemCB*semDeleted。(2)判断待删除信号量的状态,如果是未使用状态,则跳转到错误标号ERR_HANDLER:进行处理。⑶如果信号量的阻塞任??务列表不为空,不允许删除,会跳转到错误标签处处理。(4)如果信号量可以删除,.semStat会被置为unusedOS_SEM_UNUSED,将信号量节点插入到未使用信号量双向链表g_unusedSemList中。LITE_OS_SEC_TEXT_INITUINT32LOS_SemDelete(UINT32semHandle){UINT32intSave;LosSemCB*semDeleted=NULL;UINT32errNo;UINT32errLine;⑴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_sem_delete,semdeleted);returnlos_ok;erry_handler:os_return_return_ertor_error_error_pp2(errline,errno,errno,errno);请求一个信号量,需要的两个参数是信号量semHandle和等待时间timeout,取值范围是[0,LOS_WAIT_FOREVER],单位是Tick。下面我们分析一下源码,看看如何请求一个信号量。申请信号量时,会先检查信号量的编号和参数的有效性。⑴处的代码表示如果信号量大于配置的最大值,则返回错误码。(2)得到要申请的信号量控制块semPended。(3)调用函数检查信号量控制块。如果信号量尚未创建,处于中断处理期,处于锁任务调度期,则返回错误码。⑷如果验证失败,跳转到ERROR_SEM_PEND:标签停止信号量的应用。(5)如果信号量计数大于0,则将信号量计数减1,返回申请成功的结果。⑩如果信号量计数等于0,等待超时时间为0,则返回结果码LOS_ERRNO_SEM_UNAVAILABLE。⑺如果请求的信号量已满,需要等待,将当前任务阻塞的信号量.taskSem标记为请求的信号量,然后调用函数OsSchedTaskWait()。上面已经分析了该函数的详细代码,将当前任务状态设置为阻塞状态,加入semaphore.semList的阻塞链表。如果不是一直等待LOS_WAIT_FOREVER,还需要将任务状态改为OS_TASK_STATUS_PEND_TIME,并设置waitTimes等待时间。⑻触发任务调度切换任务,后续代码暂不执行。如果等待时间到,信号量还没有,任务无法获取信号量,继续执行⑼,改变任务状态,返回错误码。如果信号量可用,执行⑽,任务获取到信号量,返回申请成功。LITE_OS_SEC_TEXTUINT32LOS_SemPend(UINT32semHandle,UINT32timeout){UINT32intSave;LosSemCB*semPended=NULL;UINT32retErr;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){⑷gotoERROR_SEM_PEND;}⑸if(semPended->semCount>0){semPended->semCount--;LOS_IntRestore(intSave);OsHookCall(LOS_HOOK_TYPE_SEM_PEND,semPended,running_Task);returnLOS;}⑹if(!timeout){retErr=LOS_ERRNO_SEM_UNAVAILABLE;gotoERROR_SEM_PEND;}⑺runningTask=(LosTaskCB*)g_losTask.runTask;runningTask->taskSem=(VOID*)semPended;OsSchedTaskWait(&semPended->semList,timeout);LOS_IntRestore(&semPended->semList,timeout);LOS_IntRestore(;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;gotoERROR_SEM_PEND;}LOS_IntRestore(intSave);⑽returnLOS_OK;ERROR_SEM_PEND:LOS_IntRestore(intSave);OS_RETURN_ERROR(retErr);}3.4信号量释放我们可以通过函数UINT32LOS_semPost(UINT32semHandle)来释放信号量,分析源信号量如何释放信号量释放信号量时,首先检查信号量的编号和参数的有效性。这些比较简单,大家可以自己看。(1)检查判断信号量是否溢出。(2)如果信号量的任务阻塞链表不为空,执行(3)从阻塞链表中获取第一个任务,设置.taskSem为NULL,不再阻塞信号量。执行(4)调整已经获取到信号量的任务的状态,加入到队列中。(5)触发任务调度进行任务切换。(6)如果信号量的任务阻塞链表为空,则将信号量的计数加1。LITE_OS_SEC_TEXTUINT32LOS_SemPost(UINT32semHandle){UINT32intSave;LosSemCB*semPosted=GET_SEM(semHandle);LosTaskCB*resumedTask=NULL;if(semHandle>=LOSCFG_BASE_IPC_SEM_LIMIT){returnLOS_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_ListemPosted(&⑶)(⑹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,resumed_Schedel;Task}seed);(⑸)semCount++;LOS_IntRestore(intSave);OsHookCall(OsHookCall)LOS_HOOK_TYPE_SEM_POST,semPosted,resumedTask);}returnLOS_OK;}4.信号量使用小结4.1计数信号量、二进制信号量和互斥量唯一的区别计数信号量和二进制信号量就是信号量的初始数量不一致。二值信号量的初始个数只能为0和1,计数信号量的初始值可以为0和大于1的整数。互斥量可以理解为一种特征性的二进制信号量,在实现临界资源的独占处理和互斥场景时没有本质区别。相对于nextbinaryvalue的结构,互斥量的成员变量.muxCount表示锁的个数,信号量的成员变量.semCount表示信号量的计数,含义略有不同。4.2信号量的互斥和同步信号量可以用于互斥和同步两种场景。以同步为目的的信号量和以互斥为目的的信号量在使用上有如下区别:用于互斥信号量的初始信号量计数值不为0,表示可用共享资源的个数。在需要使用共享资源之前,先获取信号量,然后使用共享资源,使用完毕后释放信号量。这样,当获取共享资源时,即信号量计数减为0时,其他需要获取信号量的任务将被阻塞,从而保证对共享资源的访问互斥。信号量的申请和释放需要成对出现,申请和释放要在同一个任务中完成。用于同步的信号量在多个任务同时访问同一个共享资源时会引起冲突。这时候就需要引入任务同步机制,让各个任务根据业务需求,对共享资源逐一进行有序的访问操作。任务同步的本质是任务按需排队。用于同步的信号量,初始信号量计数值为0。任务1申请信号量并阻塞,直到任务2或中断释放信号量,任务1进入Ready或Running状态,从而实现任务间的同步。信号量能否申请成功取决于其他任务是否释放信号量,申请和释放是在不同的任务中完成的。总结本文带领大家分析鸿蒙LightKernel信号量模块源码,包括信号量结构、信号量池初始化、信号量创建和删除、应用发布等,感谢阅读!更多信息请访问:https://harmonyos.51cto.com,与华为共同打造的鸿蒙技术社区

最新推荐
猜你喜欢