花了3個晚上,把這個章節看完,受益匪淺。
- 最有用的應該是與中斷相關的錯誤,優先排查中斷優先級設置。
- 堆棧溢出檢查,可能用到,一般先把堆棧設置的足夠大,只要沒有溢出就是好事,溢出了,掌握了棧溢出鈎子函數排錯很方便。
printf()
相關的問題應該盡量不會出現,畢竟只要需要打印調試信息的情況下才使用,而且嵌入式系統一般都是用串口重定向的。
講真,嵌入式中printf()
真的挺煩的,嚴重影響性能,我的開發案例中發現,串口打印會影響板子的 power save性能,這是實測到的。
此章節涉及新手最常遇見的3種問題:
- 錯誤的中斷優先級設置
- 棧溢出
- 不恰當的使用
printf()
使用configASSERT()
能夠顯著地提高生產效率,它能夠捕獲、識別多種類型的錯誤。強烈建議在開發或者調試中開啟宏configASSERT()
。
中斷優先級
注意:這是頭號需要技術支持的問題,在大多數的移植版本中通過定義configASSERT()
就能夠立刻捕獲這個錯誤。
如果FreeRTOS移植版本支持中斷嵌套,並且中斷服務程序使用了FreeRTOS API,那么必須把中斷優先級設置為configMAX_SYSCALL_INTERRUPT_PRIORITY
或者低一點。沒有這么設置將會導致臨界區失效,反過來就會導致間歇性的錯誤。
當FreeRTOS運行在以下處理器上需要特別注意:
- 中斷優先級使用可能的最高優先級,這就是ARM Cortex 處理器上的情形,還有一些其他的。在這些處理器上,調用FreeRTOS API 的中斷的優先級不能留置未初始化。
- 優先級數值越高表示邏輯上優先級越低,這可能與直覺相反,因此可能導致混淆。同樣這可能在某些ARM Cortex處理器上,可能還有其他的。
- 例如,在某個處理器上一個中斷的優先級為5,正在運行,但是能夠被一個優先級為4的中斷打斷。因此,如果
configMAX_SYSCALL_INTERRUPT_PRIORITY
設置為5,那么任何其他的使用FreeRTOS API的中斷的優先級必須設置為5甚至更高。在這種情形下優先級為5或者6的是有效的,但是優先級為3的中斷是無效的。 - 不同的庫實現期待中斷優先級用不同的方式指定。此外尤其是針對ARM Cortex處理器相關的庫,它們的中斷優先級在寫入硬件寄存器之前是經過位移的。某些庫可能自己進行位移操作,然而其他的庫期待中斷優先級在傳給庫函數之前已經進行了位移操作。
- 同樣架構上的不同的實現,實現的是中斷優先級的比特位不同。例如同樣的
Cortex-M
處理器某一個廠商可能實現了3個優先級比特位,但是另一個廠商實現了4個優先級比特位。 - 定義一個中斷優先級的比特位被分成兩個部分,一部分定義搶占的級別,另外的比特位定義子優先級。確保所有的比特位都是指定搶占的優先級,而子優先級不使用。
在某些移植版本中configMAX_SYSCALL_INTERRUPT_PRIORITY
有一個別名configMAX_API_CALL_INTERRUPT_PRIORITY
。
棧溢出
棧溢出是第二個經常尋求技術支持的問題。FreeRTOS提供了幾個特性來輔助捕獲和調試和棧相關的問題。
API函數uxTaskGetStackHighWaterMark()
每個任務都在維護自己的棧,棧的總大小在創建任務的時候就指定了。函數uxTaskGetStackHighWaterMark()
就是用來查詢分配給這個任務的棧接近棧溢出的程度。返回值稱為棧的高水位線。
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
任務使用棧的多少,隨着任務的運行和中斷的處理時而增加時而減少。uxTaskGetStackHighWaterMark()
返回自任務開始運行以來剩余可用的棧空間的最小值。它返回的是棧未使用的空間占最大值的比值。高水位越接近於0,那么這個任務的棧就越快要溢出。
運行時棧檢查
FreeRTOS提供了兩種在運行時檢查棧的機制。都是由文件FreeRTOSConfig.h
中的configCHECK_FOR_STACK_OVERFLOW
來在編譯時進行控制的。兩種方法都會增加上下文切換的時間。
棧溢出鈎子函數(又稱為棧溢出回調函數)是一個由內核檢查到棧溢出時調用的函數。要使用棧溢出鈎子函數要滿足以下條件:
- 在文件
FreeRTOSConfig.h
中把configCHECK_FOR_STACK_OVERFLOW
設置為1或者2。 - 實現以下鈎子函數,使用完全一樣的函數名字和原型:
void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName);
棧溢出鈎子函數會讓捕獲和調試棧錯誤更加的簡單,但是發生棧溢出錯誤時是沒有辦法恢復的。此函數把發生棧溢出的任務的句柄和名字傳遞進去。
棧溢出鈎子函數會在一個中斷的上下文中進行調用。
某些微控制器在檢測到一個錯誤的內存訪問時會產生一個錯誤異常,這個錯誤異常的觸發會使得內核根本就沒有機會調用棧溢出鈎子函數。
運行時棧檢查--方法1
當進行如下設置是會選擇方法1.
#define configCHECK_FOR_STACK_OVERFLOW 1
每當一個任務被切換出去時它的整個的執行上下文都會被保存到它自己的棧中。很有可能這就是棧使用率達到最大值的時候。當使用方法1是,當任務的上下文被保存之后內核回去檢查棧指針是否在棧可用空間內。如果發現棧指針已經超出了可用的范圍那么就會調用棧溢出鈎子函數。
方法1執行速度快,但是在發生上下文切換時有可能會錯過棧溢出。
運行時棧檢查--方法2
進行如下設置后才會選擇方法2.
#define configCHECK_FOR_STACK_OVERFLOW 2
除了方法1中的行為,方法2還會執行其他的檢查。
創建任務時它的棧會被一個已知的樣本填充。任務2檢查棧空間的最后20個字節,驗證這個已知的樣本是否已經被覆蓋。如果這20個字節的值與預期值不一樣那么就會調用棧溢出鈎子函數。
方法2不如方法1快,當時相對來講還是快,畢竟只是測試20個字節。很有可能方法2會捕獲到所有的棧溢出,但是有可能(幾乎不可能)某些棧溢出還是漏掉了。
不恰當地使用printf()
和sprintf()
不恰當地只用printf()
是一種常見的錯誤源,並且沒有意識到這種錯誤,通常應用開發者會增加更多的printf()
來輔助調試,結果就是加重了問題。
許多交叉編譯器廠商會提供一種適合在小型嵌入式系統中使用的printf()
的實現。即便在這種情形下,printf()
的實現可能並不是線程安全的,幾乎可以肯定不適合在中斷服務程序中使用,並且取決於輸出被重定向到哪里,會占用相當長的一段執行時間。。
如果小型嵌入式系統的printf()
的實現不可用,並且使用了通用的printf()
的實現,那么就需要特別注意了:
- 僅僅增加了一個對
printf()
或者sprintf()
的調用就會急劇的增加應用執行文件的體積; - 如果使用了
heap_3
以外的存儲空間方案,printf()
和sprintf()
調用了malloc()
,這個是無效的。 printf()
和sprintf()
可能會申請一個幾倍於通常情況的棧空間。
Printf-stdarg.c
許多的FreeRTOS示例工程了使用了一個printf-stdarg.c
的文件,它提供了一個極小的、棧使用率非常高效的能夠取代標准庫函數版本的sprintf()
實現。在大多數情形下,使得任務每次調用sprintf()
或者相關的函數卻分配更少的棧空間。
printf-stdarg.c
提供了一種機制把printf()
輸出重定向,一個字節一個字節的輸出,雖然慢,但是卻極大地減少了棧空間的占用。
注意:並不是所有的FreeRTOS下載副本中文件printf-stdarg.c
都實現了snprintf()
函數。沒有實現snprintf()
的副本直接忽略緩沖區大小參數,它們是直接映射到sprintf()
函數。
printf-stdarg.c
是開源的,但是是第三方擁有的,因此它的授權和FreeRTOS是分開的。它的授權條款在文件的首部。
其他的常見錯誤
症狀:添加一個簡單任務到例程中卻導致了例程掛掉
創建任務需要從堆中分配內存。許多示例工程的棧空間僅僅能夠容納例程任務,因此在創建了例程任務后,沒有足夠的堆空間留給其他更多的任務,隊列,事件組,信號量。
空閑任務,又或許是FreeRTOS的守護進程,在調用vTaskStartScheduler()
時是自動創建的。只有當堆空間不足以創建這些任務時vTaskStartScheduler()
才會返回。在調用vTaskStartScheduler()
之后添加一個for(;;);
會讓這個問題更容易排錯。
要想添加更多的任務,要么擴大堆空間,要么減少已經存在的例子任務。
症狀:中斷中使用API導致應用掛掉
在中斷服務程序中不要使用API,除非API名字是以FromISR()
結尾。特別地,在中斷中不要創建一個臨界區,除非使用中斷安全的宏。
在支持中斷嵌套的FreeRTOS移植版本中,如果中斷優先級高於configMAX_SYSCALL_INTERRUPT_PRIORITY
就不要在其中使用 API 函數。
症狀:有時應用程序在中斷服務函數中掛掉
首先要檢查中斷是否產生了棧溢出。有的移植版本中只檢查任務的棧溢出,並沒有檢查中斷是否棧溢出。
中斷的定義和使用方式隨着移植版本和編譯器的不同而不同。因此,其次要檢查語法,宏定義,調用規則在中斷服務程序中的使用是否與移植文檔中的完全一致,是否與例程中的完全一致。
如果應用運行在數值越低的優先級表示邏輯上越高的優先級的處理器上,那么需要確保分配給中斷的優先級要考慮到這種情況,因為它看起來是違反直覺的。
如果應用運行在把中斷優先級默認設置為最大可能的優先級的處理器上,需要確保每個中斷的優先級沒有留置為默認值。
症狀:調度器嘗試啟動第一個任務時掛掉
確保設置了FreeRTOS的中斷句柄。參考FreeRTOS移植文檔,還有示例程序。
某些處理器必須在調度器啟動之前處於特權模式。最簡單的實現方法是在C語言main()
之前的啟動代碼中就把處理器置於特權模式。
症狀: 中斷被異常地禁止了,又或者臨界區沒有正確地嵌套
如果在調度器啟動之前就調用了FreeRTOS API函數那么中斷就會被蓄意地禁止,直到第一個任務啟動之后才會重新使能。這樣做是為了保護系統不掛掉,原因在於初始化過程中中斷嘗試調用FreeRTOS API函數,然而調度器還沒有啟動,它可能處於一個不一致的狀態。
除了調用taskENTER_CRITICAL()
和taskEXIT_CRITICAL()
函數之外不要使用任何其它的方法來更改微處理器的中斷使能位和優先級標志。這兩個宏會統計中斷嵌套深度確保當中斷嵌套深度為0時中斷又使能。需要知悉某些庫函數可能在內部使能和禁止中斷。
症狀:遠在調度器啟動之前應用就掛掉了
有可能發生上下文切換的中斷是禁止在調度器啟動之前就開始執行的。同樣的規則適用於嘗試發送或者接收FreeRTOS對象(例如隊列和信號量)的任何中斷服務程序。上下文切換必須在調度器啟動之后才能發生。
很多API函數必須在調度器啟動之后才能調用。最好是在調用vTaskStartScheduler()
之后將API的使用限制在創建諸如任務,隊列和信號量上,而不是使用這些對象。
症狀: 在調度器掛起時又或者是臨界區內部調用API函數會導致應用程序掛掉
調用函數vTaskSuspendAll()
會掛起調度器,調用函數xTaskResumeAll()
會恢復調度器。
調用函數taskENTER_CRITICAL()
會進入臨界區,調用函數taskEXIT_CRITICAL()
會退出臨界區。
在調度器掛起時或者臨界區內永遠不要調用API函數。
聲明
歡迎轉載,請注明出處和作者,同時保留聲明。
作者:LinTeX9527
出處:http://www.cnblogs.com/LinTeX9527/p/8031565.html
本博客的文章如無特殊說明,均為原創,轉載請注明出處。如未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。