在嵌入式系統移植中重要的一部分是操作系統的移植,與其它操作系統相比,Linux大的特點:它是一款遵循GPL的操作系統,我們可以自由地使用、修改、和擴展它。正是由于這一特色,嵌入式系統移植過程中Linux系統受到越來越多人士的青睞。于是,一個經常會被探討的問題出現了,即關于Linux系統的移植。對于操作系統而言,這種移植通常是跨平臺的、與硬件相關的,即硬件系統結構、甚至CPU不同。下面就讓我們來看看在嵌入式Linux系統移植方面,我們都需要做些什么。
一、Linux系統移植的兩大部分
對于系統移植而言,Linux系統實際上由兩個比較獨立的部分組成,即內核部分和系統部分。通常啟動一個Linux系統的過程是這樣的:一個不隸屬于任何操作系統的加載程序將Linux部分內核調入內存,并將控制權交給內存中Linux內核的第一行代碼。加載程序的工作就完了,此后Linux要將自己的剩余部分全部加載到內存(如果有的話,視硬件平臺的不同而不同),初始化所有的設備,在內存中建立好所需的數據結構(有關進程、設備、內存等)。到此為止Linux內核的工作告一段落,內核已經控制了所有硬件設備。至于操作和使用這些硬件設備,則輪到系統部分上場了。內核加載根設備并啟動init守護進程,init守護進程會根據配置文件加載文件系統、配置網絡、服務進程、終端等。一旦終端初始化完畢,我們就會看到系統的歡迎界面了。小結一下:
(1)內核部分初始化和控制所有硬件設備(嚴格說不是所有,而是絕大部分),為內存管理、進程管理、設備讀寫等工作做好一切準備。
(2)系統部分加載必需的設備,配置各種環境以便用戶可以使用整個系統。
二、系統移植所必需的環境
在進一步敘述之前,我們有必要提一下做系統移植所必需的環境。
首先,需要一個新版本的gcc。對于一個準備系統移植的程序員而言,“新”到什么程度應該心里有數。做跨平臺編譯,gcc也許是好的選擇。另外,Linux內核依賴許多gcc特有的特性,非它不可。如果你已經會使用gcc并實地操練過多回,那你只需要再進一步鞏固一下跨平臺編譯的操作即可。兩種編譯環境是可用的:非目標平臺上的Linux或目標平臺上的非Linux系統,除非你的開發平臺過于特殊,否則你一定能夠找到你能用的gcc。
其次,編譯鏈接庫是必需的,而且必須是目標平臺的編譯鏈接庫。通常這是一個枯燥、繁瑣、又絲毫沒有成就感的過程。幸運的話,會有現成的鏈接庫可以用。否則,你需要自己用gcc建立它。
后,需要目標平臺的所有文檔,越多越好。如果有一定的開發支持/仿真環境,Loader(加載程序)則好,這些可以幫助你減少移植過程中浪費在瑣事上的時間。
三、Linux系統移植
接下來我們從內核和系統兩個方面描述一下移植中的關鍵。
(1) 內核移植
Linux系統采用了相對來說并不是很靈活的單一內核機制,但這絲毫沒有影響Linux系統的平臺無關性和可擴展性。Linux使用了兩種途徑分別解決這些問題,很干凈利落,絲毫不拖泥帶水,而且十分清晰易懂。分離硬件相關代碼和硬件無關代碼,使上層代碼永遠不必關心低層換用了什么代碼,如何完成了操作。不論對x86上還是在Alpha平臺上分配一塊內存,對上層代碼而言沒什么不同。硬件相關部分的代碼不多,占總代碼量的很少一部分。所以對更換硬件平臺來說,沒有什么真正的負擔。另一方面,Linux使用內核機制很好地解決了擴展的問題,一堆代碼可以在需要的時候輕松地加載或卸下,象隨身聽,需要的時候帶上,不需要時則鎖在抽屜里。
Linux內核可以視為由五個功能部分組成:進程管理(包括調度和通信)、內存管理、設備管理、虛擬文件系統、網絡。它們之間有著復雜的調用關系,但幸運的是,在移植中不會觸及到太多,因為Linux內核良好的分層結構將硬件相關的代碼獨立出來。何謂硬件相關,何謂無關?以進程管理為例,對進程的時間片輪轉調度算法在所有平臺的Linux中都是一樣的,它是與平臺無關的;而用來在進程中切換的實現在不同的CPU上是不同的,因此需要針對該平臺編寫代碼,這就是平臺相關的。上面所講的五個部分的順序不是隨便排的,從前到后分別代表著它們與硬件設備的相關程度。越靠前越高,后面的兩個虛擬文件系統和網絡則幾乎與平臺無關,它們由設備管理中所支持的驅動程序提供底層支持。因此,在做系統移植的時候,需要改動的就是進程管理、內存管理和設備管理中被獨立出來的那部分即硬件相關部分的代碼。在Linux代碼樹下,這部分代碼全部在arch目錄下。
如果你的目標平臺已經被Linux核心所支持的話,那么你是幸運的,因為已經沒有太多的工作讓你去做。只要你的交叉編譯環境是正確的,你只需要簡單的配置、編譯就可以得到目標代碼。否則,需要你去編寫,或修改一些代碼。只需修改平臺相關部分的代碼即可。但需要對目標平臺,主要是對CPU的透徹理解。在Linux的代碼樹下,可以看到,這部分的典型代碼量為:2萬行左右C代碼和2千行左右的匯編(C代碼中通常包含許多偽匯編指令,因此實際上純C代碼要少很多),這部分工作量是不可小看的。它包含了對絕大多數硬件的底層操作,涉及IRQ、內存頁表、快表、浮點處理、時鐘、多處理器同步等問題,頻繁的端口編程意味著需要你將目標平臺的文檔用C語言重寫一遍。這就是為什么說目標平臺的文檔極其重要。
代碼量大的部分是被核心直接調用的底層支持部分,這部分代碼在arch/xxx/kernel下(xxx是平臺名稱)。這些代碼重寫了內核所需調用的所有函數。因為接口函數是固定的,所以這里更象是為硬件平臺編寫API。不同的系統平臺,主要有以下幾方面的不同:
* 進程管理底層代碼:從硬件系統的角度來看,進程管理就是CPU的管理。在不同的硬件平臺上,這有很大的不同。CPU中用的寄存器結構不同,上下文切換的方式、現場的保存和恢復、棧的處理都不同,這些內容主要由CPU開發手冊所描述。通常來說,CPU的所有功能和狀態對于Linux不一定有意義。實現時,需要在小的開發代價和好的系統性能之間加以權衡。
* BIOS接口代碼:這一名稱似乎并不太準確,因為它沿用了PC一貫的叫法。但在不致引起混淆的情況下我們還是這么叫它。在通用平臺上,通常有基本輸入輸出系統供操作系統使用,在PC上是BIOS,在SPARC上是PROM,在很多非通用系統上甚至并沒有這樣的東西。多數情況下,Linux不依賴基本輸入輸出系統,但在某些系統里,Linux需要通過基本輸入輸出系統中得到重要的設備參數。移植中,這部分代碼通常需要完全改寫。
* 時鐘、中斷等板上設備支持代碼:即使在同一種CPU的平臺上,也會存在不同的板上外設,異種CPU平臺上更是如此。不同的系統組態需要不同的初始化代碼。很典型的例子就是MIPS平臺,看看arc/mips/的代碼,與其它系統比較一下就知道。因為MIPS平臺被OEM得廣,在嵌入式領域應用多(相對其它幾種CPU而言)。甚至同一種MIPS芯片被不同廠家封裝再配上不同的芯片組。因此要為這些不同的MIPS平臺分別編寫不同的代碼。
* 特殊結構代碼:如多處理器支持等。其實每一種CPU都是十分特殊的,熟悉x86平臺的人都知道x86系列CPU著名的實模式與虛模式的區別,而在SPARC平臺上根本就沒有這個概念。這就導致了很大的不同:PC機上的Linux在獲得控制權后不久就開始切換到虛模式,SPARC機器上則沒有這段代碼。又如電源管理的支持更是多種多樣,不同的CPU有著不同的實現方式(特殊的電源管理方式甚至被廠商標榜)。在這種情況下,除非放棄對電源管理的支持,否則必須重寫代碼。
還有一部分代碼量不多,但不能忽視的部分是在arch/xxx/mm/下的內存管理部分。所有與平臺相關的內存管理代碼全部在這里。這部分代碼完成內存的初始化和各種與內存管理相關的數據結構的建立。Linux使用了基于頁式管理的虛擬存儲技術,而CPU發展的趨勢是:為了提高性能,實現內存管理的功能單元統統被集成到CPU中。因此內存管理成為一個與CPU十分相關的工作。同時內存管理的效率也是影響系統性能的因素之一。內存可以說是計算機系統中頻繁訪問的設備,如果每次內存訪問時多占用一個時鐘周期,那就有可能將系統性能降低到不可忍受。在Linux系統里,不同平臺上的內存管理代碼的差異程度是令人吃驚的,可以說是差異大的。不同的CPU有不同的內存管理方式,同一種CPU還會有不同的內存管理模式。Linux是從32位硬件平臺上發展起來的操作系統,但是現在已經有數種64位平臺出現。在64位平臺上,可用內存范圍增大到原來的232倍,其間差異可略窺一斑了。鑒于這部分代碼的重要性和復雜性,移植工作在這里變得相當謹慎。有些平臺上甚至只是用保守的內存管理模式。如在SPARC平臺上的頁面大小可以是多種尺寸,為了簡單和可靠起見,SPARC版的Linux只是用了8K頁面這一種模式。這一狀況直到2.4版才得以改善。
除了上面所講的之外,還有一些代碼需要考慮,但相對來說次要一些。如浮點運算的支持。較完美的做法是對FPU編程,由硬件完成浮點運算。但在某些時候,浮點并不重要,甚至CPU根本就不支持浮點。這時候就可以根據需求來取舍。
對于內核移植的討論到此為止。實際上,還有一些移植工作需要同時考慮,但很難說這是屬于內核范疇還是屬于驅動程序范疇,比如說顯示設備的支持,和內核十分相關,但在邏輯上又不屬于內核,并且在移植上也更像是驅動程序的開發。因此不在這里討論。
(2)系統移植
當內核移植完畢后,可以說所有的移植工作就已經完成大半了。就是說,當內核在交叉編譯成功后,加載到目標平臺上正常啟動,并出現類似VFS: Can't mount root file system的提示時,則表示可以開始系統移植方面的工作了。系統移植實際上是一個小系統的重建過程。許多Linux愛好者有過建立Linux系統應急盤的經驗,與其不同的是,你需要使用目標平臺上的二進制代碼生成這個小系統。包括:init、libc庫、驅動模塊、必需的應用程序和系統配置腳本。一旦這些工作完成,移植工作就進入聯調階段了。
一個比較容易的系統部分移植辦法是:先著手建立開發平臺上的小系統,保證這套小系統在開發平臺上正確運行。這樣可以避免由于小系統本身的邏輯錯誤而帶來的麻煩。由于小系統中是多個應用程序相互配合工作,有時出現的問題不在代碼本身而在系統的邏輯結構上。
Linux系統移植工作至少要包括上述的內容,除此之外,有一些看不見的開發工作也是不可忽視的,如某個特殊設備的驅動程序,為調試內核而做的遠程調試工作等。另外,同樣的一次移植工作,顯然符合小功能集的移植和完美移植是不一樣的;向16位移植和向64位移植也是不一樣的。
在移植中通常會遇見的問題是試運行時鎖死或崩潰,在系統部分移植時要好辦些,因為可以容易地定位錯誤根源,而在核心移植時確實很讓人頭疼。雖然可以通過串口對運行著的內核進行調試,但是在多任務情況下,有很多現象是不可重現的。又如,在初始化的開始,很多設備還沒法確定狀態,甚至串口還沒有初始化。對于這種情況沒有什么很好的解決辦法,好的開發/仿真平臺很重要,另外要多增加反映系統運行狀態的調試代碼;再者要吃透硬件平臺的文檔。硬件平臺廠商的專業支持也是很重要的。
還有一點很重要:Linux本身是基于GPL的操作系統,移植時,可以充分發揮GPL的優勢,讓更多的愛好者參與進來,向共同的目標前進。