關(guān)于FreeRTOS任務(wù)棧的那點(diǎn)事兒

關(guān)于FreeRTOS任務(wù)棧的那點(diǎn)事兒

by Jason Yuan

0x00 基礎(chǔ)知識(shí)

0x00 00 棧指針

一般來說Cortex-M系列有兩種工作模式,一種叫做Thread Mode(線程模式),另外一種叫做Handler mode(中斷模式)。程序按照編譯好的代碼執(zhí)行,Cortex-M就會(huì)處于線程模式,一旦它收到中斷信號(hào)并執(zhí)行中斷處理函數(shù)時(shí),它就處于中斷模式了。

Cortex-M處理器有兩個(gè)棧指針,一個(gè)叫主棧指針(Main Stack Pointer)(縮寫成MSP),一個(gè)叫程序指針(Processor Stack Pointer)(縮寫成PSP)。MSP用于線程模式,并且在中斷模式下只能用MSP。PSP總是用于線程模式。

0x00 01 SVC異常和PendSV異常

FreeRTOS并沒有使用SVC異常輸入不同的參數(shù),做不同的功能處理。FreeRTOS只是在首次進(jìn)入任務(wù)時(shí)調(diào)用了SVC異常,且只使用了一次。
PendSV主要用于任務(wù)切換,即保存當(dāng)前任務(wù)狀態(tài)保存和提取下一個(gè)任務(wù)的狀態(tài)。在每次SYSTICK異常發(fā)生時(shí)都會(huì)使用指令觸發(fā)PendSV。當(dāng)然,也可以在線程模式下主動(dòng)觸發(fā)PendSV,進(jìn)行任務(wù)切換。

0x00 02 SYSTICK

因?yàn)椴僮飨到y(tǒng)把時(shí)間劃分成了時(shí)間片,而SYSTICK的主要作用就是給操作系統(tǒng)提供時(shí)間劃分。SYSTICK其實(shí)是一個(gè)Cortex-M內(nèi)核自帶的定時(shí)器,給它設(shè)定一個(gè)時(shí)間間隔,它就以該時(shí)間間隔不斷產(chǎn)生中斷。

0x01 棧幀(stack frame)

這部分是FreeRTOS設(shè)計(jì)的核心,也是所有針對Cortex-M內(nèi)核的RTOS設(shè)計(jì)的關(guān)鍵原理。

0x01 00 ARM架構(gòu)下的C函數(shù)實(shí)現(xiàn)

大家都知道ARM架構(gòu)中有一些通用寄存器R0-R15等,C編譯器對C函數(shù)編譯,編譯后的匯編會(huì)使用到這些通用寄存器。而這些寄存器分成了兩類,一類叫做調(diào)用者保存的寄存器(caller saved registers),另一類叫做被調(diào)用者保存的寄存器(callee-saved registers)。
調(diào)用者保存的寄存器包括,R0-R3,R12,LR,PSR。
被調(diào)用者保存的寄存器包括,R4-R11。

  • 在調(diào)用函數(shù)前,程序隨意使用R4-R11的,因?yàn)榻酉聛肀徽{(diào)用的函數(shù)有義務(wù)把R4-R11恢復(fù)成調(diào)用前的樣子。但是如果在調(diào)用完函數(shù)后還需要使用寄存器(R0-R3,R12,LR,PSR),那么就得先把這些寄存器保存起來,接下來得函數(shù)返回后再把這些寄存器恢復(fù)。

  • 在被調(diào)用函數(shù)中,可以隨意使用(R0-R3,R12,LR,PSR),因?yàn)楸徽{(diào)用函數(shù)對他們的值不負(fù)責(zé)。而如果要用到R4-R11,被調(diào)用函數(shù)有責(zé)任保存它們的狀態(tài)。也就是說,被調(diào)用函數(shù)需要保證R4-R11在進(jìn)入函數(shù)時(shí)的值和退出函數(shù)時(shí)的值是一樣的。如果在被調(diào)用函數(shù)中把這些寄存器值改變了,就一定要把他們恢復(fù)成被調(diào)用前的樣子。

除了以上提到的寄存器外,帶浮點(diǎn)型運(yùn)算單元的Cortex-M4內(nèi)核還有額外寄存器需要處理。
調(diào)用者保存的寄存器包括,S0-S15。
被調(diào)用者保存的寄存器包括,S16-S31。

查看通用寄存器,可以發(fā)現(xiàn)R13(SP)和R15(PC)沒有涉及到。SP在常規(guī)的C函數(shù)中當(dāng)然是保存當(dāng)前的棧地址,不管是調(diào)用函數(shù)前還是在被調(diào)用函數(shù)中都要用到SP。使用的??臻g沒有變化,所以SP也不用保存了。而在函數(shù)調(diào)用前,把LR先壓入棧中,然后把當(dāng)前PC傳給LR。這樣在函數(shù)返回時(shí),就會(huì)把LR賦值給PC,進(jìn)而恢復(fù)到函數(shù)調(diào)用前的運(yùn)行位置。

ARM希望把中斷處理函數(shù)也做成C函數(shù)的形式,那么處理方式就和上文提到的一般C函數(shù)的處理過程類似了。進(jìn)入中斷前,首先要把(R0-R3,R12,LR,PSR)保存起來,然后在中斷結(jié)束后恢復(fù)它們。這一切都是通過硬件完成的。
但是,中斷的返回地址并沒有像一般的C函數(shù)調(diào)用一樣存儲(chǔ)在LR中。也就是說,中斷過程中不但要像一般的C函數(shù)調(diào)用一樣保存(R0-R3,R12,LR,PSR),還要保存中斷返回地址(return address)。中斷的硬件機(jī)制會(huì)把EXC_RETURN放進(jìn)LR,在中斷返回時(shí)觸發(fā)中斷返回,而不是一般的C函數(shù)返回。

0x01 01 EXC_RETURN

當(dāng)然,在中斷的入棧和出棧過程中還有個(gè)填充對齊問題,這個(gè)問題對于理解任務(wù)切換并不是個(gè)關(guān)鍵問題,有興趣可以自行查找資料。
如上文所說,LR在進(jìn)入中斷后通過硬件更新為EXC_RETURN。

EXC_RETURN位定義

EXC_RETURN為中斷返回提供了更多的必要信息,如上表所示。

  • bit4,表明了壓入的是8個(gè)字,還是26個(gè)字。因?yàn)閹Ц↑c(diǎn)運(yùn)算單元和不帶浮點(diǎn)運(yùn)算單元是有區(qū)別的。
  • bit3,表明是返回到Thread模式還是Handler模式。也就是該中斷之前是從線程模式進(jìn)入的,還是從中斷中進(jìn)入的(中斷嵌套)。
  • bit2 返回到哪個(gè)棧,是程序棧(Process Stack)還是主棧(Main Stack)。

0x01 02 進(jìn)入中斷和入棧

中斷硬件自動(dòng)入棧

如圖8.8是嵌套壓棧的過程。
第一步,程序在線程模式(Thread Mode)下運(yùn)行,并使用程序棧(PSP)。
第二步,中斷來臨后,把寄存器壓入棧中。但是使用的是哪個(gè)棧,以及壓入的是8個(gè)字還是26個(gè)字,返回的是中斷模式(Handler mode)還是線程模式(Thread mode)這部分硬件自動(dòng)回檢測相應(yīng)寄存器,并生成對應(yīng)EXC_RETURN填入到LR中。因?yàn)槭褂玫氖浅绦驐?PSP),PSP先自減,然后把相應(yīng)的寄存器壓棧。這部分是硬件完成的。
第三步,執(zhí)行中斷服務(wù)函數(shù),中斷服務(wù)函數(shù)使用的是主棧(main stack)。
第四步,有了更高優(yōu)先級的任務(wù)后,第三步的中斷服務(wù)函數(shù)也會(huì)被打斷。此時(shí),使用主棧保存寄存器的狀態(tài)。也會(huì)依據(jù)當(dāng)前的狀態(tài)來生成相應(yīng)的EXC_RETURN,以便下次返回。
第五步,執(zhí)行嵌套的中斷服務(wù)函數(shù)。

0x01 03 中斷返回和出棧

中斷過程中設(shè)置LR為EXC_RETURN
出棧操作

出棧操作,其實(shí)和入棧是個(gè)相反的過程。在退出中斷時(shí),使用哪里的數(shù)據(jù)(MSP或者PSP)恢復(fù)寄存器,返回的棧幀模式(帶不帶FPU,即8個(gè)字還是26個(gè)字),返回的是線程模式(Thread Mode)還是中斷模式(Handler Mode),都是由LR寄存器中的EXC_RETURN決定的。
所以在FreeRTOS的任務(wù)切換過程中修改了LR中的EXC_RETURN,以切換處理器到期望的狀態(tài)。

0x02 FreeRTOS的任務(wù)棧操作

0x02 00 棧初始化

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    /* Simulate the stack frame as it would be created by a context switch
    interrupt. */

    /* Offset added to account for the way the MCU uses the stack on entry/exit
    of interrupts, and to ensure alignment. */
    pxTopOfStack--;

    *pxTopOfStack = portINITIAL_XPSR;   /* xPSR */  [1]
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;    /* PC */ [2]
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) prvTaskExitError;   /* LR */[3]   

    /* Save code space by skipping register initialisation. */
    pxTopOfStack -= 5;  /* R12, R3, R2 and R1. */  [4]
    *pxTopOfStack = ( StackType_t ) pvParameters;   /* R0 */ [5]

    /* A save method is being used that requires each task to maintain its
    own exec return value. */
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_EXEC_RETURN; [6]

    pxTopOfStack -= 8;  /* R11, R10, R9, R8, R7, R6, R5 and R4. */ [7]

    return pxTopOfStack;
}

以上一個(gè)任務(wù)棧的初始化過程。假設(shè)已經(jīng)為任務(wù)棧開辟了一塊內(nèi)存,pxTopOfStack指向的是棧頂。Cortex-M系列的棧是由高至低使用的。
[1],棧頂保存了xPSR,且它的值為portINITIAL_XPSR,0x01000000。其實(shí)就是一個(gè)初始狀態(tài),其中的1表示Thumb狀態(tài)。因?yàn)镃ortex-M只有Thumb狀態(tài)。
[2],棧往下存的是PC(Return Address),值為pxCode,其實(shí)是任務(wù)函數(shù)。當(dāng)從中斷(SVC或者PendSV)返回后,這個(gè)值會(huì)被自動(dòng)存入到PC,即從任務(wù)函數(shù)處開始運(yùn)行。
[3],LR為函數(shù)prvTaskExitError,其實(shí)是不允許從這返回的。如果使用了這個(gè)LR,說明任務(wù)函數(shù)返回了。正常應(yīng)該把改任務(wù)刪除,而不是返回。
[4],為R12, R3, R2 and R1保留位置
[5],R0初始化為pvParameters,也就是任務(wù)初始化中的,任務(wù)參數(shù)。在ARM中,一般R0-R3被用作輸入?yún)?shù)。
[6]初始化EXC_RETURN為portINITIAL_EXEC_RETURN,它的值為0XFFFFFFD。它表示壓入的棧是8個(gè)字,返回線程模式(Thread Mode),且使用程序棧(PSP)。
[7]為R11, R10, R9, R8, R7, R6, R5, R4保留位置。

不帶浮點(diǎn)運(yùn)算單元的棧幀

圖8.2是一個(gè)不帶浮點(diǎn)運(yùn)算的8字,中斷的棧幀狀態(tài)。其中并沒有包含R4-R11,和EXC_RETURN。這是為什么呢?它說的是棧幀,也就值中斷發(fā)生后硬件自動(dòng)完成的壓棧,而R4-R11,和EXC_RETURN是需要手動(dòng)保存的狀態(tài)。

還有一個(gè)點(diǎn)要注意,上面的函數(shù)是通過調(diào)用以下語句完成的。

pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );

可以看到,它在初始化棧頂數(shù)據(jù)后,也把棧頂指針更新到了入棧后的狀態(tài)?,F(xiàn)在的棧頂指針指向的是R4。

0x02 01 啟動(dòng)第一個(gè)任務(wù)

一個(gè)任務(wù)棧已經(jīng)被初始化了,F(xiàn)reeRTOS進(jìn)入第一個(gè)任務(wù)是通過SVC中斷完成的。這個(gè)函數(shù)是一個(gè)中斷服務(wù)函數(shù),源碼如下:

__asm void vPortSVCHandler( void )
{
    PRESERVE8

    /* Get the location of the current TCB. */
    ldr r3, =pxCurrentTCB  ;[1]
    ldr r1, [r3]          ;[2]
    ldr r0, [r1]          ;[3]
    /* Pop the core registers. */
    ldmia r0!, {r4-r11, r14}  ;[4]
    msr psp, r0   ;[5]
    isb          ;[6]
    mov r0, #0    ;[7]
    msr basepri, r0 ;[8]
    bx r14         ;[9]
}

[1][2][3] :其實(shí)是找到當(dāng)前任務(wù)的棧頂指針,賦值給r0。
pxCurrentTCB指向了一個(gè)任務(wù)控制塊,是一個(gè)變量。
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;
[1]中的=pxCurrentTCB是pxCurrentTCB的地址,相當(dāng)于&pxCurrentTCB。所以r3相當(dāng)于&pxCurrentTCB。
[2]r1獲得了變量pxCurrentTCB的值,而pxCurrentTCB是一個(gè)指針變量,指向了一個(gè)任務(wù)控制塊。
[3][r1]獲得了任務(wù)控制塊首地址的值,也就是pxTopOfStack的值。

[4] :這條語句的意思是把R0指向地址的值,由低到高分別賦值給r4-r11,和r14.然后r0更新為r14之上的地址。
什么意思呢,上文的棧初始化,可以清楚地看到棧頂指針是指向了R4。整個(gè)數(shù)據(jù)由低到高的排列是r4,r5,r6,r7,r8,r9,r10,r11,EXEC_RETURN,r0,r1,r2,r3,r12,LR,PC(Return Address),xPSR。調(diào)用[4]指令后,棧頂指針指向的r4-r11和EXEC_RETURN被保存到了寄存器的r4-r11和r14中了。這里我們并不關(guān)心r4-r11,因?yàn)槌跏蓟臅r(shí)候就沒有初始化他們。我們關(guān)心r14,它是鏈接寄存器LR,把EXEC_RETURN賦值給它了。它的值現(xiàn)在是0XFFFFFFD。這意味著,在SVC中斷返回時(shí),棧幀是8字的(bit4 == 1),返回的是線程模式(Thread Mode)(bit3 == 1),使用的棧幀是PSP(bit2 == 1)。
此時(shí)的r0寄存器指向棧中(內(nèi)存)的r0。也就是r0之前指向棧中的r4位置,現(xiàn)在指向棧中的r0位置。

[5]把r0賦值給psp,也就是讓程序棧指針(PSP)指向了棧頂,當(dāng)前的棧頂是由低到高的(r0,r1,r2,r3,r12,LR,PC(Return Address),xPSR)中的r0。
[6]等數(shù)據(jù)傳輸完成。
[7][8]: 開中斷。
[9] : 當(dāng)前的r14(LR)是0XFFFFFFD。跳轉(zhuǎn)到這個(gè)值就如上面的指令[4]所說的一樣。Cortex-M會(huì)任務(wù),棧幀是8字的(bit4 == 1),返回的是線程模式(Thread Mode)(bit3 == 1),使用的棧幀是PSP(bit2 == 1)的中斷返回。所以緊接著就會(huì)把棧中(內(nèi)存)的(r0,r1,r2,r3,r12,LR,PC(Return Address),xPSR)彈出到相應(yīng)的寄存器中。壓入的過程是硬件自動(dòng)完成的。彈出后棧頂指針psp就又回到了棧頂,即回到了當(dāng)初申請到的棧頂位置。

因?yàn)閺棾鰲V斜4娴腜C(Return Address) 到PC寄存器中,而棧中的PC(Return Address)是pxCode,即任務(wù)函數(shù)。那么程序接著就會(huì)運(yùn)行到任務(wù)函數(shù)中。

0x02 02 任務(wù)切換

對于FreeRTOS來說,任務(wù)切換就是整個(gè)系統(tǒng)的核心,那么接下來就分析下任務(wù)切換的過程。FreeRTOS的任務(wù)切換在PendSV中斷中完成,分析PendSV中斷函數(shù)就是關(guān)鍵問題了。

__asm void xPortPendSVHandler( void )
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;

    PRESERVE8

    mrs r0, psp   ;[1]
    isb
    /* Get the location of the current TCB. */
    ldr r3, =pxCurrentTCB       ;[2]
    ldr r2, [r3]               ;[3]

    /* Is the task using the FPU context?  If so, push high vfp registers. */
    tst r14, #0x10              ;[4]
    it eq
    vstmdbeq r0!, {s16-s31}

    /* Save the core registers. */      ;[5]
    stmdb r0!, {r4-r11, r14}

    /* Save the new top of stack into the first member of the TCB. */
    str r0, [r2]            [6]

    stmdb sp!, {r3}          [7]
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY [8]
    msr basepri, r0
    dsb
    isb
    bl vTaskSwitchContext       [9]
    mov r0, #0                 [10]
    msr basepri, r0
    ldmia sp!, {r3}             [11]

    /* The first item in pxCurrentTCB is the task top of stack. */
    ldr r1, [r3]                [12]
    ldr r0, [r1]                [13]

    /* Pop the core registers. */      [14]
    ldmia r0!, {r4-r11, r14}

    /* Is the task using the FPU context?  If so, pop the high vfp registers
    too. */
    tst r14, #0x10              [15]
    it eq
    vldmiaeq r0!, {s16-s31}

    msr psp, r0                 [16]
    isb
    #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
        #if WORKAROUND_PMU_CM001 == 1
            push { r14 }
            pop { pc }
            nop
        #endif
    #endif

    bx r14                  [17]
}

假設(shè)現(xiàn)在有兩個(gè)任務(wù)A和B,各自有一個(gè)任務(wù)棧AS和BS。
前面提到了棧幀的概念,也就是說在進(jìn)入PenvSV中斷前,需要先把一部分寄存器壓入到自己的棧中。例如,當(dāng)前任務(wù)A,運(yùn)行在棧AS中。SYSTICK觸發(fā)了PenvSV,Cortex-M內(nèi)核將任務(wù)A的寄存器狀態(tài)壓入當(dāng)棧AS中。


帶浮點(diǎn)運(yùn)算的棧幀

從圖8.4可以看出來,帶浮點(diǎn)運(yùn)算單元的Cortex-M4壓入了R0,R1,R2,R3,R12,LR,ReturnAddress,xPSR(bit9 == 1),S0,S1...S15,FPSCR。
接著程序運(yùn)行到了xPortPendSVHandler
[1]把psp存到r0,當(dāng)前的psp是任務(wù)A的棧指針,在進(jìn)入PendSV前壓入了R0,R1等寄存器。這時(shí)的psp指向的是任務(wù)棧AS中的R0。
[2]獲取當(dāng)前任務(wù)控制塊的指針。pxCurrentTCB是一個(gè)任務(wù)控制塊的地址。
[3]對pxCurrentTCB解引用后,[r3]的值是任務(wù)控制塊的第一個(gè)元素的值。即r2的值為pxTopOfStack。r2保存了當(dāng)前AS棧的棧指針。
[4]因?yàn)楫?dāng)前是PendSV中斷,所以r14(LR)保存的是EXC_RETURN。該指令就是測試bit4是否為1。為1壓入的是8字,為0壓入的是26字。這句的意思是如果為0,那么CPU的類型是帶浮點(diǎn)運(yùn)算的,那么除了硬件自動(dòng)保存的(S0,S1...S15,FPSCR),還要手動(dòng)保存S16-S31。r0的值就是psp,在把S16-S31壓入后,r0也相應(yīng)地向下更新了。
[5]除了自動(dòng)保存的R0,R1,R2,R3,R12,LR,ReturnAddress,xPSR(bit9 == 1),這里還手動(dòng)要壓入r4-r11, r14(EXC_RETURN)到棧中。這樣,所有的寄存器狀態(tài)都被壓入了棧中,下次回來的時(shí)候也是這個(gè)順序讀取的。
其實(shí)將要切換到的任務(wù)棧BS也是這個(gè)狀態(tài)保存的。接下來就是找到任務(wù)B的棧頂,然后把任務(wù)B相關(guān)的寄存器讀出來。
[6]更新后的任務(wù)A棧指針保存到A任務(wù)控制塊的pxTopOfStack中。下次切換的時(shí)候就可以從這開始取出寄存器狀態(tài)了。
[7]把r3壓入到sp。注意了,這里的sp不是psp,因?yàn)楫?dāng)前在中斷服務(wù)函數(shù)里面所以使用的是主棧MSP,所以把r3壓入到了主棧里面。為什么要壓入r3呢,因?yàn)閞3保存的pxCurrentTCB的地址即&pxCurrentTCB,在后續(xù)調(diào)用vTaskSwitchContext會(huì)用到r3,那么這里就先把r3壓入主棧中,避免數(shù)據(jù)被破壞,留待后續(xù)使用。
[8]關(guān)中斷。
[9]調(diào)用vTaskSwitchContext找到下一個(gè)任務(wù),更新變量pxCurrentTCB的值。pxCurrentTCB的值更新為下一個(gè)任務(wù)的任務(wù)控制塊地址。[7]中已經(jīng)把&pxCurrentTCB保存起來了。通過對&pxCurrentTCB解引用,就能找到下一個(gè)任務(wù)控制塊的地址了。
[10]開中斷
[11]從sp(MSP)恢復(fù)r3,即把r3恢復(fù)成&pxCurrentTCB。后續(xù)就可以利用r3得到新的任務(wù)控制塊了。
[12]得到變量pxCurrentTCB的值,存入r1
[13]得到新的任務(wù)控制塊的第一個(gè)變量(pxTopOfStack)值。r0這是就是任務(wù)B的棧BS的棧頂指針。這時(shí)的棧壓入的寄存器就像前面保存任務(wù)A的狀態(tài)一樣。
[14]彈出棧BS中保存的r4-r11,r14。
[15]如果是按照帶浮點(diǎn)運(yùn)算單元的棧幀保存的,就要彈出s16-s31。
[16]更新棧BS的棧指針給psp。這時(shí)棧內(nèi)保存的s是任務(wù)B之前發(fā)生PendSV時(shí)硬件自動(dòng)產(chǎn)生的棧幀。
[17]跳轉(zhuǎn)到r14(LR),這時(shí)的r14實(shí)際是,任務(wù)B發(fā)生PendSV時(shí)保存的EXC_RETURN。這時(shí)所有的寄存器都更新為了任務(wù)B時(shí)的狀態(tài),棧指針也是任務(wù)B之前退出PendSV的狀態(tài)。任務(wù)切換完成了!
接著Cortex-M就會(huì)認(rèn)為psp保存的是中斷后的棧幀,取出棧幀然后繼續(xù)運(yùn)行。而psp已經(jīng)不是指向任務(wù)A進(jìn)入時(shí)的棧幀,而是任務(wù)B的棧幀了。程序也開始在任務(wù)B中運(yùn)行了。

總結(jié)下,PendSV中任務(wù)切換的過程其實(shí)就是:
1.產(chǎn)生PendSV中斷,硬件自動(dòng)保存棧幀到任務(wù)A的棧中
2.讀取當(dāng)前任務(wù)A的棧指針PSP,手動(dòng)把一些寄存器壓棧到當(dāng)前任務(wù)棧。
3.把當(dāng)前任務(wù)A棧頂指針保存到任務(wù)A的任務(wù)控制塊中。
4.找到下一個(gè)任務(wù)B的任務(wù)控制塊。(查找下一個(gè)優(yōu)先級最高的就緒任務(wù))
5.把任務(wù)B控制塊的棧頂指針指向的數(shù)據(jù)彈出到寄存器中
6.更新PSP為任務(wù)B的棧頂指針。
7.跳出PendSV中斷。
8.硬件自動(dòng)彈出任務(wù)B棧中的棧幀。

0x03 總結(jié)

以上就是FreeRTOS最核心的部分了。如果理解了,也就發(fā)現(xiàn)這個(gè)過程其實(shí)很自然。切換過程就是保存當(dāng)前任務(wù)的所有寄存器到當(dāng)前棧,然后恢復(fù)下一個(gè)任務(wù)的所有寄存器。當(dāng)然其中涉及到很多關(guān)于Cortex-M架構(gòu)的相關(guān)知識(shí),核心就是棧幀stack frame。因?yàn)橹袛鄺拇嬖?,其?shí)有相當(dāng)一部分的寄存器不用手動(dòng)保存,硬件已經(jīng)完成了那部分工作。另外還有關(guān)于兩個(gè)棧指針PSP和MSP,以及EXC_RETURN的使用。
最后感謝左忠凱老師的《FreeRTOS源碼詳解與應(yīng)用開發(fā)》以及相關(guān)視頻。
此外還參考了《The Definitiv Guide to ARM Corte-M3 and Cortex-M4 Processors》。

很開心發(fā)現(xiàn)自己其實(shí)是有能力把任務(wù)切換過程看懂的。之前雖然也一直在讀《The Definitiv Guide to ARM Corte-M3 and Cortex-M4 Processors》,但是卻沒有勇氣去看FreeRTOS真正的核心部分。這次結(jié)合書本,以及試驗(yàn)過程才真正理解了這個(gè)過程,也算是因?yàn)闆]找到工作而給自己的一個(gè)小禮物吧。塞翁失馬,焉知非福,也許這就是生活吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容