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

鸿蒙LightKernelM核心源码解析系列之六任务与任务调度(三)任务调度模块

时间:2023-03-22 00:55:15 科技观察

更多内容请访问:与华为官方共建的鸿蒙技术社区https://harmonyos.51cto.comScheduling,Schedule也称为Dispatch,是操作系统的一个重要模块,负责选择系统要处理的下一个任务。调度模块需要协调处于就绪状态的任务之间的资源竞争,根据优先级策略从就绪队列中获取高优先级的任务,赋予资源使用权。接下来我们分析任务调度模块的源码。如果涉及到开发板部分,则以开发板项目targets\cortex-m7_nucleo_f767zi_gcc\为例进行源码分析。1、调度模块的重要函数文件kernel\src\los_sched.c定义了调度模块的几个重要函数。我们来分析一下源码。1.1调度初始化函数在任务初始化函数UINT32OsTaskInit(VOID)中调用了调度初始化函数UINT32OsSchedInit(VOID)。⑴初始化任务就绪队列,⑵初始化任务排序链表,⑶初始化调度响应时间全局变量为最大值OS_SCHED_MAX_RESPONSE_TIME。UINT32OsSchedInit(VOID){UINT16pri;⑴for(pri=0;prikernel\arch\arm\开启调度函数functionVOIDOsSchedStart(VOID)cortex-m7\gcc\los_context.c:UINT32依次调用HalStartSchedule(OS_TICK_HANDLERhandler)函数,实现系统初始化时的任务调度。我们看一下这个函数的源码,⑴调用函数获取就绪队列中优先级最高的任务,⑵设置任务状态为运行状态,然后将当前运行任务和新任务都设置为就绪队列中优先级最高的那个任务。(3)将任务调度启动状态全局变量设置为1,标志任务调度已经启用。⑷设置新任务的开始运行时间,然后将新任务从就绪队列中出队。⑸设置全局变量。⑴调用函数设置任务过期时间。VOIDOsSchedStart(VOID){(VOID)LOS_IntLock();⑴LosTaskCB*newTask=OsGetTopTask();⑵newTask->taskStatus|=OS_TASK_STATUS_RUNNING;g_losTask.newTask=newTask;g_losTask.runTask=g_losTask.newTask;⑶g_task>=开始时间1;OsGetCurrSchedTimeCycle();OsSchedTaskDeQueue(newTask);⑸g_schedResponseTime=OS_SCHED_MAX_RESPONSE_TIME;g_schedResponseID=OS_INVALID;⑹OsSchedSetNextExpireTime(newTask->startTime,newTask->taskID,newTask->startTime+newTaskEn->timeSlice1}调度函数K);(任务切换函数用于实现任务切换,由文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S中的汇编函数HalPendSV调用,我们来分析一下这个函数的源码。(1)获取当前正在运行的任务,然后调用函数减去它的运行时间片,设置开始运行时间为当前时间。(2)如果任务处于阻塞等待状态或延迟状态,则将其加入到任务排序中(3)如果任务不在t他阻塞挂起状态或处于阻塞状态,将其加入就绪队列。(4)获取就绪队列中优先级最高的任务,(5)如果当前运行的任务与就绪队列中优先级最高的任务不是同一个任务,则将当前任务状态设置为非运行status,设置新任务为running状态,设置新任务的开始时间为当前任务的开始时间,然后执行(6)标记是否需要切换任务。⑺将新任务从就绪队列中出队,在⑻计算新任务的运行结束时间,然后执行⑼设置任务过期时间。BOOLOsSchedTaskSwitch(VOID){UINT64endTime;BOOLisTaskSwitch=FALSE;⑴LosTaskCB*runTask=g_losTask.runTask;OsTimeSliceUpdate(runTask,OsGetCurrSchedTimeCycle());⑵if(runTask->taskStatus&(OS_TASK_STATUS_PEND_TIME|OS_TASK_STATUS_DELAY)){OsAdd2SortLink(&runTask->sortList,runTask->startTime,runTask->waitTimes,OS_SORT_LINK_TASK);}elseif(!(runTask->taskStatus&(OS_TASK_STATUS_PEND|OS_TASK_STATUS_SUSPEND|OS_TASK_STATUS_UNUSED))){⑶OsSchedTaskEnQueue(runTask);}⑷LosTaskCB*newTask=OsGetTopTask=newTask();g_newif(runTask!=newTask){#if(LOSCFG_BASE_CORE_TSK_MONITOR==1)OsTaskSwitchCheck();#endif⑸runTask->taskStatus&=~OS_TASK_STATUS_RUNNING;newTask->taskStatus|=OS_TASK_STATUS_RUNNING;newTask->startTime=runTask->startTime;isTaskSwitch=;OsHookCall(LOS_HOOK_TYPE_TASK_SWITCHEDIN);}⑺OsSchedTaskDeQueue(newTask);⑻if(newTask->taskID!=g_idleTaskID){endTime=newTask->startTime+newTask->timeSlice;}else{endTime=OS_SCHED_MAX_RESPONSE_TIME;}⑼OsSchedSetNextExpireTime(newTask->startTime,newTask->taskID,endTime);returnisTaskSwitch;}2.Schedulingmoduleassemblyfunctionfilekernel\arch\arm\cortex-m7\gcc\los_dispatch.Sdefinestheassemblyfunctionoftheschedulingmodule,weanalyzethefollowingmacrosaredefinedinthesourcecodeassemblyfilesoftheseschedulinginterfaces,seenotes..equOS_NVIC_INT_CTRL,0xE000ED04;InterruptControlStateRegister,ICSR中断控制状态寄存器.equOS_NVIC_SYSPRI2,0xE000ED20;SystemHandlerPriorityRegister系统优先级寄存器.equOS_NVIC_PENDSV_PRI,0xF0F00000;PendSV异常优先级.equOS_NVIC_PENDSVSET,0x10000000;ICSR寄存器的PENDSVSET位置1时,会触发PendSV异常.equOS_TASK_STATUS_RUNNING,0x0010;themacrodefinitionwiththesamenameinlos_task.h,thevalueisalsothesame,indicatingtherunningstatusofthetask,2.1HalStartToRunassemblyfunctionstartstorunThefunctionHalStartToRunisscheduledbythestartinthefilekernel\arch\arm\cortex-m7\gcc\los_context.cThefunctionHalStartScheduleiscalledduringthesystemstartupphase.Let'sanalyzetheassemblycodeofthisfunctionnext.(1)SetthePendSVexceptionprioritytoOS_NVIC_PENDSV_PRI,andthePendSVexceptionisgenerallysettothelowest.(2)Writebinary10tothecontrolregisterCONTROL,whichmeansusingthePSPstackandprivilegedthreadmode.(3)Loadtheaddressoftheglobalvariableintoregisterr1.BecauseUINT16taskStatusisthesecondmembervariableoftheLosTaskCBstructure,add4bytestotheaddressat[r1,#4]toobtainthestatusofthecurrentrunningtask.Atthistime,thevalueofregisterr0is0x4,whichisthereadystateOS_TASK_STATUS_READY.(5)加载[r0]的值,即任务的堆栈指针taskCB->stackPointer,到寄存器R12。现在R12指向任务栈的栈指针。任务栈现在保存的是上下文,定义在kernel\arch\arm\cortex-m7\gcc\los_arch_context.h中的结构体TaskContext。如果支持浮点寄存器,则执行(6),将R12增加100个字节,包括S16到S31的16个4字节,R4到R11的9个4字节和uwPriMask。指令执行后,R12指向任务栈上上下文的UINT32uwR0位置。(7)代码加载UINT32uwR0-uwR3,UINT32uwR12;UINT32uwLR;UINT32uwPC;任务栈上下文中的UINT32uwxPSR分别存入寄存器R0-R7,其中R5对应UINT32uwLR,R6对应UINT32uwPC,此时寄存器R12指向任务栈上下文的UINT32uwxPSR。然后执行下一条指令,指针继续加72字节(=18个4字节长度),分别对应S0到S15、UINT32FPSCR等18个上下文成员;UINT32NO_NAME。此时寄存器R12指向任务栈底,然后执行(8)将寄存器R12写入寄存器psp。如果不支持浮点寄存器,则执行⑼,从栈指针开始加36字节,寄存器R12指向任务栈上下文的UINT32uwR0位置。然后将context中的寄存器信息加载到寄存器R0-R7,再将寄存器R12写入寄存器psp。最后执行(10)处的指令,将寄存器R5写入lr寄存器,使能中断,然后跳转到R6对应的context对应的PC对应的函数VOIDOsTaskEntry(UINT32taskID)执行入口任务的功能。.typeHalStartToRun,%function.globalHalStartToRunHalStartToRun:.fnstart.cantunwind⑴ldrr4,=OS_NVIC_SYSPRI2ldrr5,=OS_NVIC_PENDSV_PRIstrr5,[r4]⑵movr0,#2msrCONTROL,r0⑶ldrr1,=g_losTask⑷ldrr0,[r1,#4]rldrrES([⑸0derES(⑸dePRES))&&(__FPU_PRESENT==1U))&&\(defined(__FPU_USED)&&(__FPU_USED==1U)))⑩addr12,r12,#100⑺ldmfdr12!,{r0-r7}addr12,r12,#72⑻msrpsp,r12vpush{S0}vpop{S0}#else⑼addr12,r12,#36ldmfdr12!,{r0-r7}msrpsp,r12#endif⑽movlr,r5//MSRxPSR,R7cpsieIbxr6.fnend2.2OsTaskSchedule汇编函数汇编函数HalTask??Schedule实现新旧任务的切换调度。由上可知,它是由任务调度函数VOIDLOS_Schedule(VOID)调用的。我们来看看这个汇编函数的源代码。首先设置中断控制状态寄存器OS_NVIC_INT_CTRL中的OS_NVIC_PENDSVSET位置为1,触发PendSV异常。HalTask??Schedule函数执行完毕后,返回上层调用函数。PendSV异常的回调函数是HalPendSV汇编函数,下面会分析。汇编函数HalTask??Schedule如下:.typeHalTask??Schedule,%function.globalHalTask??ScheduleHalTask??Schedule:.fnstart.cantunwindldrr0,=OS_NVIC_INT_CTRLldrr1,=OS_NVIC_PENDSVSETstrr1,[r0]dsbisbbxlr.fnend3.4HalPendSV汇编函数接下来我们分析函数HalPendSV的汇编代码。(1)将寄存器PRIMASK的值写入寄存器r12,备份中断的开关状态,然后执行命令cpsidI屏蔽全局中断。(2)将寄存器r12和lr压栈,然后调用上面分析的任务切换函数OsSchedTaskSwitch。函数执行完后,执行⑶处的指令并出栈,恢复寄存器r12和lr的值。(4)比较寄存器r0,即任务切换函数OsSchedTaskSwitch的返回值与0,然后执行(5)用r0寄存器保存lr寄存器的值,如果(4)处的比较是不等于,则执行(6)跳转到标签TaskContextSwitch进行任务上下文切换。⑺恢复中断状态,然后返回。我们先看一下需要任务上下文切换的情况,再看标签TaskContextSwitch。(8)从r0寄存器恢复lr寄存器的值。⑼用r0寄存器表示栈指针,然后将寄存器r4-r12的值压入当前任务栈。如果支持浮点寄存器,需要执行⑽将寄存器d8-d15的值压入当前任务栈,r0为任务栈指针。⑾处的指令将全局变量g_losTask的地址加载到寄存器r5中,⑿获取当前运行任务的栈指针,然后更新当前运行任务的栈指针。⒀处的指令获取新任务newTask的地址,后面的指令将新任务的地址赋值给当前运行的任务,即runTask=newTask。⒁处的指令将r1寄存器设置为表示新任务的堆栈指针。如果支持浮点数,则指令⒂将新任务栈中的数据加载到寄存器d8-d15,继续执行后续指令并将数据加载到寄存器r4-r12,然后执行指令⒃更新psp任务栈指针。⒄处指令恢复中断状态,然后执行跳转指令,然后继续执行C代码VOIDOsTaskEntry(UINT32taskId)进入任务执行入口函数。.typeHalPendSV,%function.globalHalPendSVHalPendSV:.fnstart.cantunwind⑴mrsr12,PRIMASKcpsidIHalTask??Switch:⑵push{r12,lr}blxOsSchedTaskSwitch⑶pop{r12,lr}⑷cmpr0,#0⑸movr0,lr⑹bneTaskContextSwitch⑺msrPRIMASK,r12bxlrTaskContextSwitch:⑻movlr,r0⑼mrsr0,pspstmfdr0!,{r4-r12}#if((定义(__FPU_PRESENT)&&(__FPU_PRESENT==1U))&&\(定义(__FPU_USED)&&(__FPU_USED==1U)))⑽vstmdbr0!,{d8-d15}#endif⑾ldrr5,=g_losTask⑿ldrr6,[r5]strr0,[r6]⒀ldrr0,[r5,#4]strr0,[r5]⒁ldrr1,[r0]#if((定义(__FPU_PRESENT)&&(__FPU_PRESENT==1U))&&\(定义(__FPU_USED)&&(__FPU_USED==1U)))⒂vldmiar1!,{d8-d15}#endifldmfdr1!,{r4-r12}⒃msrpsp,r1⒄msrPRIMASK,r12bxlr.fnend总结本文带领大家分析鸿蒙轻内核调度模块源码,包括调用接口和底层装配函数完成。更多信息请访问:Harmonyos.51cto.com,与华为官方合作打造的鸿蒙技术社区