天天看點

《 嵌入式系統設計與實踐》一一3.7 處理錯誤

3.7 處理錯誤

代碼的生命周期讓我震驚。因為有時侯好像我們總在以不同的方式重寫同樣的舊代碼,有一天我們可能會發現自己在10年前入門階段寫的一段代碼正在被一個财富500強公司使用。既然代碼工作的如此之好,為什麼還要修複其中那些隐藏很深的問題呢?

在某些時候,代碼将失效。這一點可能讓人會感到可怕。一個錯誤的發生,要麼是由于代碼本身,要麼是由于環境中一個意想不到的情況。有兩種方法來處理錯誤。首先,該系統可以進入優雅降級的狀态,在這個狀态中軟體會盡可能做到最好。或者,系統可能會優雅地立即失效。長期運作的傳感器類型的系統需要采用前一個方法,醫療系統則要求後者。無論哪種方式,系統必須安全地失效。

但如何實作其中之一的方法?更重要的是,應該采用什麼标準來确定哪些子系統應該實作哪一種錯誤處理方法?具體怎麼做取決于不同的産品要求,我們需要在設計期間思考錯誤處理問題。

3.7.1 一緻的方法

函數應竭盡所能地處理錯誤。例如,如果一個變量可能超出範圍,那麼這個範圍應當固定,并适當記錄錯誤。函數可以傳回錯誤以允許調用者處理這些問題。調用者應該檢查并處理錯誤,這意味着可能進一步将其向上傳遞到一個多層的應用程式。在許多情況下,如果錯誤不是那麼重要,就不需要對其進行檢查,那麼同樣不需要将它傳回。另一方面,在有些情況下,隻需要傳回一個僅用于測試的診斷代碼。可以在注釋中指出傳回的錯誤代碼不是給正常運作的程式使用的,或隻用于在assert()函數調用中。

并不是所有的嵌入式系統都實作了assert()函數,但以适合于系統的方法去實作它并不難。可以輸出到調試控制台的消息、輸出到系統控制台或者日志的消息,一個斷點指令(如bkpt),或者甚至在錯誤發生時觸發一個輸入/輸出線或led。輸出會改變嵌入式系統的時序,是以将錯誤通信函數分離出來以允許用其他輸出方法(如led),這樣做往往是有益的。

一個應用程式或者系統的錯誤傳回代碼應該在代碼庫上進行标準化。可以建立一個高層errorcodes.h檔案(或類似的),以枚舉的格式提供一緻的錯誤代碼定義。建議的錯誤代碼包括:

沒有錯誤(應該總是為0)。

未知的錯誤(或無法識别的錯誤)。

錯誤的參數。

錯誤的索引(指針超出範圍或者為空)。

未初始化的變量或子系統。

災難性的失效(這可能會導緻處理器複位,除非它在開發模式下,在這種情況下,它可能會引發一個斷點或自旋循環)。

應該有一個最小數量的錯誤(一般性錯誤),這樣應用程式可以解釋它們。雖然在将特異性丢棄(uart_failed_to_init_because_second_parameter_was_too_high)的同時,泛化使得錯誤處理和使用更容易(如果parmeter_bad錯誤發生在某個子系統中,那麼就已經有了一個合适的地方可以開始尋找這個錯誤)。從本質上講,讓它保持盡可能簡單,確定将重要的資訊(某個錯誤)提供給開發者,這樣調試時就可以進一步挖掘錯誤發生的地方和原因。

3.7.2 錯誤處理庫

錯誤處理庫也是一個不錯的想法。實作它的方式之一就是讓每個函數傳回一個錯誤代碼。不用再像下面這樣調用函數和檢查結果:

error = functionfoo();

if (error != no_error) {

errorset (&globalerrorcode, error);

}

調用錯誤檢查函數中的函數:

errorset(&globalerrorcode, functionfoo());

如果這個函數沒有傳回任何錯誤,那麼errorset函數不會覆寫以前的錯誤條件。允許一次調用多個函數,并在最後檢查錯誤,而不是在每個函數調用之後都去檢查。

在這樣的錯誤處理庫中,将有四個函數這四個函數對應用程式有意義:errorset、errorget、errorprint和errorclear。這個庫應該設計得便于調試和測試,雖然這個機制即使在開發結束後也應當保留在程式裡。比如,errorprint可能會從向序列槽寫日志資訊轉變為隻是觸發輸入/輸出線的一個小函數。這不最終使用者需要處理的錯誤,這個是當産品單元不能正常工作時,開發者應當處理的錯誤。

3.7.3 調試時序錯誤

在調試硬體/軟體之間的互動行為(或者任何時間關鍵的軟體)時,日志或者printf之類的串行輸出會改變代碼的時序。在大多數情況下,一個與時序相關的問題是否出現(或者消失)取決于輸出語句以及(或者)斷點的位置。而當工具和問題互相作用時,調試變得尤其困難。

使用錯誤處理庫(或者如第2章所述的日志庫)的好處之一,就是我們可以不用實際輸出資料,而将資料存儲在ram中,這是一個很快的方法。事實上,如果遇到時序上的麻煩,考慮使用一個小的緩沖區(4~16位元組,取決于可用ram的大小)來儲存來自軟體的信号(1~2位元組)。在代碼中,在感興趣的觸發點(例如,assert或者errorset)填充這個緩沖區。當代碼退出時間關鍵區域之後,再将緩沖區的内容轉儲出來。如果需要對最新發生的錯誤資訊進行處理,則可以使用一個環形緩沖區持續不斷地捕獲最近發生的一些事件(在第6章中讨論環形緩沖區)。

或者,如果在設計電路闆的時候有輸入,我強烈推薦在電路闆上預留多餘的處理器i/o引腳,并可以容易地通過頭檔案進行通路。它們可以在調試的時候派上用場(特别是複雜的時序問題,如串行輸出破壞時序),用于顯示被測試的系統狀态,并擷取處理器周期剖面。

繼續閱讀