今天和一個小伙伴討論了一下基於cortex-m3內核的RTOS在任務切換時的程序流程,小伙伴說國內某搜索引擎都搜不到這類的信息,所以我才打算寫下來,硬件平台是stm32f1。
這里的切換有兩種情況:
第一種:從main函數跳到任務一時的程序流程;
第二種:從任務一跳到任務二時的程序流程。
先說第一種:從stm32f1上電復位說起吧,上電復位之后,CPU執行以下代碼:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
可見代碼先去執行SystemInit()這個函數,執行結束之后去執行main()函數。我為什么要說這個呢,因為在《cortex-m3權威指南》里面有這樣一句:“主堆棧指針( MSP):復位后缺省使用的堆棧指針,用於操作系統內核以及異常處理例程(包括中斷服務例程)”
也就是說當cpu復位之后執行的SystemInit()和main()函數時用的都是MSP。我為什么要提MSP,因為下面還有個PSP。
繼續說main()函數,執行main()函數的時候,肯定會執行一大堆初始化(這里就不多贅述初始化了些啥),然后去找優先級最高的任務,並把這個找到的任務賦給nextTask變量,這時會啟動任務調度——也就是觸發PendSV異常。注意到這里用的都是MSP。
這里插一句任務切換的本質:“保存上一個任務的運行狀態,恢復下個任務的運行狀態”。
觸發PendSV異常必然會去執行PendSV_Handler()異常處理函數,在這個函數里執行的就是上面這句紅字,但是從main函數到任務一的時候,是沒有上一個任務的,所以不需要保存上一個任務,直接恢復下一個任務就行。恢復下一個任務就是把下一個任務的運行狀態(這時任務一的運行狀態是該任務初始化完成的狀態)通過彈棧彈入CPU的寄存器里面然后占據CPU的控制權運行該任務,這時就不用MSP了(因為有專門給任務跑的棧),通過下面這句代碼 ORR LR, LR, #0x04 切換到PSP堆棧,就是下圖的第二位 並把它置1,這個PSP是啥呢,同樣在《cortex-m3權威指南》里面有這句解釋,進程堆棧指針( PSP):由用戶的應用程序代碼使用。這個PSP就是專門給任務用的棧指針,這個時候任務就在PSP上運行了。
這時就是任務一占據CPU的控制權在運行!
再來說第二種:當任務一占據了CPU的使用權在運行時,PendSV異常觸發需要切換到任務二。這時CPU會通過壓棧的方式把當前任務一的運行狀態(就是任務一占據cpu控制權時寄存器的值)壓入只屬於任務一的獨立棧中,這就是保存上一個任務的運行狀態;然后恢復下一個任務,任務二的運行狀態就是一開始在main()函數中初始化的狀態,cpu通過彈棧的方式 把 這些保存在任務二獨立棧中的運行狀態數據 彈入CPU的寄存器,讓任務二占據CPU的控制權並運行。
當然了,在這里多說一句,觸發PendSV異常時執行的PendSV_Handler()函數時 用的是MSP指針運行的,任務一與任務二運行時用的是PSP指針。