__asm void xPortPendSVHandler( void ) { extern uxCriticalNesting; extern pxCurrentTCB; extern vTaskSwitchContext; PRESERVE8 //棧的8字節對齊 mrs r0, psp //讀取當前psp進程指針,存入r0 isb
/* 獲取當前任務控制塊 */ ldr r3, =pxCurrentTCB //把當前任務控制塊的指針給r3 ldr r2, [r3] //把r3地址中的值給r2,r2中就存儲當前的任務控制塊 /* 是否使用了FPU,使用的話要手動保存s16~s31 */ tst r14, #0x10 it eq vstmdbeq r0!, {s16-s31} /* Save the core registers. */ stmdb r0!, {r4-r11, r14} //含義::依次壓棧r0 = r0 - 4,先壓r14,r0 = r14(即將r14中的內容放入r0所指的內存地址) // r0 = r0 - 4,再壓r11,r0 = r11。 // r0 = r0 - 4,再壓r10,r0 = r10......r0 = r0 - 4,最后壓r4,r0 = r4。 // 則r0中就保存最新的棧頂指針值 /* 保存最新的棧頂指針到當前任務控制塊的第一字段*/ str r0, [r2] //把r0的值存入r2的地址,相當於*r2 = r0,[r2]中已經保存了最新的任務的控制塊,經過上面的兩個ldr指令,r2中已經保存最新的任務控制塊的地址 stmdb sp!, {r3} //將寄存器R3的值臨時壓棧,寄存器r3中仍然保存着當前任務的任務控制塊, //而接下來要調用函數vTaskSwitchContext,防止r3的值被改寫,故臨時壓棧 mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 //關中斷,進入臨界區 dsb isb bl vTaskSwitchContext //調用函數vTaskSwitchContext,此函數用來獲取下一個要運行的任務,並將pxCurrentTCB更新為要運行的這個任務 mov r0, #0 msr basepri, r0 //開中斷,退出臨界區 ldmia sp!, {r3} //剛剛保存的寄存器R3的值出棧,恢復寄存器R3的值。注意,經過調用函數vTaskSwitchContext,此時 //pxCurrentTCB的值已經改變了,所以讀取R3所保存的地址處的數據就會發現其值改變了,成 //為了下一個要運行的任務的任務控制塊。 ldr r1, [r3] ldr r0, [r1] //獲取新的運行任務的棧頂,並存到r0中去 /* 彈出內核寄存器 */ ldmia r0!, {r4-r11, r14} //含義::依次出棧 r0 = r0 + 4,先彈出r14,r0 = r14(即將r14中的內容放入r0所指的內存地址) // r0 = r0 + 4,再彈r11,r0 = r11。 // r0 = r0 + 4,再彈r10,r0 = r10......r0 = r0 - 4,最后出r4,r0 = r4。 /*是夠使用了FPU,使用了徐要手動保存s16~s31*/ tst r14, #0x10 it eq vldmiaeq r0!, {s16-s31} msr psp, r0 //更新進程棧指針PSP的值 isb #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata *///這塊暫時不用管 #if WORKAROUND_PMU_CM001 == 1 push { r14 } pop { pc } nop #endif #endif bx r14 //執行此行代碼以后硬件自動恢復寄存器R0~R3、R12、LR、PC和xPSR的值,確定異常返回以后應該進入處理器模式還是進程模式,使用主棧指針(MSP)還是進程棧指針(PSP)。
很明顯這里會進入進程模式,並且使用進程棧指針(PSP), 寄存器PC值會被恢復為即將運行的任務的任務函數,新的任務開始運行!至此,任務切換成功。
}