通過最簡單的任務切換函式講解,工程使用《[野火?]《uCOS-III內核實作與應用開發實戰指南—基于STM32》》第5章節的工程,以下所說的地址自己做時可能有所不同,
先說明幾個任務相關全域變數:
#define TASK1_STK_SIZE 20
#define TASK2_STK_SIZE 20
static CPU_STK Task1Stk[TASK1_STK_SIZE]; //Task1Stk[0]地址為0x20000028;Task1Stk[TASK1_STK_SIZE]地址為0x20000078
static CPU_STK Task2Stk[TASK2_STK_SIZE];//Task2Stk[0]地址為0x20000078;Task2Stk[TASK2_STK_SIZE]地址為0x200000c8
static OS_TCB Task1TCB;//Task1TCB的地址為0x20000008
static OS_TCB Task2TCB;//Task2TCB的地址為0x20000010
void Task1( void *p_arg );//任務入口地址為0x00000401
void Task2( void *p_arg );//任務入口地址為0x00000492
然后幾個作業系統相關的全域變數:
OS_EXT OS_TCB *OSTCBCurPtr; //OSTCBCurPtr的地址為0x20000018
OS_EXT OS_TCB *OSTCBHighRdyPtr;//OSTCBHighRdyPtr的地址為0x2000001c
OS_EXT OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];//OSRdyList[0]的地址為0x200000c8;OSRdyList[0]的地址為0x200000d0
OS_EXT OS_STATE OSRunning;//不關注
創建任務時發生了什么?(注:把上面全域變數的地址寫到一張紙上面,后面更加好分析一些)
OSTaskCreate ((OS_TCB*) &Task1TCB,
(OS_TASK_PTR ) Task1,
(void *) 0,
(CPU_STK*) &Task1Stk[0],
(CPU_STK_SIZE) TASK1_STK_SIZE,
(OS_ERR *) &err);
經過這個OSTaskCreate 創建任務的函式之后,Task1TCB.StkPtr指向了Task1空閑堆疊的堆疊頂,Task1TCB.StkSize則是記錄堆疊的大小,以下進行詳細分析:
void OSTaskCreate(OS_TCB *p_tcb,
OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size,
OS_ERR *p_err)
{
CPU_STK *p_sp;
p_sp = OSTaskStkInit(p_task,p_arg,p_stk_base,stk_size);//回傳值就是剩下空閑堆疊的堆疊頂
p_tcb ->StkPtr = p_sp;//讓Task1TCB.StkPtr指向剩下空閑堆疊的堆疊頂
p_tcb->StkSize = stk_size;//Task1TCB.StkSize記錄堆疊的大小
*p_err = OS_ERR_NONE;
}
再對OSTaskStkInit()函式進行分析:
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size)
{
CPU_STK *p_stk;
p_stk = &p_stk_base[stk_size];
/* 例外發生時自動保存的暫存器*/
*--p_stk = (CPU_STK)0x01000000u;/* xPSR的bit24必須置1*/
*--p_stk = (CPU_STK)p_task; /* 任務的入口地址*/
*--p_stk = (CPU_STK)0x14141414u;/* R14 (LR)*/
*--p_stk = (CPU_STK)0x12121212u;/* R12*/
*--p_stk = (CPU_STK)0x03030303u;/* R3*/
*--p_stk = (CPU_STK)0x02020202u;/* R2*/
*--p_stk = (CPU_STK)0x01010101u;/* R1*/
*--p_stk = (CPU_STK)p_arg; /* R0 : 任務形參*/
/* 例外發生時需手動保存的暫存器*/
*--p_stk = (CPU_STK)0x11111111u;/* R11*/
*--p_stk = (CPU_STK)0x10101010u;/* R10*/
*--p_stk = (CPU_STK)0x09090909u;/* R9*/
*--p_stk = (CPU_STK)0x08080808u;/* R8*/
*--p_stk = (CPU_STK)0x07070707u;/* R7*/
*--p_stk = (CPU_STK)0x06060606u;/* R6*/
*--p_stk = (CPU_STK)0x05050505u;/* R5*/
*--p_stk = (CPU_STK)0x04040404u;/* R4*/
return (p_stk);
}
其中自動保存和手動保存的暫存器一定要有個映像,
再回到先前OSTaskCreate()任務創建函式,通過這個函式創建任務之后,Task1TCB.StkPtr指向了Task1空閑堆疊的堆疊頂,Task1TCB.StkSize則是記錄堆疊的大小(重復一下)
而任務1和任務2中的堆疊空間則變成了以下所示:
//Task1Stk空間
0x20000078:&Task1Stk[TASK1_STK_SIZE];堆疊的堆疊頂,記住這個地址
0x20000074:0x01000000u;/* xPSR的bit24必須置1*/
0x20000070:0x00000401 /* 任務的入口地址*/
0x2000006c:0x14141414u;/* R14 (LR)*/
0x20000068:0x12121212u;/* R12*/
0x20000064:0x03030303u;/* R3*/
0x20000060:0x02020202u;/* R2*/
0x2000005c:0x01010101u;/* R1*/
0x20000058:p_arg; /* R0 : 任務形參*/需要保存的R4-R11暫存器的堆疊頂,記住這個值
0x20000054:0x11111111u;/* R11*/
0x20000050:0x10101010u;/* R10*/
0x2000004c:0x09090909u;/* R9*/
0x20000048:0x08080808u;/* R8*/
0x20000044:0x07070707u;/* R7*/
0x20000040:0x06060606u;/* R6*/
0x2000003c:0x05050505u;/* R5*/
0x20000038:0x04040404u;/* R4*/空閑堆疊的堆疊頂,記住這個值
0x20000034:
0x20000030:
0x2000002c:
0x20000028:
//Task2Stk空間
0x200000C8:&Task2Stk[TASK2_STK_SIZE];堆疊的堆疊頂,記住這個地址
0x200000C4:0x01000000u;/* xPSR的bit24必須置1*/
0x200000C0:0x00000492 /* 任務的入口地址*/
0x200000Bc:0x14141414u;/* R14 (LR)*/
0x200000B8:0x12121212u;/* R12*/
0x200000B4:0x03030303u;/* R3*/
0x200000B0:0x02020202u;/* R2*/
0x200000Ac:0x01010101u;/* R1*/
0x200000A8:p_arg; /* R0 : 任務形參*/需要保存的R4-R11暫存器的堆疊頂,記住這個值
0x200000A4:0x11111111u;/* R11*/
0x200000A0:0x10101010u;/* R10*/
0x2000009c:0x09090909u;/* R9*/
0x20000098:0x08080808u;/* R8*/
0x20000094:0x07070707u;/* R7*/
0x20000090:0x06060606u;/* R6*/
0x2000008c:0x05050505u;/* R5*/
0x20000088:0x04040404u;/* R4*/空閑堆疊的堆疊頂,記住這個值
0x20000084:
0x20000080:
0x2000007c:
0x20000078:
所以說:
Task1TCB.StkPtr經過任務創建函式之后指向0x20000038;本身地址為0x20000008;
Task2TCB.StkPtr經過任務創建函式之后指向0x20000088;本身地址為0x20000010;
OSRdyList[0].HeadPtr = &Task1TCB;//指向Task1TCB(0x20000008),OSRdyList[0]本身地址為0x200000c8
OSRdyList[1].HeadPtr = &Task2TCB;//指向Task2TCB(0x20000010),OSRdyList[1]本身地址為0x200000D0
之后進行任務啟動,如下所示:
void OSStart(OS_ERR *p_err)
{
if( OSRunning == OS_STATE_OS_STOPPED )
{
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
//OSTCBHighRdyPtr 將指向&Task1TCB(0x20000008),本身的地址為0x2000001c
//OSTCBCurPtr 指向(OS_TCB*)0,本身的地址為0x20000018
/* 啟動任務切換,不會回傳 */
OSStartHighRdy();
/* 不會運行到這里,運行到這里表示發生了致命的錯誤 */
*p_err = OS_ERR_FATAL_RETURN;
}
else
{
*p_err = OS_STATE_OS_RUNNING;
}
}
OSStartHighRdy()函式分析如下:
OSStartHighRdy
LDR R0, = NVIC_SYSPRI14 ; 設定 PendSV 例外優先級為最低
;此時暫存器R0的為 NVIC_SYSPRI14( 0xE000ED22
LDR R1, = NVIC_PENDSV_PRI
;此時暫存器R1為 NVIC_PENDSV_PRI( 0x000000FF
STRB R1, [R0]
;R1中的值作為變數,R0中的值作為地址,然后將R1寫入NVIC_SYSPRI14(0xE000ED22)地址中
;即成功設定了 PendSV 例外優先級為最低
MOVS R0, #0 ; 設定psp的值為0,開始第一次背景關系切換
;此時R0暫存器為0
MSR PSP, R0
;將暫存器R0中的值移入PSP暫存器當中,此時PSP暫存器中的值為0
LDR R0, =NVIC_INT_CTRL ; 觸發PendSV例外
;此時暫存器R0中的值為 NVIC_INT_CTRL(0xE000ED04)
LDR R1, =NVIC_PENDSVSET
;此時暫存器R1中的值為 NVIC_PENDSVSET(0x10000000)
STR R1, [R0]
;將R1中的值作為變數,R0中的值作為地址,然后將R1寫入NVIC_INT_CTRL(0xE000ED04)地址中
;即觸發PendSV例外
CPSIE I ; 開中斷
;打開中斷,將運行到PendSV中斷服務函式
OSStartHang
B OSStartHang ; 程式應永遠不會運行到這里
LDR 表示從存盤器中加載字到一個暫存器中;
STRB 把一個暫存器的低位元組存盤到存盤器(即地址)中
STR 把一個暫存器按字存盤到存盤器(即地址)中
B 跳轉到函式
PendSV中斷服務函式如下所示:(其中,陳述句右邊的注釋為野火注釋,陳述句下方的注釋為本人理解)
PendSV_Handler
; 任務的保存,即把CPU暫存器的值存盤到任務的堆疊中
CPSID I ; 關中斷,NMI和HardFault除外,防止背景關系切換被中斷
MRS R0, PSP ; 將psp的值加載到R0
CBZ R0, OS_CPU_PendSVHandler_nosave ; 判斷R0,如果值為0則跳轉到OS_CPU_PendSVHandler_nosave
;第一次進入,此時PSP的值為0,所以直接跳轉到OS_CPU_PendSVHandler_nosave函式執行
; 進行第一次任務切換的時候,R0肯定為0
/*; 在進入PendSV例外的時候,當前CPU的xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0會自動存盤到當前任務堆疊,同時遞減PSP的值
STMDB R0!, {R4-R11} ; 手動存盤CPU暫存器R4-R11的值到當前任務的堆疊
LDR R1, = OSTCBCurPtr ; 加載 OSTCBCurPtr 指標的地址到R1,這里LDR屬于偽指令
LDR R1, [R1] ; 加載 OSTCBCurPtr 指標到R1,這里LDR屬于ARM指令
STR R0, [R1] ; 存盤R0的值到 OSTCBCurPtr->OSTCBStkPtr,這個時候R0存的是任務空閑堆疊的堆疊頂*/
; 任務的切換,即把下一個要運行的任務的堆疊內容加載到CPU暫存器中
OS_CPU_PendSVHandler_nosave
; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R0, = OSTCBCurPtr
;此時暫存器R0為&OSTCBCurPtr(0x20000018),我把它理解為R0指向OSTCBCurPtr;2級指標?
LDR R1, = OSTCBHighRdyPtr
;此時暫存器R1為&OSTCBHighRdyPtr(0x2000001C),我把它理解為R1指向OSTCBHighRdyPtr
LDR R2, [R1]
;取R1暫存器所指向的地址中的值,賦值給R2;
;此時,R1指向&OSTCBHighRdyPtr指向&Task1TCB(0x20000008),所以R2為&Task1TCB(0x20000008);即R2指向&Task1TCB
STR R2, [R0]
;將R2中的值,存盤到R0所指向的地址中,即將&Task1TCB(0x20000008)寫入R0所指向的地址&OSTCBCurPtr(0x20000018)
;即之后R0指向OSTCBCurPtr作為指標指向Task1TCB,也就是OSTCBCurPtr=&Task1TCB
;此時R2中的地址依舊為&Task1TCB(0x20000008)
LDR R0, [R2]
;取出R2中地址中存放的值,加載到R0
;此時R2中的地址依舊為&Task1TCB(0x20000008),即取出&Task1TCB(0x20000008)中的值加載到R0
;Task1TCB(0x20000008)地址中的值為Task1TCB.StkPtr(0x20000038)(不加&表示指向的地址),也就是Task1Stk堆疊的空閑堆疊的堆疊頂
;即R0指向了Task1Stk堆疊的空閑堆疊的堆疊頂Task1TCB.StkPtr(0x20000038)
LDMIA R0!, {R4-R11}
;將R0地址中的內容出堆疊,4位元組自增依次加載到R4-R11暫存器中
;此時R0指向地址0x20000058,之后的內容就是退出中斷會自動加載到暫存器中
;但是出堆疊指標不是R0,而是PSP,所以后面還要把R0存盤的值賦值給堆疊指標PSP,讓PSP指向那個地址(0x20000058)
MSR PSP, R0 ; 更新PSP的值,這個時候PSP指向下一個要執行的任務的堆疊的堆疊底(這個堆疊底已經加上剛剛手動加載到CPU暫存器R4-R11的偏移)
ORR LR, LR, #0x04 ; 確保例外回傳使用的堆疊指標是PSP,即LR暫存器的位2要為1
CPSIE I ; 開中斷
BX LR ; 例外回傳,這個時候任務堆疊中的剩下內容將會自動加載到xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0(任務的形參)
; 同時PSP的值也將更新,即指向任務堆疊的堆疊頂,在STM32中,堆疊是由高地址向低地址生長的,
;進行BX這一步之后,會按照跳轉加載到xPSR,PC(任務入口地址),R14......暫存器中的PC指標的函式去運行,也就是Task1任務
;此時,堆疊指標PSP指向0x20000078(即Task1Stk堆疊的堆疊頂),等待下次觸發PendSV中斷
;自動將xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0暫存器中的值壓入 Task1Stk堆疊陣列中
NOP ; 為了匯編指令對齊,不然會有警告
END ; 匯編檔案結束
之后執行Task1任務,如下:
void Task1( void *p_arg )
{
for( ;; )
{
flag1 = 1;
delay( 100 );
flag1 = 0;
delay( 100 );
/* 任務切換,這里是手動切換 */
OSSched();
}
}
之后進行任務切換,通過執行OSSched();
void OSSched(void)
{
if(OSTCBCurPtr == OSRdyList[0].HeadPtr)
{
OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
}
else
{
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
}
//到了這一步之后OSTCBCurPtr =&Task1TCB(0x20000008);
//OSTCBHighRdyPtr = &Task2TCB(0x20000010);
//PSP指標指向0x20000078(即Task1Stk堆疊的堆疊頂)
OS_TASK_SW();
}
之后進入PendSV中斷之后,xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0根據PSP指向的地址自動入堆疊,同時PSP自減,等到所有自動入堆疊的暫存器全部入堆疊之后,PSP指向0x20000058,需要手動保存R4-R11暫存器的堆疊頂位置,見代碼如下:
PendSV_Handler
; 任務的保存,即把CPU暫存器的值存盤到任務的堆疊中
CPSID I ; 關中斷,NMI和HardFault除外,防止背景關系切換被中斷
MRS R0, PSP ; 將psp的值加載到R0
;PSP指向0x20000058,經過這一步之后,R0指向0x20000058
CBZ R0, OS_CPU_PendSVHandler_nosave ; 判斷R0,如果值為0則跳轉到OS_CPU_PendSVHandler_nosave
;順序執行,不跳轉到OS_CPU_PendSVHandler_nosave
; 在進入PendSV例外的時候,當前CPU的xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0會自動存盤到當前任務堆疊,同時遞減PSP的值
STMDB R0!, {R4-R11} ; 手動存盤CPU暫存器R4-R11的值到當前任務的堆疊
;將{R4-R11}暫存器的值手動壓入0x20000058指向的地址中,即保存到Task1的堆疊中
;同時R0暫存器中的值遞減,全部入堆疊之后R0中的值為0x20000038,即指向空閑堆疊的堆疊頂
LDR R1, = OSTCBCurPtr
;R1暫存器指向&OSTCBCurPtr(0x20000018),而OSTCBCurPtr =&Task1TCB(0x20000008)
LDR R1, [R1]
;將R1指向的地址中的值拿出來,再賦值給R1,即將&OSTCBCurPtr(0x20000018)中的值Task1TCB(0x20000008)
;拿出來,再賦值給R1
STR R0, [R1]
;將R0暫存器中的值存盤到到R1所指向的地址Task1TCB(0x20000008)
;即讓Task1TCB.StkPtr指向0x20000038,即指向空閑堆疊的堆疊頂
; 任務的切換,即把下一個要運行的任務的堆疊內容加載到CPU暫存器中
OS_CPU_PendSVHandler_nosave
; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R0, = OSTCBCurPtr
;R0指向&OSTCBCurPtr(0x20000018)
LDR R1, = OSTCBHighRdyPtr
;R1指向&OSTCBHighRdyPtr(0x2000001C)
LDR R2, [R1]
;將R1所指向的地址中的內容賦值到R2,即將OSTCBHighRdyPtr = &Task2TCB(0x20000010),
;將&Task2TCB(0x20000010)賦值給暫存器R2
STR R2, [R0]
;將R2中的值加載到R0所指向的地址中,即讓OSTCBCurPtr = &Task2TCB(0x20000010)
LDR R0, [R2]
;將R2所指向的地址中的內容賦值給R2,即R2指向&Task2TCB(0x20000010)指向空閑堆疊的堆疊頂,
;參考上面保存上文時空閑堆疊的堆疊頂保存在任務控制塊的地址中
;也就是說將&Task2TCB(0x20000010)地址中的內容0x20000088地址賦值給R0
LDMIA R0!, {R4-R11}
;將R0所指向的地址0x20000088中的內容出堆疊到R4-R11暫存器中,同時,R0所指向的地址將同步遞增
;全部出堆疊之后,R0所指向的地址為0x200000A8,即自動出堆疊的堆疊底
MSR PSP, R0
;將R0所指向的地址賦值給PSP,當退出中斷時,將從此地址中自動出堆疊,PSP的值同步遞增,等待下一次任務切換時
;將自動壓入堆疊的暫存器中的值自動壓入PSP地址中
ORR LR, LR, #0x04 ; 確保例外回傳使用的堆疊指標是PSP,即LR暫存器的位2要為1
CPSIE I ; 開中斷
BX LR ; 例外回傳,這個時候任務堆疊中的剩下內容將會自動加載到xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0(任務的形參)
; 同時PSP的值也將更新,即指向任務堆疊的堆疊頂,在STM32中,堆疊是由高地址向低地址生長的,
NOP ; 為了匯編指令對齊,不然會有警告
END ; 匯編檔案結束
通過這一個程序之后,任務切換時保存任務當前的運行環境,并將下一個任務的運行環境加載出來,實作了任務的切換,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/263912.html
標籤:其他
