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

本章節爲大家講解FreeRTOS任務棧大小的確定方法以及棧溢出檢測方法。給任務分配多大的棧空間,一直是初學者比較頭疼的問題,本章就主要爲大家講解如何解決此問題。
本章教程配套的例子含Cortex-M3內核的STM32F103和Cortex-M4內核的STM32F407以及F429。

11.1 任務棧大小的確定

在基於RTOS的應用設計中,每個任務都需要自己的棧空間,應用不同,每個任務需要的棧大小也是不同的。將如下的幾個選項簡單的累加就可以得到一個粗略的棧大小:
u 函數的嵌套調用,針對每一級函數用到棧空間的有如下四項:
l 函數局部變量。
l 函數形參,一般情況下函數的形參是直接使用的CPU寄存器,不需要使用棧空間,但是這個函數中如果還嵌套了一個函數的話,這個存儲了函數形參的CPU寄存器內容是要入棧的。所以建議大家也把這部分算在棧大小中。
l 函數返回地址,針對M3和M4內核的MCU,一般函數的返回地址是專門保存到LR(Link Register)寄存器裏面的,如果這個函數裏面還調用了一個函數的話,這個存儲了函數返回地址的LR寄存器內容是要入棧的。所以建議大家也把這部分算在棧大小中。
l 函數內部的狀態保存操作也需要額外的棧空間。

u 任務切換,任務切換時所有的寄存器都需要入棧,對於帶FPU浮點處理單元的M4內核MCU來說,FPU寄存器也是需要入棧的。

u 針對M3內核和M4內核的MCU來說,在任務執行過程中,如果發生中斷:
l M3內核的MCU有8個寄存器是自動入棧的,這個棧是任務棧,進入中斷以後其餘寄存器入棧以及發生中斷嵌套都是用的系統棧。
l M4內核的MCU有8個通用寄存器和18個浮點寄存器是自動入棧的,這個棧是任務棧,進入中斷以後其餘通用寄存器和浮點寄存器入棧以及發生中斷嵌套都是用的系統棧。

u 進入中斷以後使用的局部變量以及可能發生的中斷嵌套都是用的系統棧,這點要注意。
實際應用中將這些都加起來是一件非常麻煩的工作,上面這些棧空間加起來的總和只是棧的最小需求,實際分配的棧大小可以在最小棧需求的基礎上乘以一個安全係數,一般取1.5-2。上面的計算是我們用戶可以確定的棧大小,項目應用中還存在無法確定的棧大小,比如調用printf函數就很難確定實際的棧消耗。又比如通過函數指針實現函數的間接調用,因爲函數指針不是固定的指向一個函數進行調用,而是根據不同的程序設計可以指向不同的函數,使得棧大小的計算變得比較麻煩。
另外還要注意一點,建議不要編寫遞歸代碼,因爲我們不知道遞歸的層數,棧的大小也是不好確定的。
一般來說,用戶可以事先給任務分配一個大的棧空間,然後通過第8章介紹的調試方法打印任務棧的使用情況,運行一段時間就會有個大概的範圍了。這種方法比較簡單且實用些。
l 函數棧大小確定
函數的棧大小計算起來是比較麻煩的,那麼有沒有簡單的辦法來計算呢?有的,一般IDE開發環境都有這樣的功能,比如MDK會生成一個htm文件,通過這個文件用戶可以知道每個被調用函數的最大棧需求以及各個函數之間的調用關係。但是MDK無法確定通過函數指針實現函數調用時的棧需求。另外,發生中斷或中斷嵌套時的現場保護需要的棧空間也不會統計。
關於MDK生成的map和htm文件的使用,我們安富萊電子有出過一期視頻教程,可以在這裏查看:http://bbs.armfly.com/read.php?tid=15408

11.2 什麼是棧溢出

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

這裏寫圖片描述

(1) 上圖標識1的位置是RTOS的某個任務調用了函數test()前的SP棧指針位置。

void  test (void);
{
     int i;
     int array[10];
     :
     :
     // Code
}

(2) 上圖標識2的位置是調用了函數test需要保存返回地址到棧空間。這一步不是必須的,對於M3和M4內核是先將其保存到LR寄存器中,如果LR寄存器中有保存上一級函數的返回地址,需要將LR寄存器中的內容先入棧。
(3) 上圖標識3的位置是局部變量int i和int array[10]佔用的棧空間,但申請了棧空間後已經越界了。這個就是所謂的棧溢出了。如果用戶在函數test中通過數組array修改了這部分越界區的數據且這部分越界的棧空間暫時沒有用到或者數據不是很重要,情況還不算嚴重,但是如果存儲的是關鍵數據,會直接導致系統崩潰。
(4) 上圖標識4的位置是局部變量申請了棧空間後,棧指針向下偏移(返回地址+變量i+10個數組元素)*4 =48個字節。
(5) 上圖標識5的位置可能是其它任務的棧空間,也可能是全局變量或者其它用途的存儲區,如果test函數在使用中還有用到棧的地方就會從這裏申請,這部分越界的空間暫時沒有用到或者數據不是很重要,情況還不算嚴重,但是如果存儲的是關鍵數據,會直接導致系統崩潰。

11.3 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提供的這兩種棧溢出檢測機制,還有其它的棧溢出檢測機制,大家可以在Mircrium官方發佈的如下這個博文中學習:https://www.micrium.com/detecting-stack-overflows-part-2-of-2/
    鉤子函數
    鉤子函數的主要作用就是對原有函數的功能進行擴展,用戶可以根據自己的需要往裏面添加相關的測試代碼,大家可以在FreeRTOS工程中檢索這個鉤子函數vApplicationStackOverflowHook所在的位置。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章