鴻蒙輕內(nèi)核M核源碼分析系列六 任務及任務調(diào)度(3)任務調(diào)度模塊
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
調(diào)度,Schedule也稱為Dispatch,是操作系統(tǒng)的一個重要模塊,它負責選擇系統(tǒng)要處理的下一個任務。調(diào)度模塊需要協(xié)調(diào)處于就緒狀態(tài)的任務對資源的競爭,按優(yōu)先級策略從就緒隊列中獲取高優(yōu)先級的任務,給予資源使用權(quán)。
下面,我們剖析下任務調(diào)度模塊的源代碼,若涉及開發(fā)板部分,以開發(fā)板工程targets\cortex-m7_nucleo_f767zi_gcc\為例進行源碼分析。
1、調(diào)度模塊的重要函數(shù)
文件kernel\src\los_sched.c中定義了調(diào)度模塊的幾個重要的函數(shù),我們來分析下源碼。
1.1 調(diào)度初始化函數(shù)
調(diào)度初始化函數(shù)UINT32 OsSchedInit(VOID)在任務初始化函數(shù)UINT32 OsTaskInit(VOID)中調(diào)用。⑴處會初始化任務就緒隊列,⑵處初始化任務排序鏈表,⑶處初始化調(diào)度響應時間全局變量為最大值OS_SCHED_MAX_RESPONSE_TIME。
- UINT32 OsSchedInit(VOID)
 - {
 - UINT16 pri;
 - ⑴ for (pri = 0; pri < OS_PRIORITY_QUEUE_NUM; pri++) {
 - LOS_ListInit(&g_priQueueList[pri]);
 - }
 - g_queueBitmap = 0;
 - ⑵ g_taskSortLinkList = OsGetSortLinkAttribute(OS_SORT_LINK_TASK);
 - if (g_taskSortLinkList == NULL) {
 - return LOS_NOK;
 - }
 - OsSortLinkInit(g_taskSortLinkList);
 - ⑶ g_schedResponseTime = OS_SCHED_MAX_RESPONSE_TIME;
 - return LOS_OK;
 - }
 
1.2 任務調(diào)度函數(shù)
任務調(diào)度函數(shù)VOID LOS_Schedule(VOID)是出鏡率較高的一個函數(shù)。當系統(tǒng)完成初始化開始調(diào)度,并且沒有鎖任務調(diào)度時,會調(diào)用函數(shù)HalTaskSchedule()進行任務調(diào)度。該函數(shù)定義在kernel\arch\arm\cortex-m7\gcc\los_dispatch.S,由匯編語言實現(xiàn),后文會詳細分析。
- VOID LOS_Schedule(VOID)
 - {
 - if (g_taskScheduled && LOS_CHECK_SCHEDULE) {
 - HalTaskSchedule();
 - }
 - }
 
1.3 開啟調(diào)度函數(shù)
函數(shù)VOID OsSchedStart(VOID)被kernel\src\los_init.c:UINT32 LOS_Start(VOID)-->kernel\arch\arm\cortex-m7\gcc\los_context.c:UINT32 HalStartSchedule(OS_TICK_HANDLER handler)函數(shù)依次調(diào)用,在系統(tǒng)初始化時開啟任務調(diào)度。我們看下該函數(shù)的源碼,⑴處調(diào)用函數(shù)獲取就緒隊列中優(yōu)先級最高的任務,⑵把該任務狀態(tài)設置為運行狀態(tài),接著把當前運行任務和新任務都設置為就緒隊列中優(yōu)先級最高的那個任務。⑶處設置任務調(diào)度啟動狀態(tài)全局變量為1,標記任務調(diào)度已經(jīng)開啟。⑷處設置新任務的開始運行時間,然后把新任務從就緒隊列中出隊。⑸處設置全局變量。⑹處調(diào)用函數(shù)設置該任務的運行過期時間。
- VOID OsSchedStart(VOID)
 - {
 - (VOID)LOS_IntLock();
 - ⑴ LosTaskCB *newTask = OsGetTopTask();
 - ⑵ newTask->taskStatus |= OS_TASK_STATUS_RUNNING;
 - g_losTask.newTask = newTask;
 - g_losTask.runTask = g_losTask.newTask;
 - ⑶ g_taskScheduled = 1;
 - ⑷ newTask->startTime = OsGetCurrSchedTimeCycle();
 - OsSchedTaskDeQueue(newTask);
 - ⑸ g_schedResponseTime = OS_SCHED_MAX_RESPONSE_TIME;
 - g_schedResponseID = OS_INVALID;
 - ⑹ OsSchedSetNextExpireTime(newTask->startTime, newTask->taskID, newTask->startTime + newTask->timeSlice);
 - PRINTK("Entering scheduler\n");
 - }
 
1.4 任務調(diào)度切換函數(shù)
任務切換函數(shù)用于實現(xiàn)任務切換,被文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S中的匯編函數(shù)HalPendSV調(diào)用。我們分析下該函數(shù)的源代碼。
⑴處獲取當前運行的任務,然后調(diào)用函數(shù)減去其運行的時間片,開始運行時間設置為當前時間。⑵如果任務處于阻塞等待狀態(tài)或延遲狀態(tài),則把其加入任務排序鏈表。⑶如果任務不是處于阻塞掛起狀態(tài)、不是處于阻塞狀態(tài),則把其加入就緒隊列。⑷處獲取就緒隊列中優(yōu)先級最高的任務,⑸處如果當前運行任務和就緒隊列匯總優(yōu)先級最高的任務不是同一個任務,把當前任務狀態(tài)設置為非運行狀態(tài),新任務設置為運行狀態(tài),并設置新任務的開始時間為當前任務的開始時間,然后執(zhí)行⑹標記是否需要任務切換。⑺處把新任務從就緒隊列中出隊,⑻處計算新任務的運行結(jié)束時間,然后執(zhí)行⑼設置任務到期時間。
- BOOL OsSchedTaskSwitch(VOID)
 - {
 - UINT64 endTime;
 - BOOL isTaskSwitch = 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);
 - } else if (!(runTask->taskStatus & (OS_TASK_STATUS_PEND | OS_TASK_STATUS_SUSPEND | OS_TASK_STATUS_UNUSED))) {
 - ⑶ OsSchedTaskEnQueue(runTask);
 - }
 - ⑷ LosTaskCB *newTask = OsGetTopTask();
 - g_losTask.newTask = newTask;
 - if (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 = TRUE;
 - 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);
 - return isTaskSwitch;
 - }
 
2、調(diào)度模塊匯編函數(shù)
文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S定義了調(diào)度模塊的匯編函數(shù),我們分析下這些調(diào)度接口的源代碼。匯編文件中定義了如下幾個宏,見注釋。
- .equ OS_NVIC_INT_CTRL, 0xE000ED04 ; Interrupt Control State Register,ICSR 中斷控制狀態(tài)寄存器
 - .equ OS_NVIC_SYSPRI2, 0xE000ED20 ; System Handler Priority Register 系統(tǒng)優(yōu)先級寄存器
 - .equ OS_NVIC_PENDSV_PRI, 0xF0F00000 ; PendSV異常優(yōu)先級
 - .equ OS_NVIC_PENDSVSET, 0x10000000 ; ICSR寄存器的PENDSVSET位置1時,會觸發(fā)PendSV異常
 - .equ OS_TASK_STATUS_RUNNING, 0x0010 ; los_task.h中的同名宏定義,數(shù)值也一樣,表示任務運行狀態(tài),
 
2.1 HalStartToRun匯編函數(shù)
開始運行函數(shù)HalStartToRun被文件kernel\arch\arm\cortex-m7\gcc\los_context.c中的開始調(diào)度函數(shù)HalStartSchedule在系統(tǒng)啟動階段調(diào)用。我們接下來分析下該函數(shù)的匯編代碼。
⑴處設置PendSV異常優(yōu)先級為OS_NVIC_PENDSV_PRI,PendSV異常一般設置為最低。⑵處往控制寄存器CONTROL寫入二進制的10,表示使用PSP棧,特權(quán)級的線程模式。⑶處把全局變量地址加載到寄存器r1。因為UINT16 taskStatus是LosTaskCB結(jié)構(gòu)體的第二個成員變量,⑷處[r1 , #4]把地址加4個字節(jié)來獲取當前運行任務的狀態(tài),此時寄存器r0數(shù)值為0x4,即就緒狀態(tài)OS_TASK_STATUS_READY。
⑸處把[r0]的值即任務的棧指針taskCB->stackPointer加載到寄存器R12,現(xiàn)在R12指向任務棧的棧指針,任務?,F(xiàn)在保存的是上下文,對應定義在kernel\arch\arm\cortex-m7\gcc\los_arch_context.h中的結(jié)構(gòu)體TaskContext。如果支持浮點寄存器,則執(zhí)行⑹,把R12加100個字節(jié),其中包含S16到S31共16個4字節(jié),R4到R11及uwPriMask共9個4字節(jié)的長度,執(zhí)行指令后,R12指向任務棧中上下文的UINT32 uwR0位置。
⑺處代碼把任務棧上下文中的UINT32 uwR0-uwR3, UINT32 uwR12; UINT32 uwLR; UINT32 uwPC; UINT32 uwxPSR;共8個成員變量數(shù)值分別加載到寄存器R0-R7,其中R5對應UINT32 uwLR,R6對應UINT32 uwPC,此時寄存器R12指向任務棧上下文的UINT32 uwxPSR。然后執(zhí)行下一個指令,指針繼續(xù)加72字節(jié)(=18個4字節(jié)長度),即對應S0到S15及UINT32 FPSCR; UINT32 NO_NAME等上下文的18個成員。此時,寄存器R12指向任務棧的棧底,緊接著執(zhí)行⑻把寄存器R12寫入寄存器psp。
如果不支持浮點寄存器,則執(zhí)行⑼,從棧指針加36字節(jié),然后寄存器R12指向任務棧中上下文的UINT32 uwR0位置。接著把上下文中的寄存器信息加載到寄存器R0-R7,緊接著把寄存器R12寫入寄存器psp。
最后,執(zhí)行⑽處指令,把寄存器R5寫入lr寄存器,開中斷,然后跳轉(zhuǎn)到R6對應的上下文的PC對應的函數(shù)VOID OsTaskEntry(UINT32 taskID),去執(zhí)行任務的入口函數(shù)。
- .type HalStartToRun, %function
 - .global HalStartToRun
 - HalStartToRun:
 - .fnstart
 - .cantunwind
 - ⑴ ldr r4, =OS_NVIC_SYSPRI2
 - ldr r5, =OS_NVIC_PENDSV_PRI
 - str r5, [r4]
 - ⑵ mov r0, #2
 - msr CONTROL, r0
 - ⑶ ldr r1, =g_losTask
 - ⑷ ldr r0, [r1, #4]
 - ⑸ ldr r12, [r0]
 - #if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
 - (defined(__FPU_USED) && (__FPU_USED == 1U)))
 - ⑹ add r12, r12, #100
 - ⑺ ldmfd r12!, {r0-r7}
 - add r12, r12, #72
 - ⑻ msr psp, r12
 - vpush {S0}
 - vpop {S0}
 - #else
 - ⑼ add r12, r12, #36
 - ldmfd r12!, {r0-r7}
 - msr psp, r12
 - #endif
 - ⑽ mov lr, r5
 - //MSR xPSR, R7
 - cpsie I
 - bx r6
 - .fnend
 
2.2 OsTaskSchedule匯編函數(shù)
匯編函數(shù)HalTaskSchedule實現(xiàn)新老任務的切換調(diào)度。從上文可以知道,被任務調(diào)度函數(shù)VOID LOS_Schedule(VOID)調(diào)用。我們看看這個匯編函數(shù)的源代碼,首先往中斷控制狀態(tài)寄存器OS_NVIC_INT_CTRL中的OS_NVIC_PENDSVSET位置1,觸發(fā)PendSV異常。執(zhí)行完畢HalTaskSchedule函數(shù),返回上層調(diào)用函數(shù)。PendSV異常的回調(diào)函數(shù)是HalPendSV匯編函數(shù),下文會分析此函數(shù)。匯編函數(shù)HalTaskSchedule如下:
- .type HalTaskSchedule, %function
 - .global HalTaskSchedule
 - HalTaskSchedule:
 - .fnstart
 - .cantunwind
 - ldr r0, =OS_NVIC_INT_CTRL
 - ldr r1, =OS_NVIC_PENDSVSET
 - str r1, [r0]
 - dsb
 - isb
 - bx lr
 - .fnend
 
3.4 HalPendSV匯編函數(shù)
接下來,我們分析下HalPendSV匯編函數(shù)的源代碼。⑴處把寄存器PRIMASK數(shù)值寫入寄存器r12,備份中斷的開關(guān)狀態(tài),然后執(zhí)行指令cpsid I屏蔽全局中斷。⑵處把寄存器r12、lr入棧,然后調(diào)用上文分析過的任務切換函數(shù)OsSchedTaskSwitch。函數(shù)執(zhí)行完畢,執(zhí)行⑶處指令出棧,恢復寄存器r12、lr數(shù)值。⑷處比較寄存器r0即任務切換函數(shù)OsSchedTaskSwitch的返回值與0,然后執(zhí)行⑸使用r0寄存器保存lr寄存器的值,如果⑷處的比較不相等,則執(zhí)行⑹跳轉(zhuǎn)到標簽TaskContextSwitch進行任務上下文切換。⑺處恢復中斷狀態(tài),然后返回。
我們來看下需要任務上下文切換的情況,接著看標簽TaskContextSwitch。⑻處從r0寄存器恢復lr寄存器的值。⑼處使用r0寄存器指示棧指針,然后把寄存器r4-r12的數(shù)值壓入當前任務棧。如果支持浮點寄存器,還需要執(zhí)行⑽,把寄存器d8-d15的數(shù)值壓入當前任務棧,r0為任務棧指針。
⑾處指令把全局變量g_losTask地址加載到寄存器r5,⑿獲取當前運行任務的棧指針,然后更新當前運行任務的棧指針。⒀處指令獲取新任務newTask的地址,接著的指令把新任務地址賦值給當前運行任務,即runTask = newTask。⒁處指令把r1寄存器表示新任務的棧指針。如果支持浮點,⒂指令把新任務棧中的數(shù)據(jù)加載到寄存器d8-d15寄存器,繼續(xù)執(zhí)行后續(xù)指令繼續(xù)加載數(shù)據(jù)到r4-r12寄存器,然后執(zhí)行⒃處指令更新psp任務棧指針。⒄處指令恢復中斷狀態(tài),然后執(zhí)行跳轉(zhuǎn)指令,后續(xù)繼續(xù)執(zhí)行C代碼VOID OsTaskEntry(UINT32 taskId)進入任務執(zhí)行入口函數(shù)。
- .type HalPendSV, %function
 - .global HalPendSV
 - HalPendSV:
 - .fnstart
 - .cantunwind
 - ⑴ mrs r12, PRIMASK
 - cpsid I
 - HalTaskSwitch:
 - ⑵ push {r12, lr}
 - blx OsSchedTaskSwitch
 - ⑶ pop {r12, lr}
 - ⑷ cmp r0, #0
 - ⑸ mov r0, lr
 - ⑹ bne TaskContextSwitch
 - ⑺ msr PRIMASK, r12
 - bx lr
 - TaskContextSwitch:
 - ⑻ mov lr, r0
 - ⑼ mrs r0, psp
 - stmfd r0!, {r4-r12}
 - #if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
 - (defined(__FPU_USED) && (__FPU_USED == 1U)))
 - ⑽ vstmdb r0!, {d8-d15}
 - #endif
 - ⑾ ldr r5, =g_losTask
 - ⑿ ldr r6, [r5]
 - str r0, [r6]
 - ⒀ ldr r0, [r5, #4]
 - str r0, [r5]
 - ⒁ ldr r1, [r0]
 - #if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
 - (defined(__FPU_USED) && (__FPU_USED == 1U)))
 - ⒂ vldmia r1!, {d8-d15}
 - #endif
 - ldmfd r1!, {r4-r12}
 - ⒃ msr psp, r1
 - ⒄ msr PRIMASK, r12
 - bx lr
 - .fnend
 
小結(jié)
本文帶領(lǐng)大家一起剖析了鴻蒙輕內(nèi)核調(diào)度模塊的源代碼,包含調(diào)用接口及底層的匯編函數(shù)實現(xiàn)。
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
















 
 
 






 
 
 
 