FreeRTOS--疑難雜症

花了3個晚上,把這個章節看完,受益不淺。html

  1. 最有用的應該是與中斷相關的錯誤,優先排查中斷優先級設置。
  2. 堆棧溢出檢查,可能用到,通常先把堆棧設置的足夠大,只要沒有溢出就是好事,溢出了,掌握了棧溢出鉤子函數排錯很方便。
  3. printf()相關的問題應該儘可能不會出現,畢竟只要須要打印調試信息的狀況下才使用,並且嵌入式系統通常都是用串口重定向的。
    講真,嵌入式中printf()真的挺煩的,嚴重影響性能,個人開發案例中發現,串口打印會影響板子的 power save性能,這是實測到的。

此章節涉及新手最常碰見的3種問題:api

  1. 錯誤的中斷優先級設置
  2. 棧溢出
  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來在編譯時進行控制的。兩種方法都會增長上下文切換的時間。

棧溢出鉤子函數(又稱爲棧溢出回調函數)是一個由內核檢查到棧溢出時調用的函數。要使用棧溢出鉤子函數要知足如下條件:

  1. 在文件FreeRTOSConfig.h中把configCHECK_FOR_STACK_OVERFLOW設置爲1或者2
  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 本博客的文章如無特殊說明,均爲原創,轉載請註明出處。如未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索