錯誤處理不太可能成為任何用于嵌入式系統(tǒng)應(yīng)用的操作系統(tǒng)的主要功能。這是資源限制的必然結(jié)果——所有嵌入式系統(tǒng)都有某種限制。這也是合乎邏輯的,因為只有有限數(shù)量的嵌入式系統(tǒng)有機會像桌面系統(tǒng)一樣運行——即為用戶提供在發(fā)生某些異常事件時決定下一步做什么的機會。
在 Nucleus SE 中,錯誤檢查大致有三種類型:
·
對所選配置進行“健全性檢查”的設(shè)施——只是為了確保所選選項是一致的
·
·
可選包含代碼來檢查運行時行為
·
·
特定的 API 函數(shù)有助于設(shè)計更強大的代碼
·
本文將介紹這些內(nèi)容以及一些關(guān)于用戶實現(xiàn)的診斷的想法。
配置檢查
Nucleus SE 的設(shè)計非常便于用戶配置,因此可以根據(jù)需要進行定制,以充分利用可用資源。這種可配置性是一個挑戰(zhàn),因為選項的數(shù)量以及它們之間的相互依賴性非常大。正如之前許多文章中所述,Nucleus SE 的大多數(shù)用戶配置都是通過 在文件nuse_config.h中設(shè)置#define常量來執(zhí)行的。
為了幫助識別配置錯誤,包含了一個文件 - nuse_config_check.h (即通過#include 到nuse_config.c中),該文件對#define符號執(zhí)行了許多一致性檢查 。以下是該文件的摘錄:
/*** Tasks and task control ***/
#if NUSE_TASK_NUMBER < 1 || NUSE_TASK_NUMBER > 16
#error NUSE: invalid number of tasks – must be 1-16
#endif
#if NUSE_TASK_RELINQUISH && (NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER)
#error NUSE: NUSE_Task_Relinquish() selected – not valid with
priority scheduler
#endif
#if NUSE_TASK_RESUME && !NUSE_SUSPEND_ENABLE
#error NUSE: NUSE_Task_Resume() selected – task suspend not
enabled
#endif
#if NUSE_TASK_SUSPEND && !NUSE_SUSPEND_ENABLE
#error NUSE: NUSE_Task_Suspend() selected – task suspend not
enabled
#endif
#if NUSE_INITIAL_TASK_STATE_SUPPORT && !NUSE_SUSPEND_ENABLE
#error NUSE: Initial task state enabled – task suspend not
enabled
#endif
/*** Partition pools ***/
#if NUSE_PARTITION_POOL_NUMBER > 16
#error NUSE: invalid number of partition pools – must be 0-16
#endif
#if NUSE_PARTITION_POOL_NUMBER == 0
#if NUSE_PARTITION_ALLOCATE
#error NUSE: NUSE_Partition_Allocate() enabled – no
partition pools configured
#endif
#if NUSE_PARTITION_DEALLOCATE
#error NUSE: NUSE_Partition_Deallocate() enabled – no
partition pools configured
#endif
#if NUSE_PARTITION_POOL_INFORMATION
#error NUSE: NUSE_Partition_Pool_Information() enabled –
no partition pools configured
#endif
#endif
執(zhí)行的檢查包括以下內(nèi)容:
·
驗證已配置至少一個(但不超過十六個)任務(wù)
·
·
確認所選的 API 函數(shù)與所選的調(diào)度程序或其他選項不一致
·
·
驗證是否已指定不超過 16 個其他內(nèi)核對象的實例
·
·
確認沒有為根本沒有實例化的對象選擇 API 函數(shù)
·
·
確保在未啟用這些功能時不選擇信號和系統(tǒng)時間的 API 函數(shù)
·
·
驗證所選調(diào)度程序類型和相關(guān)選項
·
在所有情況下,檢測到錯誤都會導致編譯#error 語句。這通常會導致編譯以指定的消息終止。
此文件不會使創(chuàng)建不合邏輯的配置變得不可能,但會使這種配置不太可能發(fā)生。
API 參數(shù)檢查
與 Nucleus RTOS 一樣,Nucleus SE 具有可選功能,可包含代碼以在運行時驗證 API 函數(shù)調(diào)用參數(shù)。通常,這只會在初始調(diào)試和測試期間使用,因為生產(chǎn)代碼中的內(nèi)存和運行時開銷是不可取的。
通過將nuse_config.h 中的NUSE_API_PARAMETER_CHECKING設(shè)置 為TRUE來啟用參數(shù)檢查。這樣就可以編譯所需的附加代碼。以下是 API 函數(shù)參數(shù)檢查的示例:
STATUS NUSE_Mailbox_Send(NUSE_MAILBOX mailbox, ADDR *message,
U8 suspend)
{
STATUS return_value;
#if NUSE_API_PARAMETER_CHECKING
if (mailbox >= NUSE_MAILBOX_NUMBER)
{
return NUSE_INVALID_MAILBOX;
}
if (message == NULL)
{
return NUSE_INVALID_POINTER;
}
#if NUSE_BLOCKING_ENABLE
if ((suspend != NUSE_NO_SUSPEND) &&
(suspend != NUSE_SUSPEND))
{
return NUSE_INVALID_SUSPEND;
}
#else
if (suspend != NUSE_NO_SUSPEND)
{
return NUSE_INVALID_SUSPEND;
}
#endif
#endif
此參數(shù)檢查可能會導致 API 函數(shù)調(diào)用返回錯誤代碼。這些都是形式為NUSE_INVALID_xxx的負值 (例如NUSE_INVALID_POINTER)——完整的定義集包含在nuse_codes.h中。
可以包含額外的應(yīng)用程序代碼(可能是有條件編譯的)來處理這些錯誤值,但最好使用現(xiàn)代嵌入式調(diào)試器的數(shù)據(jù)監(jiān)控功能來檢測它們。
參數(shù)檢查會引入內(nèi)存開銷(額外代碼)和運行時性能,因此其使用有些干擾。由于 Nucleus SE 的完整源代碼可供開發(fā)人員使用,因此如果需要絕對的精度,可以“手動”對生產(chǎn)代碼進行檢查和調(diào)試。
任務(wù)堆棧檢查
只要未使用“運行至完成”調(diào)度程序,Nucleus SE 就會提供任務(wù)堆棧檢查功能,該功能與 Nucleus RTOS 中提供的類似,并提供剩余堆??臻g的指示。此 API 調(diào)用 - NUSE_Task_Check_Stack() - 在上一篇文章中進行了詳細描述。本文后面的“用戶診斷”部分將討論有關(guān)堆棧錯誤檢查的一些想法。
版本信息
Nucleus RTOS 和 Nucleus SE 有一個 API 函數(shù),它簡單地返回有關(guān)內(nèi)核的版本/發(fā)布信息。
Nucleus RTOS API 調(diào)用
服務(wù)調(diào)用原型:
字符*NU_Release_Information(VOID);
參數(shù):
沒有任何
返回:
指向以 NULL 結(jié)尾的版本字符串的指針
Nucleus SE API 調(diào)用
此 API 調(diào)用支持 Nucleus RTOS API 的關(guān)鍵功能。
服務(wù)調(diào)用原型:
char *NUSE_Release_Information(void);
參數(shù):
沒有任何
返回:
指向以 NULL 結(jié)尾的版本字符串的指針
Nucleus SE 實施發(fā)布信息
這個API調(diào)用的實現(xiàn)幾乎很簡單。返回一個指向在nuse_globals.c中聲明和初始化的字符串常量NUSE_Release_Info的指針。
該字符串采用 Nucleus SE – Xyymmdd的形式,其中:
X 是發(fā)布狀態(tài):A = alpha;B = beta;R = 已發(fā)布
yy 是發(fā)布年份
mm 是發(fā)布月份
dd 是發(fā)布日期
與 Nucleus RTOS 的兼容性
Nucleus RTOS 包含一個可選功能,用于維護歷史記錄。內(nèi)核記錄各種系統(tǒng)活動的詳細信息。提供 API 函數(shù)以使應(yīng)用程序能夠:
·
啟用/禁用歷史記錄保存
·
·
記錄歷史
·
·
檢索歷史記錄條目
·
Nucleus SE 不支持此功能。
Nucleus RTOS 還包括一些錯誤管理宏,它們執(zhí)行斷言并提供調(diào)用用戶定義的致命錯誤函數(shù)的方法。這些宏有條件地包含在 OS 構(gòu)建中。Nucleus SE 不支持此類功能。
用戶診斷
到目前為止,我們在本文中研究了 Nucleus SE 本身提供的診斷和錯誤檢查功能?,F(xiàn)在是一個很好的機會來考慮如何使用內(nèi)核提供的功能和/或應(yīng)用我們對其內(nèi)部結(jié)構(gòu)和實現(xiàn)的了解來實現(xiàn)用戶定義或面向應(yīng)用程序的診斷。
特定應(yīng)用診斷
幾乎任何應(yīng)用程序都可以添加額外的代碼來在運行時檢查其自身的完整性。使用多任務(wù)內(nèi)核,讓特定任務(wù)執(zhí)行這項工作非常方便和直接。顯然,特定于應(yīng)用程序的診斷不在本系列文章的討論范圍內(nèi),但我們可以考慮一些廣泛的想法。
內(nèi)存檢查
內(nèi)存的正確運行顯然對任何基于處理器的系統(tǒng)的完整性都至關(guān)重要。同樣明顯的是,災(zāi)難性故障會阻止任何軟件(更不用說診斷程序)運行。但在某些情況下,一定程度的故障會發(fā)生,這是一個主要問題,但不會完全阻止代碼執(zhí)行。測試內(nèi)存是一個相當復雜的主題,遠遠超出了本文的范圍,所以我只能給出一些一般性的想法。
在 RAM 中可能發(fā)生的兩個最常見故障是:“卡住位”——某個位的值為零或一,無法更改;以及“串擾”——相鄰位相互干擾??梢酝ㄟ^依次將適當?shù)臏y試模式寫入和讀回每個 RAM 位置來測試這些故障。有些測試實際上只能在啟動時執(zhí)行,甚至在建立堆棧之前;例如,“移動位”測試,其中內(nèi)存中的每個位都設(shè)置為 1,并檢查其他每個位以確保其為零。其他逐字節(jié)模式測試可以即時執(zhí)行,只要您確保在 RAM 位置損壞時不會發(fā)生上下文切換。使用 Nucleus SE 臨界區(qū)分隔宏NUSE_CS_Enter() 和NUSE_CS_Exit()在nuse_types.h中定義,簡單且可移植。
各種類型的 ROM 也有可能偶爾出現(xiàn)故障,但軟件能夠進行的檢查有限。在構(gòu)建代碼時生成的校驗和會很有用??梢栽趩訒r檢查,也可以在運行時檢查。
內(nèi)存尋址邏輯故障會影響 ROM 和 RAM??梢栽O(shè)計專門針對此問題的測試,但最有可能在上述其他測試中出現(xiàn)。
外圍設(shè)備檢查
除了 CPU 之外,外圍電路也有可能出現(xiàn)故障。當然,這種情況在不同系統(tǒng)之間存在很大差異,但設(shè)備通常都有一些方法可以通過診斷軟件來驗證其完整性。例如,通信線路可能具有環(huán)回模式,任何寫入該線路的數(shù)據(jù)都會立即返回。
看門狗服務(wù)
嵌入式系統(tǒng)設(shè)計人員通常會使用“看門狗”電路。這是一種外圍設(shè)備,它要么中斷 CPU 并期望得到及時響應(yīng),要么(更確切地說)需要軟件發(fā)起定期訪問。無論是哪種情況,看門狗“咬人”的常見結(jié)果是系統(tǒng)重置。
在多任務(wù)環(huán)境中有效使用看門狗是一項挑戰(zhàn)。僅通過一項任務(wù)對其進行維護只能確認該特定任務(wù)正在運行。解決此問題的一種方法是實施“監(jiān)控任務(wù)”——本文后面將介紹此示例。
堆棧溢出檢查
除非您選擇“運行至完成”調(diào)度程序,否則 Nucleus SE 應(yīng)用程序?qū)槊總€任務(wù)包含一個堆棧。這些堆棧的完整性至關(guān)重要,但 RAM 可能有限,因此獲得最佳大小至關(guān)重要。靜態(tài)預(yù)測每個任務(wù)的堆棧要求是可能的,但非常困難——它需要有足夠的容量來滿足最嵌套的函數(shù)調(diào)用和最耗費堆棧的中斷服務(wù)例程的需求。一種更簡單的方法是執(zhí)行詳盡的運行時測試。
堆棧驗證大致有兩種方法。如果使用復雜的嵌入式軟件調(diào)試器,則可以監(jiān)視堆棧邊界并檢測任何違規(guī)行為。Nucleus SE 堆棧的位置和大小可通過 ROM 中的全局數(shù)據(jù)結(jié)構(gòu)輕松訪問:NUSE_Task_Stack_Base[] 和NUSE_Task_Stack_Size[]。
另一種方法是執(zhí)行運行時測試。通常的方法是在每個堆棧的末尾添加“保護字”——通常這些字是每個堆棧數(shù)據(jù)區(qū)域中的第一個位置。這些字被初始化為可識別的非零值。然后,診斷例程/任務(wù)檢查它們是否已更改并采取適當?shù)拇胧?。覆蓋保護字并不意味著堆棧實際上已溢出,而是表明它即將溢出;因此,軟件可能會繼續(xù)執(zhí)行足夠長的時間以采取糾正措施或警告用戶。
主管任務(wù)
盡管 Nucleus SE 不會將這 16 個可能的任務(wù)中的任何一個保留給自己使用,但用戶可以選擇將其中一個任務(wù)專門用于診斷。這可能是一個低優(yōu)先級任務(wù),它只利用任何“空閑”的 CPU 時間;也可能是一個高優(yōu)先級任務(wù),它偶爾會運行一小段時間,從而確保始終定期執(zhí)行診斷。
以下是示例的運行方式:
主管任務(wù)的信號標志用于監(jiān)視系統(tǒng)中六個關(guān)鍵任務(wù)的運行。每個任務(wù)都使用一個特定的標志(位 0 到位 5),并且需要定期設(shè)置它。主管任務(wù)清除所有標志,然后暫停一段時間。當它恢復時,它希望所有六個任務(wù)都通過設(shè)置適當?shù)臉酥緛怼昂灥健?它尋求與值b00111111 (來自nuse_binary.h)的精確匹配。如果滿足這一點,它將清除標志并再次暫停。如果不滿足,它將調(diào)用關(guān)鍵錯誤處理例程,例如,該例程可能執(zhí)行系統(tǒng)重置。
另一種實現(xiàn)方式是使用事件標志組。如果信號未在應(yīng)用程序中的其他地方使用(因為所有任務(wù)都會產(chǎn)生 RAM 開銷),則這種方法是有意義的,如果事件標志用于其他目的,則這種方法更有意義。
跟蹤和分析
盡管許多現(xiàn)代嵌入式軟件調(diào)試器都具有很高的可定制性,并且可以“感知 RTOS”,但調(diào)試多線程應(yīng)用程序仍然具有挑戰(zhàn)性。一種廣泛使用的方法是執(zhí)行后分析,其中對 (RTOS) 代碼進行檢測,以便可以回顧性地分析其操作的詳細審計。通常,實現(xiàn)這樣的功能涉及兩個組件:
1.
向 RTOS 添加附加代碼(即“儀表化”)以記錄活動。通常,這將包含在預(yù)處理器指令中以方便條件編譯。當發(fā)生重大事件(如 API 調(diào)用或上下文切換)時,此代碼會存儲幾個字節(jié)的信息。這些信息包括以下內(nèi)容:
2.
– 當前地址(PC)。
– 當前任務(wù) ID(索引)。
– 所涉及的任何其他對象的索引。
– 指示已執(zhí)行操作的代碼。
3.
專門用于將配置文件信息緩沖區(qū)清空到外部存儲器(通常是主機)的任務(wù)。
4.
這些捕獲數(shù)據(jù)的分析也需要一些工作,但這可能就像使用 Excel 電子表格一樣簡單。