FreeRTOS 任務棧大小確定及其溢出檢測


以下轉載自安富萊電子: http://forum.armfly.com/forum.php

FreeRTOS 的任務棧設置
不管是裸機編程還是 RTOS 編程,棧的分配大小都非常重要。 局部變量,函數調用時的現場保護和返
回地址,函數的形參,進入中斷函數前和中斷嵌套等都需要棧空間,棧空間定義小了會造成系統崩潰。
裸機的情況下,用戶可以在這里配置棧大小:

為什么是堆中的?因為我們采用的就是動態創建任務的方式。如果靜態創建,就和我們自己開辟的空間有關,通常靜態創建任務用數組作為容器,但是通常靜態創建的方式我們都不使用。

FreeRTOS 的系統棧設置
上面跟大家講解了什么是任務棧,這里的系統棧又是什么呢?裸機的情況下,凡是用到棧空間的地方
都是在這里配置的棧空間:

在 RTOS 下,上面兩個截圖中設置的棧大小有了一個新的名字叫系統棧空間,而任務棧是不使用這里的空間的。 任務棧不使用這里的棧空間,哪里使用這里的棧空間呢?答案就在中斷函數和中斷嵌套。 

由於 Cortex-M3 和 M4 內核具有雙堆棧指針,MSP 主堆棧指針和 PSP 進程堆棧指針,或者叫 PSP
任務堆棧指針也是可以的。在 FreeRTOS 操作系統中,主堆棧指針 MSP 是給系統棧空間使用的,進
程堆棧指針 PSP 是給任務棧使用的。 也就是說,在 FreeRTOS 任務中,所有棧空間的使用都是通過
PSP 指針進行指向的。 一旦進入了中斷函數以及可能發生的中斷嵌套都是用的 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 的任務狀態
FreeRTOS 的運行支持以下四種狀態:
Running—運行態
當任務處於實際運行狀態被稱之為運行態,即 CPU 的使用權被這個任務占用。
Ready—就緒態
處於就緒態的任務是指那些能夠運行(沒有被阻塞和掛起) ,但是當前沒有運行的任務,因為同優先
級或更高優先級的任務正在運行。
Blocked—阻塞態
由於等待信號量,消息隊列,事件標志組等而處於的狀態被稱之為阻塞態,另外任務調用延遲函數也
會處於阻塞態。
Suspended—掛起態
類似阻塞態,通過調用函數 vTaskSuspend()對指定任務進行掛起,掛起后這個任務將不被執行,只
有調用函數 xTaskResume()才可以將這個任務從掛起態恢復。
使用如下函數即可啟動 FreeRTOS:
vTaskStartScheduler();
函數原型:
void vTaskStartScheduler( void );
函數描述:
函數 vTaskStartScheduler 用於啟動 FreeRTOS 調度器,即啟動 FreeRTOS 的多任務執行。
使用這個函數要注意以下幾個問題:
1. 空閑任務和可選的定時器任務是在調用這個函數后自動創建的。
2. 正常情況下這個函數是不會返回的,運行到這里極有可能是用於定時器任務或者空閑任務的 heap 空
間不足造成創建失敗,此時需要加大 FreeRTOSConfig.h 文件中定義的 heap 大小:
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )

FreeRTOS 的任務恢復

使用如下函數可以實現 FreeRTOS 的任務恢復:
xTaskResume()

使用如下函數可以實現 FreeRTOS 的任務恢復(中斷方式):
xTaskResumeFromISR()

任務棧大小的確定
在基於 RTOS 的應用設計中,每個任務都需要自己的棧空間,應用不同,每個任務需要的棧大小也是
不同的。 將如下的幾個選項簡單的累加就可以得到一個粗略的棧大小:
函數的嵌套調用,針對每一級函數用到棧空間的有如下四項:
函數局部變量。
函數形參,一般情況下函數的形參是直接使用的 CPU 寄存器,不需要使用棧空間,但是這個函
數中如果還嵌套了一個函數的話,這個存儲了函數形參的 CPU 寄存器內容是要入棧的。 所以建
議大家也把這部分算在棧大小中。
函數返回地址,針對 M3 和 M4 內核的 MCU,一般函數的返回地址是專門保存到 LR(Link
Register)寄存器里面的,如果這個函數里面還調用了一個函數的話,這個存儲了函數返回地址
的 LR 寄存器內容是要入棧的。 所以建議大家也把這部分算在棧大小中。
函數內部的狀態保存操作也需要額外的棧空間。
任務切換,任務切換時所有的寄存器都需要入棧,對於帶 FPU 浮點處理單元的 M4 內核 MCU 來說,
FPU 寄存器也是需要入棧的。
針對 M3 內核和 M4 內核的 MCU 來說,在任務執行過程中,如果發生中斷:
M3 內核的 MCU 有 8 個寄存器是自動入棧的,這個棧是任務棧,進入中斷以后其余寄存器入棧
以及發生中斷嵌套都是用的系統棧。
M4 內核的 MCU 有 8 個通用寄存器和 18 個浮點寄存器是自動入棧的,這個棧是任務棧,進入
中斷以后其余通用寄存器和浮點寄存器入棧以及發生中斷嵌套都是用的系統棧。
進入中斷以后使用的局部變量以及可能發生的中斷嵌套都是用的系統棧,這點要注意。
實際應用中將這些都加起來是一件非常麻煩的工作,上面這些棧空間加起來的總和只是棧的最小需求,
實際分配的棧大小可以在最小棧需求的基礎上乘以一個安全系數,一般取 1.5-2。上面的計算是我們用戶
可以確定的棧大小,項目應用中還存在無法確定的棧大小,比如調用printf函數就很難確定實際的棧消耗。
又比如通過函數指針實現函數的間接調用,因為函數指針不是固定的指向一個函數進行調用,而是根據不
同的程序設計可以指向不同的函數,使得棧大小的計算變得比較麻煩。
另外還要注意一點,建議不要編寫遞歸代碼,因為我們不知道遞歸的層數,棧的大小也是不好確定的。
一般來說,用戶可以事先給任務分配一個大的棧空間,然后通過第 8 章介紹的調試方法打印任務棧的
使用情況,運行一段時間就會有個大概的范圍了。這種方法比較簡單且實用些。

函數棧大小確定
函數的棧大小計算起來是比較麻煩的, 那么有沒有簡單的辦法來計算呢? 有的,一般 IDE 開發環境都
有這樣的功能,比如 MDK 會生成一個 htm 文件,通過這個文件用戶可以知道每個被調用函數的最大棧
需求以及各個函數之間的調用關系。但是 MDK 無法確定通過函數指針實現函數調用時的棧需求。另外,
發生中斷或中斷嵌套時的現場保護需要的棧空間也不會統計。 關於 MDK 生成的 map 和 htm 文件的使用,安富萊電子有出過一期視頻教程,可以在這里查看:
http://bbs.armfly.com/read.php?tid=15408

什么是棧溢出
前面為大家講解了如何確定任務棧的大小,那什么又是棧溢出呢?簡單的說就是用戶分配的棧空間不
夠用了,溢出了。 下面我們舉一個簡單的實例,棧生長方向從高地址向低地址生長(M4 和 M3 是這種方
式)。

 



 

FreeRTOS 的棧溢出檢測機制
FreeRTOS 提供了兩種棧溢出檢測機制,這兩種檢測都是在任務切換時才會進行:
方法一
在任務切換時檢測任務棧指針是否過界了,如果過界了,在任務切換的時候會觸發棧溢出鈎子函
數。
void vApplicationStackOverflowHook( TaskHandle_t xTask,
signed char *pcTaskName );
用戶可以在鈎子函數里面做一些處理。這種方法不能保證所有的棧溢出都能檢測到。比如任務在執行
的過程中出現過棧溢出。任務切換前棧指針又恢復到了正常水平,這種情況在任務切換的時候是檢測
不到的。又比如任務棧溢出后,把這部分棧區的數據修改了,這部分棧區的數據不重要或者暫時沒有
用到還好,但如果是重要數據被修改將直接導致系統進入硬件異常,這種情況下,棧溢出檢測功能也
是檢測不到的。
使用方法一需要用戶在 FreeRTOSConfig.h 文件中配置如下宏定義:
#define configCHECK_FOR_STACK_OVERFLOW 1

 方法二
任務創建的時候將任務棧所有數據初始化為 0xa5,任務切換時進行任務棧檢測的時候會檢測末
尾的 16 個字節是否都是 0xa5,通過這種方式來檢測任務棧是否溢出了。相比方法一,這種方法的速
度稍慢些,但是這樣就有效地避免了方法一里面的部分情況。 不過依然不能保證所有的棧溢出都能檢
測到,比如任務棧末尾的 16 個字節沒有用到,即沒有被修改,但是任務棧已經溢出了,這種情況是
檢測不到的。 另外任務棧溢出后,任務棧末尾的 16 個字節沒有修改,但是溢出部分的棧區數據被修
改了,這部分棧區的數據不重要或者暫時沒有用到還好,但如果是重要數據被修改將直接導致系統進
入硬件異常,這種情況下,棧溢出檢測功能也是檢測不到的。
使用方法二需要用戶在 FreeRTOSConfig.h 文件中配置如下宏定義:
#define configCHECK_FOR_STACK_OVERFLOW 2

鈎子函數
鈎子函數的主要作用就是對原有函數的功能進行擴展,用戶可以根據自己的需要往里面添加相關的測
試代碼, 大家可以在 FreeRTOS 工程中檢索這個鈎子函數 vApplicationStackOverflowHook 所在的位
置。
除了 FreeRTOS 提供的這兩種棧溢出檢測機制,還有其它的棧溢出檢測機制,大家可以在 Mircrium 官
方發布的如下這個博文中學習:
https://www.micrium.com/detecting-stack-overflows-part-2-of-2/
對於這種溢出檢測,我測試了幾種情況,確實不容易檢測到,多半進入硬件錯誤中斷,所以以后要是遇到程序硬件中斷死循環,清注意檢測堆棧大小。不確定的時候,先使用printf調試打印任務棧的剩余,選擇一個合理安全的值作為堆棧設置,這確實是個難以簡單計算的東西。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM