馮 晗,夏璐怡,樊玲玲,裴文良
(1.中國科學(xué)院微小衛(wèi)星創(chuàng)新研究院,上海 201210;2.復(fù)旦大學(xué)軟件學(xué)院,上海 201203)
衛(wèi)星嵌入式系統(tǒng)及其他高實時嵌入式電子學(xué)系統(tǒng)的運行環(huán)境,決定了其極高的維護難度,因此,在地面測試過程中對故障進行充分的數(shù)據(jù)記錄、問題診斷并采取相應(yīng)措施,提高系統(tǒng)的可靠性及安全性,具有重要意義[1-4]。
目前,星載電子學(xué)系統(tǒng)平臺中主控CPU以SPARC體系結(jié)構(gòu)較為常見,其芯片有歐空局的TSC695F和AT697F。相應(yīng)操作系統(tǒng)有VxWorks和RTEMS,其中VxWorks操作系統(tǒng)具有一定的應(yīng)用基礎(chǔ)[5-7]。
通常嵌入式平臺的軟件內(nèi)存布局劃分為代碼段、數(shù)據(jù)段和BSS 段,在VxWorks操作系統(tǒng)中,用戶代碼可以獨立于操作系統(tǒng)內(nèi)核,也可和操作系統(tǒng)鏈接為一個整體。作為不區(qū)分內(nèi)核態(tài)和用戶態(tài)的操作系統(tǒng),系統(tǒng)啟動完成后和用戶進程共享進程棧,沒有單獨的內(nèi)核棧。同時,由于VxWorks操作系統(tǒng)沒有區(qū)分內(nèi)核空間以及用戶空間,因此應(yīng)用程序可以訪問系統(tǒng)內(nèi)核的變量。當代碼執(zhí)行觸發(fā)異常時,VxWorks 異常處理函數(shù)代碼的運行棧為當前進程?;蛘咧袛鄺?。當進入異常處理函數(shù)時,首先會在棧首部保存當前的上下文信息,該信息可以提供觸發(fā)異常問題的診斷信息。堆在VxWorks 中稱為動態(tài)內(nèi)存池,用于動態(tài)內(nèi)存的分配、進程TCB 及進程??臻g的分配和信號量隊列等結(jié)構(gòu)的創(chuàng)建。VxWorks 為中斷上下文在內(nèi)存中分配了獨立的中斷棧,起始地址和大小初始化完成后,不再變動[8-10]。其整體內(nèi)存布局如圖1 所示。
圖1 內(nèi)存布局圖
在TSC695 體系結(jié)構(gòu)中區(qū)分中斷及異常。異常也可稱為同步中斷。VxWorks操作系統(tǒng)對兩者進行區(qū)別維護,有不同的入口機制和現(xiàn)場保護機制[11-16]。
其中,中斷亦稱外部中斷,操作系統(tǒng)為其分配獨立的中斷棧空間,單次中斷以及中斷嵌套,均使用該中斷棧。內(nèi)部中斷亦稱為異常,則嵌套使用當前進程的棧或者中斷棧,即如果異常發(fā)生時正在運行用戶進程,則使用當前進程的進程棧。如果中斷處理過程中有異常發(fā)生,則嵌套使用中斷棧。當發(fā)生中斷或者異常時,會在棧幀頭部保存現(xiàn)場,因此關(guān)鍵信息如發(fā)生異常前正在運行代碼的PC 指針等信息則會記錄在相應(yīng)的棧幀頭部,通過分析該棧幀頭部數(shù)據(jù)即可獲取異常上下文信息,即可用于后續(xù)故障診斷。VxWorks操作系統(tǒng)異常棧ESF 結(jié)構(gòu)主要信息包括當前窗口全部寄存器的值,上一窗口全部寄存器的值以及L1、L2的值(即PC 與NPC 值)。同時中斷保存的信息包括當前窗口的寄存器值、發(fā)生中斷的PC和NPC。中斷棧幀的結(jié)構(gòu)如表1 所示。
表1 中斷棧幀主要內(nèi)容結(jié)構(gòu)
當發(fā)生任務(wù)切換時,原先進程的運行上下文會進行保存,通常有兩種保存方式,進程棧保存和進程控制塊(TCB)保存。VxWorks的實現(xiàn)方式主要為TCB保存,即將進程當前的運行環(huán)境所需要的全部寄存器值等信息,放置在進程TCB 相應(yīng)的結(jié)構(gòu)體中[17-19]。VxWorks的TCB 主要成員結(jié)構(gòu)如表2 所示。
表2 TCB主要成員結(jié)構(gòu)
在VxWorks 內(nèi)核5.4 版本中,進程創(chuàng)建taskspawn返回的指針值,即是進程的TCB的指針,通過該指針即可以訪問一個進程的TCB 中的相關(guān)信息。
SPARC 體系結(jié)構(gòu)中,異常亦稱同步中斷,主要包括非法指令、指令數(shù)據(jù)校驗多位錯、系統(tǒng)硬件錯誤、存儲器地址未對齊等。為判斷是哪個異常,并查找出異常信息,需要及時診斷相應(yīng)異常棧中的上下文信息。
操作系統(tǒng)提供的異常入口函數(shù)執(zhí)行時,首先將相應(yīng)的異常上下文信息保存在異常棧中,同時將棧的地址指針sp 通過參數(shù)傳遞機制傳遞給操作系統(tǒng)的處理函數(shù),然后在操作系統(tǒng)函數(shù)excExcHandleHandle中將異常棧中的信息保存到該函數(shù)中的局部變量中,然后調(diào)用操作系統(tǒng)的函數(shù)指針變量_func_excBaseHook,調(diào)用用戶指定的處理函數(shù),將異常棧幀中的內(nèi)容傳遞給應(yīng)用層。該函數(shù)同時傳遞的參數(shù)有異常的向量號vec,則是通過對當前TBR 寄存器的值進行位與操作及移位操作得到。
通過異常棧幀保存的相關(guān)信息對應(yīng)的偽代碼如下:
同時,TSC695 系統(tǒng)寄存器ERRRSR、SYSFSR和FAILAR 保存了相關(guān)的錯誤信息,可以直接訪問并讀取。
VxWorks操作系統(tǒng)的內(nèi)核變量intCnt和taskIdCurrent 亦可以在充分考慮安全的情況下,直接由應(yīng)用代碼進行訪問,其中,intCnt 記錄了當前中斷嵌套層數(shù),taskIdCurrent 指向了當前進程的TCB,為WIND_TCB 結(jié)構(gòu)體類型指針。其中在TCB的0x40的偏移位置記錄了進程的優(yōu)先級,在無同等優(yōu)先級的應(yīng)用中,可以讀取優(yōu)先級來判斷是哪個進程中出現(xiàn)異常。有同優(yōu)先級輪轉(zhuǎn)算法的應(yīng)用中,可以讀取進程名字符串。
其中,TRAP_taskname 為進程名字符串的起始內(nèi)存地址,可以根據(jù)具體應(yīng)用,讀取相應(yīng)的進程名字符串,并判斷當前異常發(fā)生時所在的進程。
在VxWorks-for-SPARC 中,非屏蔽中斷,即NMI和普通外部中斷具有相同的入口函數(shù)和現(xiàn)場保存方式。如果中斷發(fā)生,則首先切換到系統(tǒng)預(yù)留的中斷棧幀,保存當前的中斷上下文信息,保存完畢后即可調(diào)用C 語言中斷服務(wù)函數(shù)。由于存放中斷棧幀的幀底寄存器g6的值沒有通過參數(shù)傳遞機制傳遞給C 函數(shù),對于相關(guān)信息的保存沒有異常處理場景方便。
所以在用戶處理函數(shù)中,需要采用合理安全的方法,讀取寄存器g6的值,并避免寄存器使用出現(xiàn)沖突,是讀取中斷棧的核心。讀取g6 寄存器后,便可以通過讀取棧幀結(jié)構(gòu)保存其他中斷現(xiàn)場信息。
內(nèi)聯(lián)匯編方法代碼如下:
其中,定義兩個局部變量,g6_value和p_g6_value,由于是局部變量,編譯器在用戶進程棧中分配內(nèi)存空間。內(nèi)嵌匯編中的雙百分號,其中一個為寄存器的原有操作符,另一為內(nèi)聯(lián)匯編語法。其最終的執(zhí)行效果為將寄存器g6的值通過指針的方式做過渡保存到局部變量g6_value 中。獲取g6的值后,可以相應(yīng)的保存中斷棧中的核心信息,如被中斷前的PC 值等上下文信息。
獲取相應(yīng)的PC 信息之后,也可以和異常信息保存方法一致,相應(yīng)的獲取NMI_taskid,即發(fā)生NMI 時的所處進程號,NMI 前是否發(fā)生中斷嵌套的計數(shù)intCnt,以及TSC695 異常信息寄存器ERRRSR,SYSFSR、FAILAR和異常信息保存具有完全相同的操作方法。向量號不需要保存因為是NMI,其向量號是固定的,不需要保存。
在基于嵌入式操作系統(tǒng)的應(yīng)用實現(xiàn)中,通常設(shè)計最高優(yōu)先級的監(jiān)管進程,該進程負責(zé)處理硬件看門狗,同時負責(zé)排查全部應(yīng)用進程的運行情況。同時在應(yīng)用進程的運行代碼中,加入計數(shù)清零標志,當以while 循環(huán)為主體的進程完全執(zhí)行一遍功能后,將計數(shù)清零。
當最高優(yōu)先級的監(jiān)管進程運行時,發(fā)現(xiàn)某進程長時間未將計數(shù)標志清零,可判斷該進程被阻塞(如等待無效信號量或者隊列)、異常掛起、或者出現(xiàn)進程代碼中觸發(fā)死循環(huán)。此時,高優(yōu)先級監(jiān)管進程搶占CPU 運行,將陷入死循環(huán)進程的運行上下文保存在該進程的TCB 中,通過讀取TCB的相關(guān)狀態(tài),診斷死循環(huán)進程的運行上下文。
由于此時taskIdCurrent 指向了監(jiān)管進程,故在創(chuàng)建所有應(yīng)用進程時,需要將全部進程的TCB 指針保存到數(shù)組等結(jié)構(gòu)中,并在監(jiān)管進程中,通過查表等方法對應(yīng)到該陷入死循環(huán)進程的TCB 指針,并將其轉(zhuǎn)換為UINT32 類型,即為TCB 結(jié)構(gòu)體的起始地址,假設(shè)為變量ErrorTaskId。則后續(xù)保存信息為:
該值為異常TCB 中的進程狀態(tài),其中WIND_SUSPEND(0x1)為進程被TaskSuspend 掛起,WIND_PEND(0x2)為等待信號量,WIND_DELAY(0x4)為任務(wù)延時,WIND_DEAD(0x8)為死進程。根據(jù)該值,可以判斷此進程的基本狀態(tài),是等待無效的信號量一直無法運行,還是被其他進程掛起或進程自身掛起。如果進程狀態(tài)為WIND_READY(0x0),則進程代碼本身可能出現(xiàn)死循環(huán),此時在監(jiān)管進程中進一步讀取PC和NPC:
其中,0X130是由進程結(jié)構(gòu)體內(nèi)WIND_TCB_REGS 與REG_SET_PC 相加得到,0X134是由WIND_TCB_REGS 與REG_SET_NPC 相加得到。得到PC和NPC的值后,可以定位導(dǎo)致進程死循環(huán)處的具體代碼所在范圍。
由于PC和NPC 只能確定一層函數(shù)名,當該函數(shù)為底層公共函數(shù)時,則不能具體定位問題代碼所在范圍,根據(jù)VxWorks的相關(guān)機理,當進程切換后,其窗口寄存器的值全部保存到內(nèi)存棧中,故可以通過棧幀回溯的方法,查找上級函數(shù)調(diào)用。棧幀回溯需要讀取SP、FP的值,故最后一級的函數(shù)調(diào)用中的值需要先行獲取。
這兩個數(shù)據(jù)即指明了最后一個棧幀的棧底和棧頂?shù)刂贰?/p>
接下來,如果whileloop_PC 確定的函數(shù)執(zhí)行了SAVE操作,則返回地址和輸入?yún)?shù)通過IN 寄存器獲取。
如果whileloop_PC 確定的函數(shù)未執(zhí)行SAVE,則返回地址和輸入?yún)?shù)通過OUT 寄存器獲取,代碼如下:
至此,當前棧幀中可利用分析診斷的信息全部保存完畢,開始棧幀回溯代碼,查找調(diào)用函數(shù)的再次返回地址和再上一層的FP。
Restore0 代表一級回溯,其再次返回地址通過上一個棧幀中的IN7 寄存器獲取,再上一層回溯的FP以及當前棧幀的SP,通過IN6 寄存器獲取。
至此,已經(jīng)查找到3 或4 個PC 指針,分別為whileloop_PC和RPC,Restore0_RPC(或RPC_ 當最后一個函數(shù)為葉子函數(shù)時,和父函數(shù)公用一個內(nèi)存中的棧幀,但是當前可視窗口的O7和I7其實分別保存了兩個函數(shù)對應(yīng)的返回地址,故這種情況有4層返回),基本上可以確定函數(shù)的3或4層調(diào)用關(guān)系,查找問題代碼。
通常情況下,除非進入很底層的驅(qū)動函數(shù),且底層函數(shù)調(diào)用層次數(shù)比較深,則通常都會查出具體問題所在的行數(shù)。但是保險起見,可以迭代查找,直到停止條件。
迭代方法:
停止條件如下:
1)可采用某次迭代PC 指針為零,則即可停止迭代。停止理由是此時的棧幀已經(jīng)為初始棧幀,初始棧幀僅僅是操作系統(tǒng)為進程主函數(shù)提供入口參數(shù),其沒有返回地址,設(shè)置地址為零。
2)根據(jù)初始棧幀,當某次讀取的FP 值是進程分配的棧頂減去112 字節(jié)初始棧幀的內(nèi)存空間大小時,即可以確定停止條件。此時已經(jīng)回溯到第二個棧幀,即真正的進程主入口函數(shù)的棧。
進程的棧頂可以通過如下代碼確定,其值保存在進程TCB的0X78的位置。
stack_vx_base=*(UINT32*)
(ErrorTaskId+0X78);
在3 種常見故障中,PC和NPC的獲取方法各不相同,TRAP 中是通過ESF 中的異常棧幀獲取,NMI中是通過g6 指向的中斷棧幀獲取,死循環(huán)是通過TCB的方法獲取。
不管哪種方法,獲取的PC均可以提供故障前的現(xiàn)場代碼上下文信息??梢猿醪蕉ㄎ粏栴}的大體范圍。
死循環(huán)問題現(xiàn)場信息上下文由于進程進行切換,其窗口寄存器的內(nèi)容全部保存到內(nèi)存,故可以棧幀回溯。發(fā)生NMI 時,棧幀進行了切換,從原先的進程棧切換到中斷棧,且原先進程棧的信息可能仍在窗口寄存器中,沒有保存到內(nèi)存。同理,TRAP的棧幀回溯也不可以直接讀取內(nèi)存中的棧信息。
上面NMI 以及發(fā)生TRAP的情形時,如果需要讀取棧幀回溯信息,需要在匯編中通過多次執(zhí)行save 指令,讓窗口寄存器中的內(nèi)容保存到內(nèi)存,該方法有一定的風(fēng)險。
在這些方法中,3 種故障全部可以讀取系統(tǒng)寄存器SYSFSR、ERRRSR、FAILAR的值,其值主要給出了在trap的情況的下的相關(guān)信息。但是值的分析一般在trap 中利用信息比較大,其他情況可以結(jié)合具體分析提供輔助信息。
3 種情況中,均記錄了intCnt、taskId_Current等信息,intCnt 為操作系統(tǒng)的內(nèi)部變量,記錄了內(nèi)核嵌套層數(shù)(中斷退出代碼中的部分代碼可以被再次嵌套),taskId_Current 記錄了活動的進程或被中斷打斷前的進程,用于分析中斷可能的故障和定位具體進程。
文中對常見的系統(tǒng)故障進行了概括與分析,并針對相應(yīng)的系統(tǒng)異常、NMI 中斷和進程死循環(huán),提出了記錄相應(yīng)現(xiàn)場信息的方法,以及對應(yīng)的分析方法,其可以提供問題出現(xiàn)時的故障現(xiàn)場信息記錄以及診斷信息。
該方法已經(jīng)在量子科學(xué)實驗衛(wèi)星及遙感觀測衛(wèi)星上被成功應(yīng)用,并成功定位了多個代碼問題,為后續(xù)代碼的可靠性貢獻力量。