摘要 Linux在消費電子類產品中得到了廣泛應用,由于嵌入式用戶對于系統啟動速度較為敏感,因此快速啟動技術逐漸成為研究和應用中的一個重點。本文通過對嵌入式 Linux的啟動時序和主要延時因素的分析,針對性地探討了在各個啟動階段降低時耗的技術,重點分析了XIP技術和XIP文件系統,并給出了主要的實現過程。
關鍵詞? Linux? 快速啟動? XIP? Prelink? CRAMFS? AXFS
嵌入式Linux系統主要特點在于使用Bootloader替代了桌面系統的BIOS,同時對系統進行了規模上的裁剪,但硬件上的劣勢往往導致系統啟動速度較慢,而嵌入式產品使用者又對系統的開機速度比較敏感,這樣就產生了對于提高嵌入式Linux系統啟動速度的需求。本文對系統啟動時執行哪些階段的操作,以及縮短這些操作時間的方法進行了探討。
1? 嵌入式Linux系統啟動時序
目前,嵌入式系統的硬件平臺和應用方向區別很大,但總體啟動流程是一致的。這里的系統啟動是指從用戶執行上電/復位操作,到系統開始提供用戶可接收的服務水平所需要的過程。典型的上電/復位時序如表1所列。
2? Linux快速啟動方法
目前,一些Linux的發行版本已經對啟動速度進行了優化。如果利用標準Linux進行開發,則啟動速度的提高主要是通過內核配置和各種補丁包來實現的。下面分析快速啟動的一些關鍵技術。
2.1? Firmware和Bootloader階段
目標板一旦確定,Firmware運行的時間就無法改變了,Flash和RAM的讀寫速度也就隨之確定了。但如果復位時能夠繞過Firmware和Bootloader,即允許運行中的內核加載以及運行另一個內核,可以縮短啟動的時間。典型的實現有Kexec,它有2個組件,即用戶空間組件 kexectools和內核補丁。另外一種辦法是在內核命令行中加入reboot=soft參數,同樣可以跳過Firmware,但是缺點在于無法從用戶空間調用。
對于正常啟動,可以選擇速度比較快的Bootloader,并對內核進行小型化處理;還可以使用高速的映像復制技術(如DMA2RAM),從而縮短復制的時間。為了縮短解壓消耗的時間,可尋求比較高效的壓縮算法。但一般情況下,壓縮比越高,算法越復雜,解壓速度就越慢,從而造成復制時間(與壓縮比成反比)和解壓時間(一般與壓縮比成正比)之間的矛盾。
2.2? 內核階段
內核初始化時要對RealTime Clock (RTC)進行同步。此過程要占用1 s的時間,可去掉以節約時間,但這樣CPU會與正確的時間有1 s的偏差,如果關機時CPU時鐘又要保存在RTC中,偏差就會不斷累積。但對于使用外部時鐘源進行同步的系統,則可安全地跳過這個階段。
Preset LPJ可以用來縮短每次啟動時調用calibrate_delay()來校準loops_per_jiffy消耗的時間。這個時間開銷與CPU頻率無關,在典型的嵌入式硬件環境下會消耗300 ms左右。LPJ值對于固定硬件平臺應該是一致的,可以只計算一次,在后續的啟動中就可以在啟動參數中強制指定LPJ值,而跳過實際的計算過程。具體方法是:在正常啟動后記錄下內核啟動信息中的“Calibrating Delay”數值,在啟動參數中以“lpj=xxxxxx”的形式強制指定。
啟動過程默認打開控制臺輸出啟動消息,但是控制臺尤其是基于幀緩沖的控制臺會減慢啟動速度。因此在嵌入式Linux產品中,將啟動過程中的控制臺設為靜默狀態,方法是在內核啟動參數中加入“quiet”。
設備搜索和驅動安裝是比較耗時的操作,因此要在編譯內核時確定需要安裝哪些驅動模塊,以免系統搜索那些根本不存在的設備,尤其是多余的IDE設備。對于啟動時暫時不用安裝的設備,盡量將驅動編譯成模塊,在以后空閑時或者使用設備時加載,而不是全部放在啟動階段。
2.3? 用戶空間階段
傳統Linux的初始化腳本是由bash執行的,在內核引導后啟動init進程(/sbin/init)。它使用一個ASCII文件(/etc/inittab)來改變運行級別,這個文件中又會調用RCSript,由RCSript查找/etc/rc.d/rc5.d/并啟動相應鏈接指向的系統服務。
消費電子類Linux系統需要啟用圖形界面等必要的服務,未經優化的系統在這個過程中會默認啟動很多根本用不到或者當前用不到的系統服務,這一部分會花去較大的時間開銷。最簡單的優化辦法就是根據實際需要,通過改寫服務配置文件定制系統服務。另外,init腳本的執行是串行的,在腳本量大時會導致引導過程非常長,因此可以考慮并行運行各種服務以加快啟動的速度。現在已經出現了一些初始化程序來替代init進程,下面介紹initng和upstart。
initng(init nextgerneration)能夠并行啟動服務從而快速完成初始化工作。initng認為滿足了依賴關系的服務就可以啟動。在從外存加載一個腳本或等待硬件設備啟動的同時,可以運行另一個腳本來啟動別的服務,使系統在 CPU 和 I/O 之間實現較好的平衡。作為一個基于依賴關系的解決方案,initng 使用自己的初始化腳本集,它們對服務和守護進程的依賴性進行了編碼。如果某個服務依賴(使用 need 關鍵字定義)于其他服務,則要保證啟動時它所依賴的所有服務均可用。無依賴關系的服務立即并行啟動,具有依賴關系的服務則要等待以安全啟動。
upstart與 initng的區別在于: upstart基于事件,任務/服務的啟動/停止都取決于它所等待的事件是否發生。 upstart對事件的定義非常靈活,分為3類:edge (simple) events, level (value) events和temporal events。使用start/stop、事件名以及它所期待的值(可選)組成條目對觸發事件進行描述。事件依賴有兩種辦法:一種是任務自身導致事件發生,不管任務何時啟動/結束都會有事件發生,對于啟動時要執行的基本任務,這種辦法比較有效;而對于較復雜的依賴關系,則可使用任務的Shell腳本工具。
2.4? 預讀取和預鏈接
預讀取(Readahead)可以將文件(程序和庫文件)在使用之前預先加載到RAM緩存中,這樣就不用在使用時為讀取這個文件而訪問I/O。如果知道下一步操作要訪問哪些文件,就可以提前將它們全部/部分讀取到緩沖區,從而加快執行速度。嵌入式系統很多場合下對于下一步操作都是可預測的,比如系統啟動時總是以同樣的順序訪問同樣的可執行/數據文件,文件塊的訪問往往是順序的,應用程序啟動時總是訪問同樣的程序文件段、共享庫、資源或者輸入文件。這樣使用預讀取有很強的針對性,從而提高程序執行速度。
ELF(Excutable and Linkable File)是目前Linux中的標準二進制格式,其啟動需要以下步驟:將共享庫映射到虛擬地址空間;解析符號引用;初始化每個ELF文件。由于共享庫是位置無關的,要在運行時完成部分重定位處理和符號查找的工作,才能跳到程序的入口點,因此在帶來靈活性的同時,也造成ELF文件的啟動速度緩慢,尤其是解析符號引用要消耗大量的時間,對于使用多個共享庫的大型程序更是如此。但在很多嵌入式系統中,可執行文件和共享庫極少變化,而且每次程序運行時鏈接工作完全相同。
預鏈接(Prelink)利用這一點,修改ELF共享庫和二進制文件,將鏈接信息加入到可執行文件中以簡化動態鏈接重定位,從而使程序啟動加快。預鏈接首先搜集要預鏈接的ELF二進制文件及其所依賴的共享庫,為每個庫分配唯一的虛擬空間位置,并將共享庫重新鏈接到這個基準位置(動態鏈接器要加載這個庫時,只要虛擬空間地址未被占用,它就會將庫映射到指定位置);然后預鏈接解析二進制或者庫中的所有重定位,并將重定位信息存放到ELF對象,還要將所有依賴庫的列表及校驗和添加到二進制文件或庫中。對于二進制文件,還需列出所有的沖突(在共享庫的自然搜索范圍內對符號的解析不相同)。在運行時,動態鏈接器先檢查是否所有依賴的庫都已經映射到指定的位置,而且庫文件沒有變化,只考慮沖突而不用處理每個庫的重定位,這樣大大提高了程序啟動的速度。使用時要注意的是,若共享庫發生了改變,則使用它的所有程序都要重新鏈接,否則程序仍要進行耗時的正常重定位。
3? XIP和文件系統優化
3.1? 代碼執行方式
嵌入式系統中代碼的執行方式主要有3種:
① ?完全映射(fully shadowed)。嵌入式系統程序運行時,將所有的代碼從非易失存儲器(Flash、ROM等)復制到RAM中運行。
② ?按需分頁(demand paging)。只復制部分代碼到RAM中。這種方法對RAM中的頁進行導入/導出管理,如果訪問位于虛存中但不在物理RAM中會產生頁錯誤,這時才將代碼和數據映射到RAM中。
③ ?eXecute In Place (XIP)。在系統啟動時,不將代碼復制到RAM,而是直接在非易失性存儲位置執行。RAM中只存放需要不斷變化的數據部分,如圖1所示。如果非易失性存儲器的讀取速度與RAM相近,則XIP可以節省復制和解壓的時間。NOR Flash和ROM的讀取速度比較快(約100 ns),適合XIP;而NAND Flash的讀操作是基于扇區的,速度相對很慢(μs級),因此不宜實現XIP。
XIP可以分為以下2種:
①? 內核XIP。直接在Flash/ROM中運行內核,可以節省復制和映像解壓的時間。Linux 2.6.10內核已經包含了XIP支持。
② ?應用程序XIP。直接從應用程序代碼的存儲位置執行,而不用將它加載到RAM中,這樣應用程序的第一次執行速度會比較快。要使用應用程序XIP,應該基于支持它的文件系統。
3.2? XIP文件系統
目前XIP文件系統的實現主要有2種: Linear XIP CRAMFS和Advanced XIP File System(AXFS)。
CRAMFS是一個壓縮的只讀文件系統,本來用于桌面Linux系統的啟動,但CRAMFS經過修改后可以支持嵌入式系統并支持XIP。Linear XIP CRAMFS用一個sticky bit對它管理的文件進行區分,標記為壓縮(按需分頁)或者未壓縮(XIP)。如果文件標記為XIP,則所有頁都不壓縮,而且要在Flash中連續存儲。在加載XIP文件時,直接對所有頁地址進行映射;而按需分頁的文件則在發生頁錯誤時,將相應頁解壓到RAM中。
要創建Linear XIP CRAMFS文件系統映像,必須確定可執行文件和庫文件的使用頻率,頻繁使用的文件適合于XIP,而其他文件應該進行壓縮。現在有一些工具(如RAMUST和CFSST)可以幫助判斷哪些文件需要XIP,而哪些不需要。下面就可以給XIP文件加上標記并制作根文件系統,以使用mkfs.cramfs工具為例:
chmod +t filenames
mkfs.cramfs–x rootfs rootfs.bin
另外,還要修改內核配置參數以支持XIP:在啟動選項中向默認內核命令字符串中加入rootfstype=cramfs,選擇內核XIP并設置XIP內核物理地址;在驅動程序中加入MTD對XIP的支持;在文件系統中加入對Linear XIP CRAMFS的支持。接下來就可以生成XIP映像了。
Linear XIP CRAMFS的一個缺陷在于它是基于文件的,即一個文件中的所有頁要么全部采用XIP,要么全部采用壓縮/按需分頁,但事實上同一文件中不同頁的使用頻率區別也很大。AXFS是Intel公司開發的一個新的只讀文件系統,它從Linear XIP CRAMFS中繼承了許多方法,同時也進行了一些改進。AXFS的XIP粒度是基于頁的,并且自帶工具來判斷哪些頁需要XIP,哪些頁需要壓縮,從而更好地在速度和RAM/Flash的使用上取得平衡。
3.3? 非XIP文件系統
XIP一般基于NOR Flash,成本相對較高。對于用戶數據量大的應用,往往還要使用基于NAND Flash的,非XIP的文件系統常用的有JFFS2/YAFFS。
JFFS2是一種基于壓縮的文件系統。在多媒體應用中,如果圖片、音視頻已經經過壓縮,則使用JFFS2無疑會給CPU帶來雙重的壓縮/解壓負擔,訪問速度也會受到影響。因此,在這類應用比較密集的應用中,采用不壓縮的文件系統(如YAFFS/YAFFS2)可以加快系統速度。
YAFFS/YAFFS2是專為嵌入式系統使用NAND Flash設計的日志文件系統。與JFFS2相比,減少了一些功能(例如不支持數據壓縮),所以速度更快,掛載時間很短,對內存的占用較小。YAFFS/YAFFS2自帶NAND芯片的驅動,用戶可以不使用MTD和VFS,直接對文件系統操作。YAFFS與YAFFS2的主要區別在于: 前者僅支持小頁(512字節) NAND Flash;后者則可支持大頁(2 KB) NAND Flash,同時在內存使用、垃圾回收、訪問速度等方面有所改進。
結語
快速啟動對于嵌入式Linux系統是比較迫切的要求之一。本文通過分析嵌入式系統的引導過程和關鍵時延因素,提出了相應的解決辦法,并對XIP文件系統進行了介紹。由于啟動速度非常依賴于硬件平臺,而且有的方法互相排斥,因此在具體應用時需要綜合考慮和選擇。
參考文獻
[1] ?Tim Bird R. Methods to Improve Bootup Time in Linux [R]. Proceedings of the Linux Symposium, Ottawa,2004.
[2] ?Karim Yaghmour. 構建嵌入式Linux系統[M]. 北京:中國電力出版社, 2004: 49-66.
[3] ?陳莉君. 深入分析Linux內核源代碼[M]. 北京:人民郵電出版社, 2001: 477-499.
[4] ?左大全,吳剛. 嵌入式Linux快速啟動與XIP應用[J]. 計算機工程與科學,2006(12).
周全成(碩士研究生),主要研究方向為嵌入式應用和通信技術;鄭延召(碩士研究生),主要研究方向為嵌入式系統。
(收稿日期:2007-10-08)
評論
查看更多