今天學習了uC/OS II的任務切換,知道要實現任務的切換,要將原先任務的寄存器壓入任務堆棧,再將新任務中任務堆棧的寄存器內容彈出到CPU的寄存器,其中的CS、IP寄存器沒有出棧和入棧指令,所以只能引發一次中斷,自動將CS、IP寄存器壓入堆棧,再利用中斷返回,將新任務的任務斷點指針彈出到CPU的CS、IP寄存器中,實現任務切換。雖然明白個大概,但是其中的細節卻有點模糊,為什么調用IRET中斷返回指令后,彈入CPU的CS、IP寄存器的斷點指針是新任務的斷點指針,而不是當前任務的,UCOS II是如何實現這個過程的?網上的文章沒有提及任務切換的細節,然而這些細節卻是理解的重點,所以才有了今天這一篇文章!
在uC/OS II中,任務的調度由任務調度器完成,其主要的工作有兩項:
1.在任務就緒表中查找最高優先級的就緒任務,該任務將是未來切換執行的任務;
2.實現任務的切換。
任務級的任務調度器,由OS_Sched()實現。

1 //!!!為簡單說明問題,OS_Sched中特意刪減了一些語句 2 void OS_Sched (void) 3 { 4 INT8U y; 5 OS_ENTER_CRITICAL(); 6 y =OSUnMapTbl[OSRdyGrp]; 7 //得到最高優先級任務,OSPrioHighRdy從此為就緒表中最高優先級任務的優先級別 8 OSPrioHighRdy=(INT8U)((y <<3)+OSUnMapTbl[OSRdyTbl[y]]); 9 //判斷當前任務是否為就緒表中最高優先級任務 10 if(OSPrioHighRdy!=OSPrioCur) 11 { 12 //得到新任務的任務控制塊的指針 13 OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy]; 14 //統計任務切換次數的計數器加1 15 OSCtxSwCtr++; 16 //此句關鍵!使用軟件中斷切換任務,OS_TASK_SW()其實為INT 0x80,128號中斷 17 //中斷產生后,CPU依次將PSW狀態寄存器、斷點指針段地址:偏移地址壓入當前任務的堆棧中 18 OS_TASK_SW(); 19 } 20 OS_EXIT_CRITICAL(); 21 }
從上述代碼,可以知道,OS_Sched的任務切換步驟分為兩步:
1.獲得最高優先級任務的任務控制塊指針;
2.利用中斷實現斷點數據的切換。(當前任務中止運行時,CPU寄存器
CS、IP
、PSW和通用寄存器等中的數據就稱為斷點數據)
在獲得了新任務的任務控制塊指針,相當於獲得了任務控制塊中包含的任務堆棧,而任務堆棧中又包含了該任務運行時,CPU寄存器的初始內容,即斷點數據。前面已經提到過 “
要實現任務的切換,要將原先任務的寄存器壓入任務堆棧,再將新任務中任務堆棧的寄存器內容彈出到CPU的寄存器
”,除了
CS、IP
寄存器,其他寄存器只需要利用出棧和入棧指令即可,而
CS、IP
寄存器的出棧、入棧要利用中斷INT 0x80和IRET。好吧,那問題來了,uC/OS是如何利用中斷實現斷點指針CS、IP的切換?
產生INT 0x80后,CPU做了什么,當然是去執行和
INT 0x80相關的中斷服務程序去了(說明:每個中斷都有一個對應好的中斷服務程序,每當產生該中斷,則由CPU自動調用該對應的中斷服務程序)。
在uC/OS II中,INT 0x80對應的中斷服務程序是

1 _OSCtxSw PROC FAR 2 ; 3 ;保存當前任務寄存器內容,將它們壓入當前任務堆棧,AX、CX、DX、BX、SP(原始值)、BP、SI 及 DI 4 PUSHA ; 5 PUSH ES ; 6 PUSH DS ; 7 ; 8 ;將當前任務的任務控制塊的指針段地址賦值給DS寄存器 9 MOV AX, SEG _OSTCBCur;Reload DS in case it was altered 10 MOV DS, AX ; 11 ; 12 ; LES指令作用,取得OSTCBCur變量內容(因為任務控制塊的第一個成員為任務堆棧指針,所以ES:[BX+0]表示的地址不僅是任務控制塊的首地址,也是任務堆棧指針的地址),低字存放於BX,高字存放於ES,有關LES詳細查看另一篇文章! 13 ;同時由於任務控制塊的第一成員為任務堆棧指針變量,所以任務堆棧指針成員變量的地址和任務控制塊的地址一樣 14 LES BX, DWORD PTR DS:_OSTCBCur; OSTCBCur->OSTCBStkPtr = SS:SP!!! 15 MOV ES:[BX+2], SS ;將當前SS(棧的基地址)寄存器值存放至當前任務控制塊的2,3內存單元 16 MOV ES:[BX+0], SP ;將當前SP(棧頂的偏移量)存放至當前任務控制塊的0,1內存單元 17 ; 18 CALL FAR PTR _OSTaskSwHook;Call user defined task switch hook 19 ; 20 ;存儲器尋址,DS:_OSTCBHighRdy+2存放的是新任務的棧基址SS,DS:_OSTCBHighRdy存放的是棧頂偏移量SP 21 MOV AX, WORD PTR DS:_OSTCBHighRdy+2;將OSTCBHighRdy賦值給OSTCBCur,使OSTCBCur指向新的任務控制塊 22 MOV DX, WORD PTR DS:_OSTCBHighRdy;OSTCBCur=OSTCBHighRdy 23 MOV WORD PTR DS:_OSTCBCur+2, AX ; 24 MOV WORD PTR DS:_OSTCBCur, DX ; 25 ; 26 MOV AL, BYTE PTR DS:_OSPrioHighRdy;OSPrioCur=OSPrioHighRdy 27 MOV BYTE PTR DS:_OSPrioCur, AL ; 28 ; 29 ;將最高級優先級任務的任務控制塊包含的SS和SP寄存器值,CPU的SS和SP指向了新的任務堆棧 30 LES BX, DWORD PTR DS:_OSTCBHighRdy; SS:SP =OSTCBHighRdy->OSTCBStkPtr 31 MOV SS, ES:[BX+2]; 32 MOV SP, ES:[BX]; 33 ; 34 ;注意:這里彈出的是新任務的堆棧,而不是舊的,因為CPU的SS和SP已經指向了新的任務堆棧 35 POP DS ;Loadnew task's context 36 POP ES ; 37 POPA ; 38 ; 39 ;當新任務的堆棧都彈出的時候,只剩下新任務的CS、IP指針,剛好運行IRET彈到CPU的CS、IP寄存器,開始新任務的運行 40 IRET ;Return to new task 41 ; 42 _OSCtxSw ENDP
請仔細將上面代碼對照着注釋看一遍,注釋已經說得很清楚了,_OSCtxSw的堆棧操作,可以看下面兩幅圖
(0)在調用
_OSCtxSw中斷服務程序前,CPU將PSW和任務斷點指針CS、IP壓入當前任務堆棧
(1)將CPU寄存器
AX、CX、DX、BX、SP(原始值)、BP、SI 及 DI壓入當前任務堆棧
(2)將當前
CPU
的堆棧指針SS和SP存入當前任務控制塊的任務堆棧指針變量(FAR指針變量占4字節)
說明:至此,當前任務的斷點數據已經全部保存,所以在每一個新任務運行之前,uC/OS都會將舊任務的斷點數據全部按照順序壓入私有堆棧中,同時每一個新任務的任務堆棧都已保存初始化好的或以前任務中止時,CPU保存的斷點數據。
(3)
OSTCBCur = OSTCBHighRdy、
OSPrioCur = OSPrioHighRdy
(4)使CPU的SS和SP堆棧指針指向新的任務堆棧
(5)將新的任務堆棧的內容彈出到CPU寄存器,順序為DS、ES、DI、SI、BP、SP、BX、DX、CX、AX。
說明:注意是新任務堆棧在進行彈棧操作,而新任務堆棧在彈棧前,和舊任務的斷點數據全部壓棧后的結構一模一樣,都是
順序為DS、ES、DI、SI、BP、SP、BX、DX、CX、AX、IP、CS、PSW。經過POP DS POP ES POPA,這3條指令之后,新任務堆棧便只剩下
IP、CS、PSW,IP和CS這斷點指針指向新任務的任務代碼,只要將它們彈出到CPU的CS、IP寄存器,就可以實現任務切換。
(6)運行IRET,將新任務斷點指針彈出到CPU的
CS、IP
指針寄存器,PSW彈出到CPU的PSW寄存器
(7)至此,uC/OS開始運行新任務


參考鏈接:
μC-OS-Ⅱ中通過中斷返回指令實現任務切換
http://www.docin.com/p-223857136.html
我對OSCtxSW()任務級任務切換函數的理解(以圖例的方式)
http://blog.163.com/muren20062094%40yeah/blog/static/1618444162011727102557489/
uCOSii中斷處理過程詳解
http://www.doc88.com/p-1498746979512.html
uC/OS II任務調度
http://blog.sina.com.cn/s/blog_6f36f4fb0100n3or.html
uC/OS-II任務棧處理的一種改進方法
http://www.3edu.net/lw/qrs/lw_46692.html