ARM:FreeRTOS系統棧和任務棧
背景:ARM 有兩個棧指針PSP和MSP, 通過Control 寄存器來決定SP(R13)使用哪個棧。我們下面談論的系統棧和任務棧,就和這兩個棧指針有關。
FreeRTOS 任務棧
FreeRTOS不同於裸機每個TASK都有一個任務棧。FreeRTOS的任務棧是在任務創建的時候從FreeRTOSConfig.h 定義的Heap 空間中申請:
#define configTOTAL_HEAP_SIZE ((size_t)1024 * 9)
具體任務棧創建的格式如下:(任務棧的大小是 usStackDepth*4)
注意:這種創建方式是動態創建棧的方式,所以會放在Heap 空間中申請。
申請任務棧空間的code如下:
FreeRTOS會定義兩個棧指針來表明任務棧的大小:
1、 *pxTopOfStack棧頂指針
2、*pxStack 棧的起始地址
注意:pxTopOfStack-pxStack會小於分配的任務棧的大小,原因是arm和FreeRTOS系統會額外保存一些寄存器,用於TASK的切換和返回。
具體形式如下(棧的生長方式可以設置,這邊默認是從高往低):
在創建任務棧的過程中,FreeRTOS 會切換 arm 的棧指針,將棧指針切換到PSP,
通過將Control [1] 置 1實現。
FreeRTOS 系統棧
上面跟大家講解了什么是任務棧,這里的系統棧又是什么呢?裸機的情況下,我們在ld script 中定義一個棧空間,並在Startup.S 中將MSP指向這個棧空間,這個就是系統棧。
arm-CM3 中系統復位后給MSP賦的初值就是我們定義的系統棧。
系統棧使用MSP指針。
在 RTOS 下,任務棧是不使用這里的空間的。 既然任務棧不使用這里的棧空間,那么哪里要使用這里的棧空間呢?答案就在中斷函數和中斷嵌套。
**之前講過 arm M3 內核是具有雙堆棧指針,MSP 主堆棧指針和 PSP 進程堆棧指針,或者叫 PSP任務堆棧指針也是可以的。
在 FreeRTOS 操作系統中,主堆棧指針 MSP 是給系統棧空間使用的,進程堆棧指針 PSP 是給任務棧使用的。 也就是說,在 FreeRTOS 任務中,所有棧空間的使用都是通過PSP 指針進行指向的。 一旦進入了中斷函數以及可能發生的中斷嵌套都是用的 MSP 指針。
系統棧和任務棧的分配
FreeRTOS 中每個任務都需要自己的棧空間,棧空間的大小需要考慮如下幾個方面:
函數的嵌套調用:
函數局部變量。
函數形參,一般情況下函數的形參是直接使用的 CPU 寄存器,不需要使用棧空間,但是這個函數中如果還嵌套了一個函數的話,這個存儲了函數形參的 CPU 寄存器內容是要入棧的。
函數返回地址,arm中一般函數的返回地址是專門保存到 LR(LinkRegister)寄存器中的,如果這個函數里面還調用了一個函數的話,這個存儲了函數返回地址的 LR 寄存器內容是要入棧的。
函數內部的狀態保存操作也需要額外的棧空間。
任務切換,任務切換時所有的寄存器都需要入棧。
ARM 在任務執行過程中,如果發生中斷:
M3 內核的 MCU 有 8 個寄存器是自動入棧的,這個棧是任務棧,進入中斷以后其余寄存器入棧以及發生中斷嵌套都是用的系統棧。
M4 內核的 MCU 有 8 個通用寄存器和 18 個浮點寄存器是自動入棧的,這個棧是任務棧,進入中斷以后其余通用寄存器和浮點寄存器入棧以及發生中斷嵌套都是用的系統棧。
注意:進入中斷以后使用的局部變量以及可能發生的中斷嵌套都是用的系統棧。進入中斷后系統會自動將棧指針切換到MSP,退出中斷后再切回PSP。
系統棧空間的分配
實際應用中系統棧空間分配多大,主要是看可能發生的中斷嵌套層數,下面我們就按照最壞執行情況進行考慮,所有的寄存器都需要入棧,此時分為兩種情況:
(進入中斷后,系統會自動將SP切到MSP)
64 字節
對於 Cortex-M3 內核和未使用 FPU(浮點運算單元)功能的 Cortex-M4 內核在發生中斷時需要將 16 個通用寄存器全部入棧,每個寄存器占用 4 個字節,也就是 16*4 = 64 字節的空間。
可能發生幾次中斷嵌套就是要 64 乘以幾即可。 當然,這種是最壞執行情況,也就是所有的寄存器都入棧。
(注:任務執行的過程中發生中斷的話,有 8 個寄存器是自動入棧的,這個棧是任務棧,進入中斷以后其余寄存器入棧以及發生中斷嵌套都是用的系統棧)
200 字節
對於具有 FPU(浮點運算單元)功能的 Cortex-M4 內核,如果在任務中進行了浮點運算,那么在發生中斷的時候除了 16 個通用寄存器需要入棧,還有 34 個浮點寄存器也是要入棧的,也就是(16+34)*4 = 200 字節的空間。當然,這種是最壞執行情況,也就是所有的寄存器都入棧。
(注:任務執行的過程中發送中斷的話,有 8 個通用寄存器和 18 個浮點寄存器是自動入棧的,這個棧是任務棧,進入中斷以后其余通用寄存器和浮點寄存器入棧以及發生中斷嵌套都是用的系統棧)
FreeRTOS 中提供了一個比較好用的棧函數,可以計算出Task 使用了多少棧空間:
uxTaskGetStackHighWaterMark 會顯示出Task歷史上使用的最大棧深度是多少,返回值是歷史上該Task所剩余的最小棧空間的大小。