鴻蒙輕內(nèi)核M核源碼分析系列六 任務(wù)及任務(wù)調(diào)度(3)任務(wù)調(diào)度模塊
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
調(diào)度,Schedule也稱為Dispatch,是操作系統(tǒng)的一個重要模塊,它負責(zé)選擇系統(tǒng)要處理的下一個任務(wù)。調(diào)度模塊需要協(xié)調(diào)處于就緒狀態(tài)的任務(wù)對資源的競爭,按優(yōu)先級策略從就緒隊列中獲取高優(yōu)先級的任務(wù),給予資源使用權(quán)。
下面,我們剖析下任務(wù)調(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)在任務(wù)初始化函數(shù)UINT32 OsTaskInit(VOID)中調(diào)用。⑴處會初始化任務(wù)就緒隊列,⑵處初始化任務(wù)排序鏈表,⑶處初始化調(diào)度響應(yīng)時間全局變量為最大值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 任務(wù)調(diào)度函數(shù)
任務(wù)調(diào)度函數(shù)VOID LOS_Schedule(VOID)是出鏡率較高的一個函數(shù)。當(dāng)系統(tǒng)完成初始化開始調(diào)度,并且沒有鎖任務(wù)調(diào)度時,會調(diào)用函數(shù)HalTaskSchedule()進行任務(wù)調(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)初始化時開啟任務(wù)調(diào)度。我們看下該函數(shù)的源碼,⑴處調(diào)用函數(shù)獲取就緒隊列中優(yōu)先級最高的任務(wù),⑵把該任務(wù)狀態(tài)設(shè)置為運行狀態(tài),接著把當(dāng)前運行任務(wù)和新任務(wù)都設(shè)置為就緒隊列中優(yōu)先級最高的那個任務(wù)。⑶處設(shè)置任務(wù)調(diào)度啟動狀態(tài)全局變量為1,標(biāo)記任務(wù)調(diào)度已經(jīng)開啟。⑷處設(shè)置新任務(wù)的開始運行時間,然后把新任務(wù)從就緒隊列中出隊。⑸處設(shè)置全局變量。⑹處調(diào)用函數(shù)設(shè)置該任務(wù)的運行過期時間。
- 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 任務(wù)調(diào)度切換函數(shù)
任務(wù)切換函數(shù)用于實現(xiàn)任務(wù)切換,被文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S中的匯編函數(shù)HalPendSV調(diào)用。我們分析下該函數(shù)的源代碼。
⑴處獲取當(dāng)前運行的任務(wù),然后調(diào)用函數(shù)減去其運行的時間片,開始運行時間設(shè)置為當(dāng)前時間。⑵如果任務(wù)處于阻塞等待狀態(tài)或延遲狀態(tài),則把其加入任務(wù)排序鏈表。⑶如果任務(wù)不是處于阻塞掛起狀態(tài)、不是處于阻塞狀態(tài),則把其加入就緒隊列。⑷處獲取就緒隊列中優(yōu)先級最高的任務(wù),⑸處如果當(dāng)前運行任務(wù)和就緒隊列匯總優(yōu)先級最高的任務(wù)不是同一個任務(wù),把當(dāng)前任務(wù)狀態(tài)設(shè)置為非運行狀態(tài),新任務(wù)設(shè)置為運行狀態(tài),并設(shè)置新任務(wù)的開始時間為當(dāng)前任務(wù)的開始時間,然后執(zhí)行⑹標(biāo)記是否需要任務(wù)切換。⑺處把新任務(wù)從就緒隊列中出隊,⑻處計算新任務(wù)的運行結(jié)束時間,然后執(zhí)行⑼設(shè)置任務(wù)到期時間。
- 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ù)值也一樣,表示任務(wù)運行狀態(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ù)的匯編代碼。
⑴處設(shè)置PendSV異常優(yōu)先級為OS_NVIC_PENDSV_PRI,PendSV異常一般設(shè)置為最低。⑵處往控制寄存器CONTROL寫入二進制的10,表示使用PSP棧,特權(quán)級的線程模式。⑶處把全局變量地址加載到寄存器r1。因為UINT16 taskStatus是LosTaskCB結(jié)構(gòu)體的第二個成員變量,⑷處[r1 , #4]把地址加4個字節(jié)來獲取當(dāng)前運行任務(wù)的狀態(tài),此時寄存器r0數(shù)值為0x4,即就緒狀態(tài)OS_TASK_STATUS_READY。
⑸處把[r0]的值即任務(wù)的棧指針taskCB->stackPointer加載到寄存器R12,現(xiàn)在R12指向任務(wù)棧的棧指針,任務(wù)?,F(xiàn)在保存的是上下文,對應(yīng)定義在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指向任務(wù)棧中上下文的UINT32 uwR0位置。
⑺處代碼把任務(wù)棧上下文中的UINT32 uwR0-uwR3, UINT32 uwR12; UINT32 uwLR; UINT32 uwPC; UINT32 uwxPSR;共8個成員變量數(shù)值分別加載到寄存器R0-R7,其中R5對應(yīng)UINT32 uwLR,R6對應(yīng)UINT32 uwPC,此時寄存器R12指向任務(wù)棧上下文的UINT32 uwxPSR。然后執(zhí)行下一個指令,指針繼續(xù)加72字節(jié)(=18個4字節(jié)長度),即對應(yīng)S0到S15及UINT32 FPSCR; UINT32 NO_NAME等上下文的18個成員。此時,寄存器R12指向任務(wù)棧的棧底,緊接著執(zhí)行⑻把寄存器R12寫入寄存器psp。
如果不支持浮點寄存器,則執(zhí)行⑼,從棧指針加36字節(jié),然后寄存器R12指向任務(wù)棧中上下文的UINT32 uwR0位置。接著把上下文中的寄存器信息加載到寄存器R0-R7,緊接著把寄存器R12寫入寄存器psp。
最后,執(zhí)行⑽處指令,把寄存器R5寫入lr寄存器,開中斷,然后跳轉(zhuǎn)到R6對應(yīng)的上下文的PC對應(yīng)的函數(shù)VOID OsTaskEntry(UINT32 taskID),去執(zhí)行任務(wù)的入口函數(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)新老任務(wù)的切換調(diào)度。從上文可以知道,被任務(wù)調(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)用上文分析過的任務(wù)切換函數(shù)OsSchedTaskSwitch。函數(shù)執(zhí)行完畢,執(zhí)行⑶處指令出棧,恢復(fù)寄存器r12、lr數(shù)值。⑷處比較寄存器r0即任務(wù)切換函數(shù)OsSchedTaskSwitch的返回值與0,然后執(zhí)行⑸使用r0寄存器保存lr寄存器的值,如果⑷處的比較不相等,則執(zhí)行⑹跳轉(zhuǎn)到標(biāo)簽TaskContextSwitch進行任務(wù)上下文切換。⑺處恢復(fù)中斷狀態(tài),然后返回。
我們來看下需要任務(wù)上下文切換的情況,接著看標(biāo)簽TaskContextSwitch。⑻處從r0寄存器恢復(fù)lr寄存器的值。⑼處使用r0寄存器指示棧指針,然后把寄存器r4-r12的數(shù)值壓入當(dāng)前任務(wù)棧。如果支持浮點寄存器,還需要執(zhí)行⑽,把寄存器d8-d15的數(shù)值壓入當(dāng)前任務(wù)棧,r0為任務(wù)棧指針。
⑾處指令把全局變量g_losTask地址加載到寄存器r5,⑿獲取當(dāng)前運行任務(wù)的棧指針,然后更新當(dāng)前運行任務(wù)的棧指針。⒀處指令獲取新任務(wù)newTask的地址,接著的指令把新任務(wù)地址賦值給當(dāng)前運行任務(wù),即runTask = newTask。⒁處指令把r1寄存器表示新任務(wù)的棧指針。如果支持浮點,⒂指令把新任務(wù)棧中的數(shù)據(jù)加載到寄存器d8-d15寄存器,繼續(xù)執(zhí)行后續(xù)指令繼續(xù)加載數(shù)據(jù)到r4-r12寄存器,然后執(zhí)行⒃處指令更新psp任務(wù)棧指針。⒄處指令恢復(fù)中斷狀態(tài),然后執(zhí)行跳轉(zhuǎn)指令,后續(xù)繼續(xù)執(zhí)行C代碼VOID OsTaskEntry(UINT32 taskId)進入任務(wù)執(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ū)