***********************************************
一般情況下,為設計中的IC開發SW方案,難免會碰到Bootloader/EBoot/OS啟動失敗的情況,對於Bootloader和EBoot,由於原始碼很少,直接使用Trace32調試是最佳方法,對於OS,最佳的方法當然是MAC KITL。
對於直接拿著廠商sw方案做項目的哥們來說,看過下面網上一位大哥寫的文章後,偶爾碰到啟動有問題,不用kitl jtag啥的,說不定也能把問題給搞定。
****************************************************************************
本文通過一個真實的嵌入式項目進行說明。文中的嵌入式系統用的是ARM處理器+WinCE平台,項目的目的是要把WinCE平台從舊版本移植到WinCE6.0平台上。但結果是這個WinCE系統在啟動的時候經常會出現失敗,而且每次失敗的原因都莫明其妙和不盡相同。這使到我們Team Dev每個人在啟動WinCE系統時都心驚肉跳,非常擔心系統又再一次出現讓人意想不到的失敗。這種頻繁的啟動失敗對Team Dev來說顯然是一種讓人難以忍受的折磨。
為什麼會出現這種情況呢?經過幾個晚上通宵達旦的加班分析和研究,原來主因是系統的引導過程、核心載入過程、OAL啟動過程和硬體驅動載入過程時都存在可能導致的失敗的隱憂。本文通過對以上因素進行分析,並提出相應的解決辦法。但由於WinCE啟動失敗會非常取決於硬體平台,因此在具體應用時需要綜合考慮和分析。
一.什麼是WinCE啟動過程?
WinCE系統在啟動時一般需要三個基本元素:引導初始化、核心載入和OAL初始化等。它們的作用是要完成引導過程的初始化和作業系統執行環境的初始化。其中引導初始化是由引導工具BootLoader完成,主要是完成板級、片級的初始化。例如,通過設定寄存器來完成硬體的初始化,如設定時鐘、設定中斷控制寄存器、完成記憶體映射和初始化MMU的工作方式等。核心載入是指將作業系統核心映像從唯讀記憶體載入或者拷貝到系統的RAM中並執行。OAL(OEM Adaption Layer,即原始裝置製造商適配層)是位於作業系統的核心與硬體之間的適配層,也是串連系統核心與硬體的樞紐,它具有屏蔽硬體裝置細節以及抽象硬體功能的作用。而OAL初始化則是指通過一組函數來體現出0AL屏蔽和抽象硬體裝置的作用。
此外,如果要WinCE系統成為完整的作業系統,還得加上硬體驅動程式、硬體介面程式和應用程式組。因此,即使在一個簡單的嵌入式系統裡,WinCE系統啟動時是需要載入核心和載入許多組件或驅動程式。
現在讓我們來看看WinCE系統在啟動時調用函數的順序:①CPU執行引導向量,跳轉到硬體初始化代碼,即Startup函數。②在start up函數完成最小硬體環境初始化後跳轉到KernelStart函數,來對核心進行初始化。③Kernelstart函數調用OEMInitDebugSerial完成對調試串口的初始化;同時調用0EMInit函數來完成硬體初始化工作以及設定時鐘、中斷;最後,調用OEMGetExtensionDRAM函數來判斷是否還有另外一塊DRAM。至此,核心載入完畢。由此可見,WinCE系統啟動的重中之重是Startup函數的正確載入,如果這個Startup函數調用失敗,則會使到系統在啟動頻繁出錯。WinCE啟動時調用函數順序如所示:
因此,WinCE啟動失敗可能會存在於引導初始化失敗、核心載入失敗、0AL函數初始化失敗、驅動程式載入失敗、組件載入失敗和應用程式載入失敗。也就是說,WinCE啟動失敗一方面可能是在Startup函數的處理上,例如引導初始化和OAL初始化。另一方面還存在於驅動程式和組件自啟動的失敗上,例如基本的驅動程式、註冊表配置或自啟動並執行程式等。
就不能被使用。所以,當註冊表在啟動時載入錯誤或者註冊表配置有錯誤時,也是會導致WinCE系統啟動失敗的。
二.導致WinCE啟動失敗的主因分析
Windows CE在啟動時為什麼會失敗呢?這個問題也一直讓我頭痛。因為Windows CE啟動失敗既有軟體因素,也有硬體因素。例如,可能是WinCE的啟動引導過程有問題、也許是核心載入時有問題、也許是OAL函數調用的隱性問題或者硬體裝置本身的問題造成的。所以,解決起來比較麻煩和比較耗時間,也是最讓我們頭疼的事情。
一般來說,解決和分析WinCE啟動失敗有一個原則,就是"先軟後硬"的原則,也就是說要先分析軟體因素再到硬體因素。本文主要是在ARM微處理器和Windows CE 6.0平台上進行分析軟體因素造成的失敗。
(1)引導程式BootLoader導致的失敗
在Windows CE系統中,整個系統的載入啟動任務由BootLoader來完成,BootLoader是在WinCE核心運行之前啟動並執行一段小程式。通過這段小程式,可以初始化硬體裝置、建立記憶體空間的映射圖和初始化MMU等。從而將系統的軟硬體環境帶到一個合適的狀態,為叫用作業系統核心準備好環境。因此,只有在引導程式正確的完成自己的任務後,才會將控制權移交給核心。
在WinCE平台上,引導裝載程式是在硬體上執行的第一段代碼,通常將引導程式放置在不易丟失的儲存空間的開始地址或者是系統冷啟動時PC寄存器的初始值。如果這段小程式代碼編寫錯誤,則系統無法完成第一步的引導操作,這是導致啟動系統失敗的第一個因素。
①BootLoader初始化硬體失敗
BootLoader第一個功能是要實現板級和片級初始化硬體,主要是把CPU初始化到一已知狀態。在BootLoader目錄下,會發現一些.s檔案,可能會是init.s或者是reset.s等,這樣的檔案是CPU加電後最先執行的代碼。StartUp 函數是BootLoader的入口函數。該函數一般是使用組合語言編寫,與CPU關係非常緊密,能完成初始化CPU、記憶體等核心硬體。然後,BootLoader在平台初始化完畢後就可以在不用人工幹預的情況下自動載入WinCE核心了。但如果BootLoader在初始化硬體時失敗,就會直接導致系統的啟動失敗了。
②BootLoader載入核心時失敗
一般在平台調試完畢後,BootLoader就會載入WinCE核心映像,這也是BootLoader的功能之一。WinCE核心映像檔案通常叫做nk.bin,它是Windows CE位元據格式檔案,不僅包含了有效程式碼,還有按照一定規則加入的控制資訊。
在系統啟動時BootLoader可以通過兩種不同的方式來載入WinCE核心檔案nk.bin。一種是下載模式,另一種是本地啟動模式。本地啟動模式也稱為自主模式,即 BootLoader 從目標機上的某個固態存放裝置上將作業系統載入到 RAM 中運行,整個過程並沒有使用者的介入。而下載模式則是目標機上的 BootLoader 將通過串口串連或網路連接等通訊手段從主機(Host)下載檔案。當BootLoader正確的把nk.bin解壓到RAM後,就會把CPU控制權交給CE核心。因此,如果Boot Loader處理不當,就可能會造成載入和解壓nk.bin檔案的失敗,這樣自然也就會造成系統啟動的失敗了。
(2)OAL導致的啟動失敗
OAL(OEM Adaptation Layer)是指OEM 適配層,它是位於Windows CE核心和硬體之間的一層適配層,是OAL各個模組代碼被編譯後(.lib)和其它核心庫連結到一起形成Windows CE的核心可執行文檔NK.EXE。OAL包括了和系統硬體通訊的最底層代碼,核心是通過OAL跟硬體進行互動。邏輯上,OAL是介於CE核心和裝置硬體之間的一個代碼層,是一個抽象的概念。物理上,OAL和其它一些庫一起連結成可執行檔。
與以前的Win CE舊版本不同的是,在Win CE 6.0中核心(Kenerl)和OEM代碼被分成oal.exe、kernel.dll和kitl.dll三個部分,其中啟動代碼(startup)和 OAL層的實現部分不再與核心連結產生NK.exe,取而代之的是啟動代碼(startup)和硬體相關且獨立於核心的OAL層的實現部分編譯成 oal.exe;而與核心相關且獨立於硬體的OAL層程式碼封裝含在kernel.dll中,核心無關傳輸層(KITL)的支援代碼從OAL層分離出來編譯成 kitl.dll。因此,WinCE6.0的啟動只與oal.exe和kernel.dll有關。至於kitl.dll,只有將作業系統編譯成具有 KITL功能時才用到。這樣做的好處是可以單獨升級OAL,但整體的OAL結構並沒有改變。
①OAL初始化硬體時失敗
oal.exe是通過Startup函數來完成硬體的初始化。一般來說,OAL的啟動代碼(Startup.s)與該硬體平台的Bootloader的啟動代碼(Startup.s)是可以共用的。例如,其中PreInit 函數主要完成將ARM處理器工作模式切換到管理員模式,同時關閉MMU,並檢測系統啟動原因。如果是暖開機,即在該函數調用之前已經啟動過 Bootloader的啟動代碼(Startup.s),相當基本硬體初始化已經完成,則可直接跳轉到OALStartUp函數中;否則需要進行硬體中斷屏蔽、記憶體、系統時鐘頻率、電源管理等硬體的基本初始化過程。
在StartUp 函數初始化CPU等核心硬體並跳轉到Main函數後,系統就會轉入C語言代碼執行環境。這時Main函數分為3個模組:BLCOMMON、Download Function、FLASH Function。其中BLCOMMON模組是由微軟提供的,執行一些邏輯上的功能。而Download Function、FLASH Function中的函數與硬體平台息息相關。因此,對於每種硬體平台都要將函數的實現進行適當修改,這種修改是需要對硬體非常熟悉的。當修改出現錯誤時,就會導致系統啟動失敗了。
在硬體平台初始化完成後,oal.exe的啟動任務基本完成,餘下的啟動工作由核心相關且獨立於核心的OAL層實現體kernel.dll接管。也就是說,這時Startup會調用OALStartUp函數,OALStartUp函數主要完成將OEMAddressTable表傳遞給核心,然後調用KernelStart函數跳轉到核心。因此,如果此時OAL的啟動Startup函數調用失敗的話,就也會導致系統的啟動失敗了。
這裡需要特別注意的是,Bootloader和OAL中均包含啟動Startup函數。它的功能大致相同,都是要初始化最小硬體環境。Bootloader的啟動Startup函數是在為自己的執行準備硬體環境,OAL的啟動Startup函數則是為kernel的執行準備硬體環境。由於這兩種硬體環境要求基本相同,所以它們的代碼也有很大部分可以相互借鑒。但應該明白Bootloader與OAL在物理上是獨立的,它們並不是同一段代碼。當然,如果可以確定這一部分在Bootloader已經初始化過如暖開機,則在OAL中不必重複執行。
②OAL入口位置定位失誤導致的失敗
從上述WinCE啟動流程可知,在OAL初始化硬體後而在核心啟動前,系統是需要調用KernelStart函數來跳轉到核心。因此,這裡有一個要點,就是WinCE需要找到OAL的入口位置,然後才能調用入口函數與全域塊進行指標交換,這樣核心才能使用OAL層中的資訊,同樣OAL層也才能訪問核心(kernel)匯出的函數。
OAL入口位置函數的調用實際上是通過OEMGLOBAL結構體實現的,實際調用位置為OEMInitDebugSerial和OEMInit。也就是說,OEMGLOBAL結構體構建了核心和OAL層之間進行通訊的橋樑。OEMGLOBAL結構體定義了OAL層所有必須的函數,該結構體在oemglobal.c檔案中被初始化,並會被編譯在OEMMain.lib和 OEMMain_StaticKITL.lib兩個庫中。如果OAL連結這兩個庫,則必須要有正確的該結構體的函數實現體,同時還需要調用ARMSetup來設定物理地址和非緩衝的虛擬記憶體地址的映像、ARM中斷向量以及核心模式所需要的堆棧、調用OEMInitDebugSerial函數初始化調試串口、調用OEMInit進行平台初始化等。否則,如果OAL入口位置函數有誤,則核心和OAL層之間的訪問就會失敗,也就會導致系統在啟動時出錯和失敗。
三.導致的WinCE啟動失敗的其它相關因素
(1)驅動程式載入錯誤導致的失敗
在調試中,我們還發現系統在啟動時執行到OEMInit時也經常會出現錯誤。一般來說,系統調用OEMInit運行完成之後,就會跳回Private或Public下的代碼繼續運行,然後再啟動device.exe載入各個驅動程式。由於這一段代碼是微軟提供的default代碼,基本上不會有問題。所以,我們就有理由懷疑如果載入的驅動程式出了問題,是也會造成系統啟動失敗的。一般來說,這些載入的驅動程式主要是 BSP中的Audio、Display、SDMMC、Serial、USB等。
(2)啟動時載入配置有誤的註冊表導致的失敗
在WinCE中註冊表在啟動過程中也扮演著非常重要的角色。與案頭Windows一樣,WinCE註冊表(Registry)也是一個系統資料庫,用來儲存應用程式、驅動程式、使用者的設定以及其它一些系統的配置資訊,通常還儲存著作業系統運作和調用程式的狀態資訊。例如,每個使用者的設定檔、安裝的應用程式以及每個應用程式可以建立的文件類型、檔案夾和應用程式圖示的屬性工作表設定、系統上存在哪些硬體以及正在使用哪些連接埠等。
因此,對於硬體外設來說註冊表是一個記錄驅動程式設定和位置的資料庫。當WinCE系統在啟動時需要啟動某些必要的硬體裝置時,就會需要使用外設驅動程式。但如果在WinCE中這個外設驅動是獨立於作業系統的,WinCE系統就需要知道從哪裡找到它們,例如檔案名稱、版本號碼、其它設定和資訊。因此,註冊表上沒有此裝置的記錄時,它們就不能被使用。所以,當註冊表在啟動時載入錯誤或者註冊表配置有錯誤時,也是會導致WinCE系統啟動失敗的。