uC/OS-II總是運行進(jìn)入就緒態(tài)任務(wù)中優(yōu)先級最高的任務(wù)。確定哪個(gè)優(yōu)先級最高,下面要由哪個(gè)任務(wù)運行了,這一工作是由任務(wù)調度函數OS_Sched (void)完成的。當前就緒任務(wù)要交出CPU控制權并進(jìn)行任務(wù)切換的相關(guān)操作都調用了OS_Sched (void)函數。 如圖1所示,當前運行態(tài)任務(wù)交出CPU控制權必須是以下某個(gè)函數被調用或某事件發(fā)生:OSFlagPend()、OSMboxPend()、OSMutexPend()、OSQPend()、OSSemPend()、OSTaskSuspend()、OSTimeDly()、OSTimeDlyHMSM()、OSTaskDel()或中斷等。 圖1 我們來(lái)看看OS_Sched (void)函數的程序: //*_bspàUCOSIIàsrcàos_core.c void OS_Sched (void) { #if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0; #endif OS_ENTER_CRITICAL(); if (OSIntNesting == 0) { /* Schedule only if all ISRs done and ... */ if (OSLockNesting == 0) { /* ... scheduler is not locked */ OS_SchedNew(); if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */ OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; #if OS_TASK_PROFILE_EN > 0 OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif OSCtxSwCtr++; /* Increment context switch counter */ OS_TASK_SW(); /* Perform a context switch */ } } } OS_EXIT_CRITICAL(); } 在該函數中,簡(jiǎn)單的講,只是做了兩件事,首先找出當前優(yōu)先級最高的就緒任務(wù)(也可能是運行態(tài)任務(wù)本身),其次調用了任務(wù)級的任務(wù)切換函數OS_TASK_SW(),由此進(jìn)行切換任務(wù)間的出棧入棧操作,并模擬一次CPU中斷完成任務(wù)切換。 任務(wù)級的任務(wù)切換函數OS_TASK_SW()首先對當前運行任務(wù)在CPU寄存器中的現場(chǎng)進(jìn)行保存,即入棧;其次把即將運行的就緒態(tài)任務(wù)上一次運行時(shí)的現場(chǎng)恢復到當前CPU寄存器中,即出棧,注意每個(gè)任務(wù)都有自己專(zhuān)屬的堆棧區;最后使用軟中斷指令或陷阱TRAP人為模擬一次中斷產(chǎn)生,從而讓這個(gè)中斷返回的時(shí)候順利的從原任務(wù)程序中切換到了新任務(wù)程序中(因為當前CPU寄存器的現場(chǎng)已經(jīng)從原任務(wù)的變成了新任務(wù)的)。 OS_TASK_SW()實(shí)際上是個(gè)宏調用,而OSCtxSw(void)函數通常是匯編語(yǔ)言編寫(xiě)的,因為C編譯器通常不支持C語(yǔ)言直接操作CPU的寄存器! //*_bspàHALàincàos_cpu.h #define OS_TASK_SW OSCtxSw void OSCtxSw(void); OSCtxSw(void)函數程序如下: //*_bspàHALàsrcàos_cpu_a.S /********************************************************************************* * PERFORM A CONTEXT SWITCH * void OSCtxSw(void) - from task level * void OSIntCtxSw(void) - from interrupt level * * Note(s): 1) Upon entry, * OSTCBCur points to the OS_TCB of the task to suspend * OSTCBHighRdy points to the OS_TCB of the task to resume * ********************************************************************************/ .global OSIntCtxSw .global OSCtxSw OSIntCtxSw: OSCtxSw: /* * Save the remaining registers to the stack. */ addi sp, sp, -44 #ifdef ALT_STACK_CHECK bltu sp, et, .Lstack_overflow #endif #if OS_THREAD_SAFE_NEWLIB ldw r3, %gprel(_impure_ptr)(gp) /* load the pointer */ #endif /* OS_THREAD_SAFE_NEWLIB */ ldw r4, %gprel(OSTCBCur)(gp) stw ra, 0(sp) stw fp, 4(sp) stw r23, 8(sp) stw r22, 12(sp) stw r21, 16(sp) stw r20, 20(sp) stw r19, 24(sp) stw r18, 28(sp) stw r17, 32(sp) stw r16, 36(sp) #if OS_THREAD_SAFE_NEWLIB /* * store the current value of _impure_ptr so it can be restored * later; _impure_ptr is asigned on a per task basis. It is used * by Newlib to achieve reentrancy. */ stw r3, 40(sp) /* save the impure pointer */ #endif /* OS_THREAD_SAFE_NEWLIB */ /* * Save the current tasks stack pointer into the current tasks OS_TCB. * i.e. OSTCBCur->OSTCBStkPtr = sp; */ stw sp, (r4) /* save the stack pointer (OSTCBStkPtr */ /* is the first element in the OS_TCB */ /* structure. */ /* * Call the user definable OSTaskSWHook() */ call OSTaskSwHook 0: 9: /* * OSTCBCur = OSTCBHighRdy; * OSPrioCur = OSPrioHighRdy; */ ldw r4, %gprel(OSTCBHighRdy)(gp) ldb r5, %gprel(OSPrioHighRdy)(gp) stw r4, %gprel(OSTCBCur)(gp) /* set the current task to be the new task */ stb r5, %gprel(OSPrioCur)(gp) /* store the new task's priority as the current */ /* task's priority */ /* * Set the stack pointer to point to the new task's stack */ ldw sp, (r4) /* the stack pointer is the first entry in the OS_TCB structure */ #if defined(ALT_STACK_CHECK) && (OS_TASK_CREATE_EXT_EN > 0) ldw et, 8(r4) /* load the new stack limit */ #endif #if OS_THREAD_SAFE_NEWLIB /* * restore the value of _impure_ptr ; _impure_ptr is asigned on a * per task basis. It is used by Newlib to achieve reentrancy. */ ldw r3, 40(sp) /* load the new impure pointer */ #endif /* OS_THREAD_SAFE_NEWLIB */ /* * Restore the saved registers for the new task. */ ldw ra, 0(sp) ldw fp, 4(sp) ldw r23, 8(sp) ldw r22, 12(sp) ldw r21, 16(sp) ldw r20, 20(sp) ldw r19, 24(sp) ldw r18, 28(sp) ldw r17, 32(sp) ldw r16, 36(sp) #if OS_THREAD_SAFE_NEWLIB stw r3, %gprel(_impure_ptr)(gp) /* update _impure_ptr */ #endif /* OS_THREAD_SAFE_NEWLIB */ #if defined(ALT_STACK_CHECK) && (OS_TASK_CREATE_EXT_EN > 0) stw et, %gprel(alt_stack_limit_value)(gp) #endif addi sp, sp, 44 /* * resume execution of the new task. */ ret #ifdef ALT_STACK_CHECK .Lstack_overflow: break 3 #endif .set OSCtxSw_SWITCH_PC,0b-OSCtxSw 這個(gè)OS_TASK_SW()函數貌似非常神秘,畢竟是用匯編語(yǔ)言寫(xiě)的,估計大伙都看不懂。不過(guò)沒(méi)有關(guān)系,它在做的事情也并不神秘。正如我們前面所言,它首先模擬產(chǎn)生一次軟中斷,接著(zhù)讓當前運行的任務(wù)入棧,讓即將運行的最高優(yōu)先級的就緒態(tài)任務(wù)出棧,就此完成CPU寄存器現場(chǎng)的轉換(偷梁換柱的精髓就在此),最后執行一條ret指令表示前面的軟中斷程序已經(jīng)執行完畢,返回(即進(jìn)入新的任務(wù)執行程序)。 關(guān)于軟中斷如何產(chǎn)生,開(kāi)始也讓筆者非常納悶,教科書(shū)上總是非常學(xué)術(shù)的告訴我們“使用軟中斷指令或陷阱TRAP人為模擬一次中斷產(chǎn)生”,而理論上這個(gè)軟中斷或TRAP指令應該是一條簡(jiǎn)單的匯編指令而已,但在NIOS II中移植的這個(gè)OS_TASK_SW()函數中卻沒(méi)能找到,整個(gè)函數尋覓下來(lái)好像真沒(méi)有哪條指令看上去像軟中斷或TRAP指令,找遍NIOS II Processor Reference Handbook也沒(méi)能看到哪條指令能夠完成軟中斷或TRAP的功能。在原作者的《嵌入式實(shí)時(shí)操作系統uC/OS-II(第2版)》第14章給出的80x86上移植的OS_TASK_SW()函數實(shí)例中也沒(méi)有找到類(lèi)似的指令,其操作程序和NIOS II中移植的大同小異,那到底怎么回事? 有意思的是,最一篇講述軟中斷指令的文章(http://course.cug.edu.cn/21cn/微機原理與應用/0329.htm)中找到了蛛絲馬跡,這里提出了8086/8088中軟中斷的助記符為INT OPR,并且給出了這條指令實(shí)際運行狀況卻是多個(gè)相關(guān)寄存器的“躲閃騰挪”后完成的。那么回頭看作者給出的80x86和NIOS II移植程序,雖然沒(méi)有和INT OPR類(lèi)似的專(zhuān)用的軟中斷指令,但函數里面某些指令操作卻同樣能夠完成軟中斷這個(gè)動(dòng)作。 參考資料: 1. 《嵌入式實(shí)時(shí)操作系統uC/OS-II(第2版)》91頁(yè):3.05 任務(wù)調度。 2. 《嵌入式實(shí)時(shí)操作系統uC/OS-II(第2版)》92頁(yè):3.06 任務(wù)級的任務(wù)切換,OS_TASK_SW()。 3. 《嵌入式實(shí)時(shí)操作系統uC/OS-II(第2版)》355頁(yè):14.05.02 OSCtxSw()。 4. Altera Nios II 11.0 Software Build Tools for Eclipse的模板uC/OS-II工程。 |