文章目錄
- 1.和彙編檔案相關的頭檔案部分:
- 2.raw_start_first_task()函數:
- 3.raw_task_create(...)任務建立部分:
- 4.PendSV_Handler中斷部分:
- 注釋版代碼:
1.和彙編檔案相關的頭檔案部分:
#ifndef RAW_CPU_H
#define RAW_CPU_H
unsigned int OS_CPU_SR_Save(void);
//上述函數是把PRIMASK的值讀到R0中,并通過傳回值将PRIMASK指派給cpu_sr
//關于PRIMASK,PRIMASK是隻有1個位的寄存器,用于屏蔽中斷
void OS_CPU_SR_Restore(unsigned int sr);
//該函數是通過形參傳遞把cpu_sr的值給R0,然後把R0中的值給PRIMASK
#define RAW_SR_ALLOC() unsigned int cpu_sr = 0
//RAW_SR_ALLOC說明:調用臨界區這兩個接口時必須要定義一個cpu_sr 變量,
//這個變量是用來存放關中斷前的狀态寄存器的,看懂代碼意思知道這個局部變量隻需定義,至于指派0與否不重要
#define USER_CPU_INT_DISABLE() {cpu_sr = OS_CPU_SR_Save();}
//上述宏實作将PRIMASK的值通過R0指派給cpu_sr
#define USER_CPU_INT_ENABLE() {OS_CPU_SR_Restore(cpu_sr);}
//上述宏實作将cpu_sr的值通過R0指派給PRIMASK
#endif
下面是彙編檔案中,實作上述頭檔案中進入臨界區和退出臨界區的部分:
OS_CPU_SR_Save
MRS R0, PRIMASK ;讀取BASEPRI寄存器到R0中。MRS: 狀态寄存器到通用寄存器的傳送指令
CPSID I ;屏蔽所有中斷,隻剩下NMI、複位中斷、硬體中斷無法屏蔽,也屏蔽了PendSV
BX LR ;跳轉到調用該函數的函數中去。LR存的是調用此函數的位址
OS_CPU_SR_Restore
MSR PRIMASK, R0 ;MSR: 通用寄存器到狀态寄存器的傳送指令。形參的值通過R0傳遞,現在将cpu_sr指派給PRIMASK
BX LR ;跳轉到調用該函數的函數中去。LR存的是調用此函數的位址
如果對于以上幾句沒看懂,可以看我的另一篇文章中的詳細介紹:
https://blog.csdn.net/wcc243588569/article/details/117825965
2.raw_start_first_task()函數:
在raw_os_start()中,調用了該函數啟動作業系統:
NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register,寫這個位址的第28位可以懸起PENDSV中斷,
;參考M3權威指南第131頁表8.5
NVIC_SYSPRI14 EQU 0xE000ED22; System priority register (priority 14)。控制PendSV的優先級
;參考M3權威指南第128頁表8.3B
NVIC_PENDSV_PRI EQU 0xFF; PendSV priority value (lowest)。設定PendSV的優先級為最低
;把PendSV配置最低優先級,那麼如果同時有多個異常被觸發,它會在其他異常執行完畢後再執行,而且任何異常都可以中斷它
NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception.将第28位置1,用以懸起PendSV中斷
raw_start_first_task;這個函數的作用是設定PendSV的異常中斷優先級,并把優先級設定為最低
;設定了PendSV的中斷優先級以後,當檢測到其他的中斷被systick搶占,就會觸發PendSV異常,
LDR R0, =NVIC_SYSPRI14
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0] ;設定PendSV的優先級
MOVS R0, #0 ;把R0設定為0
MSR PSP, R0 ;将PSP任務堆棧指針指向0。MSR: 通用寄存器到狀态寄存器的傳送指令。把R0的值複制給PSP
;之是以要把PSP指向0,不是要讓程式的堆棧從0位址開始,而是要在PendSV_Handler中CBZ R0,OS_CPU_PendSVHandler_nosave檢測是否作業系統第一次執行任務
;align msp to 8 byte,8位元組對齊
; MRS R0, MSP ;把MSR的值複制給R0
; LSRS R0, R0, #3
; LSLS R0, R0, #3
; MSR MSP, R0 ;感覺這2句沒用,移位後又移回來,沒有任何作用,可以去掉,功能不變,沒發現有任何影響
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0];這一句就是往ICSR第28位寫1,觸發PendSV異常,若是目前沒有高優先級中斷産生,那麼程式将會進入PendSV handler,可以參考M3權威指南第131頁的表進行了解
;STR是吧第1個數的值複制到第二個數裡面
CPSIE I ;開啟中斷,開啟中斷後,如果沒有其他外部中斷,就會響應PendSV handler。否則就會執行下面的OSStartHang
OSStartHang
B OSStartHang ;should never get here,一般情況下不會執行到這裡,但初始化時可能會執行到這裡一下
;B是跳轉的意思
關于以上配置中,為什麼要将PendSV配置為最低優先級,可以看我的這篇文章:
https://blog.csdn.net/wcc243588569/article/details/117792602
3.raw_task_create(…)任務建立部分:
本來按順序應該講PendSV_Handler中斷部分,但是必須要先知道任務建立時幹了什麼,才能知道任務切換時做了什麼。
在raw_task_create函數的形參中,第一個是
RAW_TASK_OBJ *task_obj
而RAW_TASK_OBJ 的第一個成員是RAW_VOID *task_stack;使用者堆棧指針。是以task_obj位址也就是使用者使用者堆棧的二級指針。
在raw_task_create函數中調用了以下函數
上述函數的作用是把任務控制塊結構體的所有内容都初始設定為0
在raw_task_create函數中調用了以下函數
這個函數的作用就很有意思了,是以我們将該函數内容放到下面:其中去掉了FPU的部分,減少篇幅
RAW_VOID *port_stack_init(PORT_STACK *p_stk_base, RAW_U32 stk_size, RAW_VOID *p_arg, RAW_TASK_ENTRY p_task)
{
PORT_STACK *stk;
RAW_U32 temp = (RAW_U32)(p_stk_base + stk_size);
temp &= 0xfffffff8;
stk = (PORT_STACK *)temp;
*(--stk) = (RAW_U32)0x01000000L; /* xPSR */
*(--stk) = (RAW_U32)p_task; /* Entry Point */
*(--stk) = (RAW_U32)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/
*(--stk) = (RAW_U32)0x12121212L; /* R12 */
*(--stk) = (RAW_U32)0x03030303L; /* R3 */
*(--stk) = (RAW_U32)0x02020202L; /* R2 */
*(--stk) = (RAW_U32)0x01010101L; /* R1 */
*(--stk) = (RAW_U32)p_arg; /* R0 : argument */
*(--stk) = (RAW_U32)0x11111111L; /* R11 */
*(--stk) = (RAW_U32)0x10101010L; /* R10 */
*(--stk) = (RAW_U32)0x09090909L; /* R9 */
*(--stk) = (RAW_U32)0x08080808L; /* R8 */
*(--stk) = (RAW_U32)0x07070707L; /* R7 */
*(--stk) = (RAW_U32)0x06060606L; /* R6 */
*(--stk) = (RAW_U32)0x05050505L; /* R5 */
*(--stk) = (RAW_U32)0x04040404L; /* R4 */
return stk;
}
上述函數實作的功能是在使用者的堆棧中預留出32個位元組用于存放寄存器R4-R11資料,再預留出32個位元組用于存放R0-R3、R12、R14、任務入口函數位址、xPSR資料。
然後傳回的stk是使用者開辟的使用者堆棧區資料的棧頂位址減去64個位元組後的位址,指派給ask_obj->task_stack。
則ask_obj->task_stack位址遞增方向是存放CPU寄存器的位址,往減方向才是使用者任務中用到的臨時變量等内容存放的使用者堆棧區。
任務建立函數中用到了raw_list_entry(node, RAW_TASK_OBJ, task_list)函數,不明白這個函數什麼意思的同學可以看我的這一篇文章:
https://blog.csdn.net/wcc243588569/article/details/117754931
4.PendSV_Handler中斷部分:
相信看了我https://blog.csdn.net/wcc243588569/article/details/117792602這篇文章的,都知道為什麼要在systick中斷裡面做系統心跳,在PendSV_Handler中做任務切換了。
PendSV_Handler任務切換部分實作過程如下:
PendSV_Handler
CPSID I ;關中斷
MRS R0, PSP ;把PSP指針的值賦給R0,PSP指針是雙堆棧指針中的一個,
;可以參考https://blog.csdn.net/hanchaoman/article/details/103727155
;也就是說,PSP指針存放的是進PendSV_Handler中斷前,使用者任務的堆棧區位址。
;可以通過切換PSP指針來指向不同任務的堆棧區
CBZ R0, OS_CPU_PendSVHandler_nosave ;為0則跳轉
;如果程式堆棧指針為0(也就是如果PSP指針是0),說明程式是首次運作,可以直接跳轉到OS_CPU_PendSVHandler_nosave,
;因為是第一次運作,是以不需要不儲存任務的R4-R11寄存器的值,
執行到上面後,會因為第一次執行,PSP為0而跳轉到下面代碼中:
OS_CPU_PendSVHandler_nosave
LDR R0, =raw_task_active ;将目前活躍任務的任務控制塊位址放到R0裡。因為每1個任務在建立時都執行了port_stack_init函數,
;這個函數将每個任務的task_stack指向了每個任務棧的棧頂減去用來存放寄存器R0-R12、R14、任務入口位址、xPSP資料的共16*4=64個位元組,
LDR R1, =high_ready_obj ;将目前最高優先級的任務控制塊位址放到R1裡。
;因為*task_stack在任務控制塊結構體的第一個,任務控制塊的起始位址存放的資料也就是*task_stack的值
;raw_task_active是一個結構體指針,結構體指針指向的位址就是結構體中第一個成員的位址,也就是内部指針*task_stack的位址
LDR R2, [R1] ;将存儲器位址為R1的32位資料讀入寄存器R2。也就是high_ready_obj指向的結構體的位址
STR R2, [R0] ;剛好與LDR相反,将R2寄存器中的32位資料放到記憶體位址為寄存器位址R0值的位址中去。
;通過以上語句,将high_ready_obj指向的結構體所在位址複制給了raw_task_active,
;raw_task_active指向的結構體是high_ready_obj指向的結構體
;以上2句并沒有改變R1和R0的值
LDR R0, [R2] ;R2目前存的high_ready_obj指針指向的結構體位址,[R2]表示的是high_ready_obj指向的結構體中
;task_stack的内容
;以上一句,R0目前存的是high_ready_obj指向的結構體中task_stack的内容
;以上5句,raw_task_active指向了原本high_ready_obj指向的結構體
LDM R0, {R4-R11} ;LDM的方向與LDR相反,該指令是把R0指向的位址中的内容複制到R4-R11中,因R4-R11為32位寄存器,共拷貝了4*8=32個位元組
;以上語句,把high_ready_obj指向的結構體中task_stack指向的任務堆棧中内容拷貝到了R4-R11寄存器中。其中R0位址+
ADDS R0, R0, #0x20 ;R0+=32,跳過了使用者堆棧中用來存放r3-r11資料的32個位元組,再往上就是FPU資料或者R0資料,具體看port_stack_init這個函數
IF {FPU} != "SoftVFP"
;隻有M4核心有,M3核心沒有MPU
;如果有FPU,則r0+=32,空出32個位元組
VLDMFD r0!, {d8 - d15} ; pop FPU register s16~s31。LDMFD是事前遞增方式,也就是R0每賦一個值給D8,R0都遞增一下
ENDIF
;經過以上遞增後,task_stack再往上就是R0-R3、R12、R14、函數入口、PSR資料了
MSR PSP, R0 ;Load PSP with new process SP。MSR和MRS是用于在狀态寄存器和通用寄存器之間傳送資料。
;MRS: 狀态寄存器到通用寄存器的傳送指令。MSR: 通用寄存器到狀态寄存器的傳送指令。
;把R0的位址給PSP,PSP也就是使用者堆棧
ORR LR, LR, #0x04 ;指令用于在兩個操作數上進行邏輯或運算,并把結果放置到目的寄存器中。
;上句是将LR的低3位拉高。LR是連接配接寄存器,
CPSIE I ;開中斷,從PendSV_Handler一進入就關閉中斷,到這開中斷之間的代碼是臨界區代碼,也就是不可被中斷的操作。
BX LR ;異常傳回。如果是從異常狀态傳回到線程狀态,則使用新的PSP指針作為棧頂指針
NOP
END
當第二次再執行時,因為PSP指針不為0了,會在執行
CBZ R0, OS_CPU_PendSVHandler_nosave ;為0則跳轉
判斷時,會轉而執行:
IF {FPU} != "SoftVFP"
VSTMFD r0!, {d8 - d15} ; push FPU register s16~s31
ENDIF
SUBS R0, R0, #0x20 ;R0儲存的位址(SP)減去0x20(32),位址減去32,是因為PSP是堆棧棧頂指針,而根據port_stack_init函數,
;棧頂往下32個位元組儲存的是R0-R3、R12、R14、程式入口等資訊。
;在響應中斷時,ARM架構會自動的儲存R0-R3、R12、R14、程式入口等資訊,則此時到了R3-R11的R11處。
;減去32個位元組後,R0就指向了存放R3所在的位址,進而執行下面指令,位址遞增,将R3-R11共32個位元組資料儲存在使用者堆棧中
STM R0, {R4-R11} ;把8個寄存器的值裝進R0減去32個位元組的地方。
LDR R1, =raw_task_active
LDR R1, [R1]
STR R0, [R1]
PUSH {R14}
bl raw_stack_check
POP {R14}
自此,整個彙編檔案我們講解完畢。
*如果感覺對你有幫助,還希望幫忙點個贊、收藏一下哦^~^!!!
注釋版代碼:
我把帶詳細注釋的程式下載下傳連結放到了下面,是不要積分不要C币免費下載下傳的。有下載下傳不了的可以聯系我,留下郵箱
https://download.csdn.net/download/wcc243588569/19576360