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

鸿蒙内核源码分析(信号量)-Semaphore解决任务间同步问题

时间:2023-03-19 13:50:44 科技观察

更多内容请访问:与华为官方共建的鸿蒙技术社区https://harmonyos.51cto.com基本概念信号量Semaphore是一种实现任务间通信的机制,可以实现任务间通信任务同步或对共享资源的互斥访问。在信号量的数据结构中,通常有一个计数值,用来统计有效资源的数量,表示剩余的可以使用的共享资源的数量。该值的含义分为两种情况:0,表示该信号量当前不可取,因此可能有任务在该信号量上等待。正值,表示信号量当前可用。以同步为目的的信号量和以互斥为目的的信号量在使用上有如下区别:作为互斥使用时,初始信号量计数值不为0,表示可用共享资源的个数。在需要使用共享资源之前,先获取信号量,然后使用共享资源,使用完毕后释放信号量。这样,当获取共享资源时,即信号量计数减为0时,其他需要获取信号量的任务将被阻塞,从而保证对共享资源的访问互斥。另外,当共享资源个数为1时,推荐使用二进制信号量,类似互斥锁的机制。用于同步时,初始信号量计数值为0,任务1获取信号量后阻塞,直到任务2或中断释放信号量,任务1进入Ready或Running状态,从而实现任务间的同步。信号量操作原理信号量初始化,为配置的N个信号量申请内存(N值可由用户配置,通过LOSCFG_BASE_IPC_SEM_LIMIT宏),并将所有信号量初始化为未使用,加入未使用链表供系统使用.●创建一个信号量,从未使用的信号量列表中获取一个信号量,并设置初始值。●对于信号量应用,如果计数器值大于0,直接减1,返回成功。否则,任务被阻塞,等待其他任务释放信号量。可以设置等待超时时间。当任务被信号量阻塞时,将任务挂到信号量等待任务队列的尾部。●信号量被释放。如果没有任务等待信号量,则计数器直接加1返回。否则唤醒信号量等待任务队列中的第一个任务。●删除信号量,将正在使用的信号量设置为未使用信号量,挂回未使用链表。信号量允许多个任务同时访问共享资源,但是限制了同时访问这个资源的最大任务数。当访问资源的任务数达到资源允许的最大数量时,其他试图获取资源的任务将被阻塞,直到有任务释放信号量。信号量是什么样子的?typedefstruct{UINT8semStat;/**semID=SET_SEM_ID(0,index);//保存IDsemNode->semStat=OS_SEM_UNUSED;//标记未使用LOS_ListTailInsert(&g_unusedSemList,&semNode->semList);//通过semList将信号块挂到freelist}if(OsSemDbgInitHook()!=LOS_OK){returnLOS_ERRNO_SEM_NO_MEMORY;}returnLOS_OK;}分析如下:1024个信号量●SignalIDsrangefrom[0,1023]●未使用的信号量链接到全局变量g_unusedSemList。建议:鸿蒙内核中的其他池(如进程池、任务池)使用free命名idle链表,这里使用unused,命名风格不是很严格,有待完善。创建信号量LITE_OS_SEC_TEXT_INITUINT32OsSemCreate(UINT16count,UINT16maxCount,UINT32*semHandle){unusedSem=LOS_DL_LIST_FIRST(&g_unusedSemList);//从未使用的信号量池中获取第一个LOS_ListDelete(unusedSem);//从GETSun空闲列表中删除sem_DL_LIST_FIRST(&g_unusedSemList);;//通过semList挂在链表上,这里也通过它查看LosSemCB头。进程、线程等结构也是这样Dry.semCreated->semCount=count;//设置数量semCreated->semStat=OS_SEM_USED;//设置可用状态semCreated->maxSemCount=maxCount;//设置最大数量ofsignalsLOS_ListInit(&semCreated->semList);//初始化链表,后续的阻塞任务通过task->pendList挂在semList链表上,你就会知道有哪些任务在等待它。*semHandle=semCreated->semID;//参数带走semIDOsSemDbgUpdateHook(semCreated->semID,OsCurrTaskGet()->taskEntry,count);returnLOS_OK;ERR_HANDLER:OS_RETURN_ERROR_P2(errLine,errNo);}分析如下:取第一个信号量分配。●最大信号量数和信号量个数由参数指定。●信号量的状态由OS_SEM_UNUSED变为OS_SEM_USED●semHandle带走了信号量ID,所以外界知道它已经创建成功编号为*semHandle的信号量申请一个信号量LITE_OS_SEC_TEXTUINT32LOS_SemPend(UINT32semHandle,UINT32timeout){UINT32intSave;LosSemCB*semPended=GET_SEM(semHandle);//通过ID获取信号体UINT32retErr=LOS_OK;LosTaskCB*runTask=NULEXL(semHandle)>=(UINT32)LOSCFG_BASE_IPC_SEM_LIMIT){OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);}IVER){OS_INTER_INVALID)("!!!LOS_ERRNO_SEM_PEND_INTERR!!!\n");OsBackTrace();returnLOS_ERRNO_SEM_PEND_INTERR;}runTask=OsCurrTaskGet();//获取当前任务LOS_ERRNO_SEM_INVALID;gotoOUT;}/*Updatetheoperatetime,nomattertheactualPendsuccessornot*/OsSemDbgTimeUpdateHook(semHandle);if(semPended->semCount>0){//还有资源可用,必须返回成功。当semCount=0时,表示没有资源,task会休眠semPended->semCount--;//资源少了一个gotoOUT;//这里注意retErr=LOS_OK,所以返回OK}elseif(!timeout){retErr=LOS_ERRNO_SEM_UNAVAILABLE;gotoOUT;}if(!OsPreemptableInSched()){//不能申请调度(不能调度的原因是调度任务自旋锁没有持有)PRINT_ERR("!!!LOS_ERRNO_SEM_PEND_IN_LOCK!!!\n");OsBackTrace();retErr=LOS_ERRNO_SEM_PEND_IN_LOCK;gotoOUT;}runTask->taskSem=(VOID*)semPended;//标记当前任务正在等待这个信号量retErr=OsTaskWait(&semPended->semList,timeout,TRUE);//任务进入等待状态,当前任务会挂在semList上,并切换任务上下文retErr=LOS_ERRNO_SEM_TIMEOUT;}OUT:SCHEDULER_UNLOCK(intSave);returnretErr;}分析如下:这个函数有点复杂,goto很多,但是不要被它迷惑了,盯着返回值就行了。先说申请信号量只有一种情况可以成功(即retErr==LOS_OK)if(semPended->semCount>0){//还有可用的资源,必须返回成功,semCount=0表示没有资源没了,任务就得去睡觉了semPended->semCount--;//资源少了一个gotoOUT;//这里注意retErr=LOS_OK,所以返回OK}其他失败的原因应用有:●信号量ID超出范围(默认1024)●中断周期●系统任务●信号量状态错误,信号量ID不匹配。以上都是不正常的判断。另外,一般情况下,当semPended->semCount=0时,没有资源怎么办?任务进入OsTaskWait休眠状态,如何休眠,休眠多长时间,超时值由参数timeout决定,超时值分为以下三种模式:非阻塞模式:当任务申请一个信号量,入参timeout等于0。如果当前信号量计数值不为0,则申请成功,否则申请失败立即返回。永久阻塞模式:任务申请信号量时,入参timeout等于0xFFFFFFFF。如果当前信号量计数值不为0,则申请成功。否则,任务进入阻塞状态,系统切换到就绪任务中优先级最高的继续执行。任务进入阻塞状态后,被阻塞的任务将不会再次执行,直到另一个任务释放信号量。定时阻塞模式:当一个任务申请一个信号量时,如果OsTaskWait为0,则该任务会被挂入semList链表,所有挂在semList上的任务都在等待这个信号量。释放SemaphoreLite_OS_SEC_TEXTUINT32OSSEMPOSTUNSAFE(UINT32SEMHANDLE,BOOL*SEEDCHED){lossemcb*semposted=null;>Posted->semID|ate|==OS_SEM_UNUSED)){returnLOS_ERRNO_SEM_INVALID;}/*Updatetheoperatetime,nomattertheactualPostsuccessornot*/OsSemDbgTimeUpdateHook(semHandle);if(semPosted->semCount==OS_SEM_COUNT_MAX){//当前信号资源不能大于最大资源量returnSELOS_ERNOF_ORF(LOWtymp!&semPosted->semList)){//当前有任务挂在semList上,待唤醒任务,取第一个任务唤醒resumedTask->taskSem=NULL;//任务不需要等待信号,再次变为NULLOsTaskWake(resumedTask);//唤醒任务,注意resumedTask一定不能是当前任务,OsTaskWake不会自己切换任务上下文,设置即可状态if(needSched!=NULL){//如果参数不为空,则返回需要调度的标签*needSched=TRUE;//TRUE表示需要调度}}else{//当前没有任务挂在semList上,semPosted->semCount++;//有很多信号资源一个}returnLOS_OK;}LITE_OS_SEC_TEXTUINT32LOS_SemPost(UINT32semHandle){UINT32intSave;UINT32ret;BOOLneedSched=FALSE;SCHEDULER_LOCK(intSave);ret=OsSemPostUnsafe(semHandleSave);&needSched(semHandleS);if(needSched){//需要调度的情况LOS_MpSchedule(OS_MP_CPU_ALL);//向所有CPU发送调度指令LOS_Schedule();////发起调度}returnret;}分析如下:●注意看下什么情况下semPosted->semCount会是++,当LOS_ListEmpty为真时,semList就是等待这个信号量的任务。semList上的任务挂在OsTaskWait中。他们都在等待这个信号。●每次OsSemPost都会唤醒semList链表上的一个任务,直到semList为空。●掌握信号量核心是了解LOS_SemPend和LOS_SemPost的编程实例。本例实现如下功能:●测试任务Example_TaskEntry创建信号量,锁定任务调度,创建两个任务Example_SemTask1、Example_SemTask2,Example_SemTask2优先级高于Example_SemTask1,两个任务申请同一个信号量,解锁任务调度后,两个任务被阻塞,测试任务Example_TaskEntry释放信号量●Example_SemTask2获得信号量并被调度,然后任务休眠20Ticks,Example_SemTask2被延迟,Example_SemTask1被唤醒。●Example_SemTask1以定时阻塞方式申请一个信号量,等待时间为10Ticks。由于信号量仍由Example_SemTask2持有,因此Example_SemTask1挂掉了。10Ticks后,仍然没有获取到信号量。Example_SemTask1被唤醒,试图申请永久阻塞模式的信号量,Example_SemTask1挂起。●Example_SemTask2在20个Ticks后唤醒。释放信号量后,Example_SemTask1获取信号量并被调度运行,最后释放信号量。●Example_SemTask1被执行,任务Example_TaskEntry在40Ticks后被唤醒,信号量被删除,两个任务被删除。/*任务ID*/staticUINT32g_testTaskId01;staticUINT32g_testTaskId02;/*测试任务优先级*/#defineTASK_PRIO_TEST5/*信号量结构id*/staticUINT32g_semId;VOIDExample_SemTask1(VOID){UINT32ret;printf("Example_SemTask1trygetsemg_semIout1s/*Applyforintimed0t/\定时阻塞模式,定时时间为10ticks*/ret=LOS_SemPend(g_semId,10);/*申请信号量*/if(ret==LOS_OK){LOS_SemPost(g_semId);return;}/*当定时时间为up,信号量还没有申请*/if(ret==LOS_ERRNO_SEM_TIMEOUT){printf("Example_SemTask1timeoutandtrygetsemg_semIdwaitforever.\n");/*在永久阻塞模式下申请信号量*/ret=LOS_SemPend(g_semId,LOS_WAIT_FOREVER);printf("Example_SemTask1wait_foreverandgetsemg_semId.\n");if(ret==LOS_OK){LOS_SemPost(g_semId);return;}}}VOIDEExample_SemTask2(VOID){UINT32ret;printf("Example_SemTask2trygetsemg_semIdwaitforever.\n");/*永久阻塞模式请求信号量*/ret=LOS_SemPend(g_semId,LOS_WAIT_FOREVER);if(ret==LOS_OK){printf("Example_SemTask2getsemg_semIdandthendelay20ticks.\n");}/*任务休眠20ticks*/LOS_TaskDelay(20);printf("Example_SemTask2postsemg_semId.\n");/*释放信号量*/LOS_SemPost(g_semId);return;}UINT32ExampleTaskEntry(VOID){UINT32ret;TSK_INIT_PARAM_Stask1;TSK_INIT_PARAM_Stask2;/*创建信号量*/LOS_SemCreate(0,&g_semId);/*锁定任务调度*/LOS_TaskLock();/*创建任务1*/(VOID)memset_s(&task1,sizeof(TSK_INIT_PARAM_S),0,sizeof(TSK_INIT_PARAM_S));task1.pfnTaskEntry=(TSK_ENTRY_FUNC)Example_SemTask1;task1.pcName="TestTsk1";task1.uwStackSize=OS_TSK_DEFAULT_STACK_SIZE;task1.usTaskPrio=TASK_PRIO_TEST;ret=LOS_TaskCreate(&g_testTaskId01,&task1);if(ret!=LOS_OK){printf("task1createfailed.\n");returnLOS_NOK;}/*创建任务2*/(VOID)memset_s(&task2,sizeof(TSK_INIT_PARAM_S),0,sizeof(TSK_INIT_PARAM_S));task2.pfnTaskEntry=(TSK_ENTRY_FUNC)Example_SemTask2;task2.pcIOName="TestTsk_2";task2.uwStackSize=OS_TSK_DEFAULT_STACK_STACK2tPREZ-1(-1);,&task2);if(ret!=LOS_OK){printf("task2createfailed.\n");returnLOS_NOK;}/*解锁任务Scheduling*/LOS_TaskUnlock();ret=LOS_SemPost(g_semId);/*Taskdormancy40ticks*/LOS_TaskDelay(40);/*Deletesemaphore*/LOS_SemDelete(g_semId);/*Deletetask1*/ret=LOS_TaskDelete(g_testTaskId01);if(ret!=LOS_OK){printf("task1deletefailed.\n");returnLOS_NOK;}/*deletetask2*/ret=LOS_TaskDelete(g_testTaskId02);if(ret!=LOS_OK){printf("task2deletefailed.\n");returnLOS_NOK;}returnLOS_OK;}实例运行结果:Example_SemTask2trygetsemg_semIdwaitforever.Example_SemTask1trygetsemg_semId,timeout10ticks.Example_SemTask2getsemg_semIdandthendelay20ticks.Example_SemTask1timeoutandtrygetsemg_semIdwaitforever.Example_SemTask2postsemg_semId.Example_SemTask1wait_foreverandgetsemg_semId.参与贡献●访问注解仓库地址●Fork本仓库>>新建Feat_xxx分支>>提交代码注解>>CreateanewPullRequest●CreateanewIssueFormoreinformation,pleasevisit:HarmonyosTechnologyCommunityhttps://harmonyos.51cto.comjointlybuiltwithHuaweiofficial