如何給Linux作業系統減肥上
作者:佚名 來源:網路整理 更新時間:2009-2-7分享到
減少Linux的系統佔用,為應用程式保留更多的資源,減少裝置的硬體成本。“你能做到多小?”這個問題通常在嵌入式工程師開始他們的項目時問到。大多數時候,問這個問題的人是想減少RAM和Flash資源,減少裝置的單位成本或能源需求。
因為Linux最初是為案頭或伺服器系統設計的,預設情況下,它沒有為尺寸大小進行過最佳化,無論如何,Linux正被越來越多地用在嵌入式裝置中,要讓Linux變小不是一件容易的事情,這裡有幾個減少系統佔用記憶體的方法。
許多工程師是從減少核心大小開始的,這裡有個容易著手的方法,本文將詳細介紹如何減少核心的大小,主要通過移除那些在一個典型的嵌入式系統中用不到的代碼。
在一個系統中根檔案系統(RFS)可能是記憶體資源最大的消耗者。根檔案系統包括了應用程式和c庫使用到的基礎結構代碼。為RFS選擇檔案系統對最後的大小有非常大的影響,標準是ext3,從一個嵌入式工程師的眼光來看它的效率是非常低的,但那是另一篇文章的主題了。
實際中,如何減小?
即使最小的Linux發行版至少也有兩部分:核心和根檔案系統。有時,這些組件駐紮在同一個檔案中,但是它們仍然分成不同的組件。從核心中移除特徵幾乎差不多的所有代碼,這樣一個系統很容易就減少到不到1MB大小。但是,許多使用者選擇Linux支援網路和不同裝置,因此這不是一個現實的做法。
核心
Linux核心非常有趣,儘管在編譯時間它依賴GCC,但運行時它卻不依賴。那些工程師將目光轉向Linux初始化RAM磁碟(所謂的initrd),它是核心運行時的附屬物。Initrd首先是由核心載入的,程式運行時詢問系統需要載入什麼模組以便支援裝置,這樣真實的根檔案系統才能被載入。實際上,有兩步載入過程,載入initrd後再載入真實的根檔案系統,很少發現嵌入式系統中有根檔案系統,因為這樣在一個系統中會增加靈活性,對這個系統做改動要花費額外的空間或時間,嵌入式系統一般不需要靈活性。但本文稍後將討論根檔案系統。
可載入模組支援
核心載入模組是重新置放運行時核心串連到它自身的代碼,典型的可載入模組的例子是允許從使用者空間載入驅動到核心中(某些探測進程執行後),以及不關閉系統升級裝置驅動。對於大多數嵌入式系統而言,一旦它們處於該範圍之外,修改根檔案系統要麼不現實要麼不可能。因此系統設計者直接將模組串連到核心中,移除那些可載入的模組,節約出來的空間對於核心來說是很多的,無論如何,程式管理可載入的模組(如insmod\rmmod\lsmod)和shell指令碼載入它們不是必需的。
Linux-tiny補丁
Linux-tiny補丁集已經變得時有時無的項目了,最初是由Matt Mackall在主持。消費電子Linux論壇(CLEF)正在努力恢複這個項目,CLEF開發人員WiKi已經給2.6.22.5核心(寫本文時的最新版本)發布了補丁,同時,Linux-tiny項目的許多改變已經包括在主線核心中,儘管許多原始的Linux-tiny補丁已經整合到核心中,但實質上節約空間的補丁還沒有整合進去。
如:
1、 Fine-grain printk support【細粒度printk支援】:使用者可以控制什麼檔案可以使用printk。工程師將從不使用printk的檔案大小中受益。
2、 Change CRC from calculation to use table lookup【改變CRC演算法從計算到使用表查詢】:乙太網路資料包需要一個CRC來校正資料包的準確性。這個CRC演算法實現使用表查詢替換了計算,節約了大約2K。
3、 Network tweaking【網路調整】:幾個補丁包減少支援的網路通訊協定,緩衝大小和開啟的通訊端。許多嵌入式裝置僅支援少量的協議,不需要有成百上千已連線的服務。
4、 No panic reporting【無應急報告】:如果裝置有三個狀態燈,一系列的串連,使用者不能看到,更少的影響,應急資訊顯示在一個不存在的終端上。如果裝置發生核心應急失效,使用者只需要重新啟動裝置即可。
5、 Reduction of inlining【減少直接插入】:直接插入是編譯器將代碼作為宏拷貝到它調用的每個位置,而不是產生一個函數調用。GCC預設將直接插入任何函數。通過抑制直接插入函數,代碼運行稍微慢一點,因為編譯器需要為調用和返回產生代碼,得到的報酬是對象檔案更小了。
Linux-tiny補丁發布成一個tar包,它可以一起應用,也可以一個一個單獨應用
核心調整建議補充
儘管Linux-tiny項目涉及到許多的方面,還有幾個額外的配置改動可以減少Linux腳印。
1、 移除ext2/3支援,使用另外一個不同的檔案系統:ext2/3檔案系統通常比較大,大於32K,大部分工程師啟用一個Flash檔案系統,但是不禁用ext2/3,這樣會浪費記憶體。
2、 移除對sysctl的支援,sysctl允許使用者在運行時調整核心參數,在大部分嵌入式裝置中,核心配置一旦定了就不需要改動了,使用這個特徵會浪費1K。
3、 減少IPC選項,大多數系統沒有SysV IPC特性(grep你的msget、msgct、msgsnd和msgrcv代碼)和POSIX訊息查詢(grep mq_*[a-z])一樣可以運行,移除它們可以節約18K。
4、 查看你改動的效果
Size命令報告一個對象檔案中所有代碼和資料的大小,這與ls命令的輸入是不同的,ls報告的是在檔案系統中的位元組數大小。
例如,一個核心用armv51交叉編譯器編譯報告如下:
# armv5l-linux-size vmlinx
text data bss dec hex filename
2080300 99904 99312 2279516 22c85c vmlinux
Text小節是編譯器發出的代碼,data小節包括全域和其他使用初始靜態符號的值,bss小節包括作為初始化的一部分被調零的待用資料。
雖然這個資料有啟迪作用,但它沒有展示系統消耗的記憶體部分,也無法通過查詢vmlinux得出,但是查看串連在一起的檔案,建立vmlinux是接下來最好的事情,為了得到這個資訊,使用find命令在核心項目中定位built-in.o檔案並計算大小:
# find . -name "built-in.o" | xargs armv5l-linux-size
?--totals | sort -n -k4
這個命令的輸出類似下面:
text data bss dec hex filename
189680 16224 33944 239848 3a8e8 ./kernel/built-in.o
257872 10056 5636 273564 42c9c ./net/ipv4/built-in.o
369396 9184 34824 413404 64edc ./fs/built-in.o
452116 15820 11632 479568 75150 ./net/built-in.o
484276 36744 14216 535236 82ac4 ./drivers/built-in.o
3110478 180000 159241 3449719 34a377 (TOTALS)
這個技術可以確切地指出佔用了大量空間的代碼,因此工程師可以首先移除這些代碼,當這樣做的時候,使用者應該在建立之前執行編譯清除動作,因為從核心中丟掉一個特徵並不意味著那個之前編譯好的對象檔案將會被刪除。
對於那些新添加到Linux核心中的東西,一個常見的問題是如何在核心配置程式中使用一個選項來聯合一些built-in.o檔案,這可以通過查看目錄下的Makefile和Kconfig檔案來實現,Makefile將包括象下面這樣的一行:
obj-$(CONFIG_ATALK) += p8022.o psnap.o
當使用者佈建了組態變數CONFIG_ATALK後它將產生右手邊的檔案。核心組態工具一般不會暴露下屬組態變數名。要查出變數名之間的串連以及什麼是可見的,在Kconfig中尋找沒有CONFIG_首碼的變數名。
find . -name Kconfig -exec fgrep -H -C3 "config ATALK" {} \;
它將產生下面這樣的輸出:
./drivers/net/appletalk/Kconfig-#
./drivers/net/appletalk/Kconfig-# Appletalk driver configuration
./drivers/net/appletalk/Kconfig-#
./drivers/net/appletalk/Kconfig:config ATALK
./drivers/net/appletalk/Kconfig- tristate "Appletalk protocol support"
./drivers/net/appletalk/Kconfig- select LLC
./drivers/net/appletalk/Kconfig- ---help---
還有一些事情需要做,因為使用者需要在配置樹中找到Appletalk協議支援,但是至少要清楚要尋找的是什麼
根檔案系統
對於許多新轉到嵌入式Linux的工程師而言,他們認為在一個嵌入式裝置上的根檔案系統是一個外部概念,在Linux之前的嵌入式解決方案通過直接連接應用程式代碼到核心中進行工作,因為Linux在核心和根檔案系統之間有一個非常好的分割,最小化系統工作不是以縮小核心為終止點,在最佳化之前,根檔案系統的大小與核心相比要小得多,無論如何,對於傳統的Linux而言,系統部分有許多需要調整的部分,調整它們以減少系統的大小。
第一個需要回答的問題是“我究竟需不需要根檔案系統?”簡單地說,需要。在核心啟動進程的結尾,它要尋找根檔案系統,由它再載入並運行第一個進程(通常是init,執行ps aux|head -2將告訴你在你系統上是什麼進程),缺少根檔案系統或初始化程式中的任何一個,核心將啟動不了。
最小的根檔案系統可以是一個檔案:裝置應用程式。假如這樣,核心要指定一個檔案,它是使用者空間的第一個進程。只要那個進程在運行,系統就能工作,但是,如果這個程式基於任何原因退出了,核心將掛起,裝置將需要重新啟動。大多數空間有限的系統也會選擇一個初始化程式,因為內務消耗小,初始化包括重建進程已死的代碼,預防遇到應用程式崩潰時核心掛起。
大多數Linux系統非常複雜,包括很多可執行檔和常用共用庫(包含運行在該裝置上的應用程式共用庫代碼),對於這些檔案系統,有很多選項存在用於減少RFS的大小。
改變C庫
結合GCC,大多數使用者都不想c庫作為一個獨立的實體,c語言套件括32個關鍵字(give、take等),因此c程式的大多數位元組來自這些標準庫,權威的c庫(glibc)被設計成高度相容、國際化和跨平台支援,但它也比較大,有很多可選擇的比較小的c庫:
uClibc:這個項目是為沒有記憶體管理單元(MMU-less)的處理器實現的c庫。uClibc從開始建立就很小,它提供與c庫相同的功能,但刪除了如國際化、大字元集支援和二進位相容性等特徵。而且,uClibc組態工具給使用者很大自由選擇什麼代碼進入庫,允許使用者進一步減小大小。
uClibc++:對於那些使用c++的程式而言,這個庫是在相同設計原則下的實現,支援標準c++庫的大部分功能,工程師可以很容易地在板子上部署基於c++的應用程式,僅需要幾MB空間。
Newlib:Newlib出自Red Hat,它有一個非常完整的數學庫實現,有很多做控制和測量應用程式的使用者喜歡它。
dietlibc:dietlibc是glibc的最佳代替品,它仍然是最小的分支,非常小,實際上只有70K,它刪除了如動態連結程式庫等特徵。它對ARM和MIPS支援得很好
使用一個交替的c庫
Newlib和dietlibc通過提供一個封裝的指令碼調用編譯器使用正確的參數集,忽略包括在編譯器中的標準c庫,使用一個替換的c庫。uClibc有點不同,它需要工具鏈。
一旦你知道如何調用GCC,接下來就要為項目更新Makefile檔案或建立指令碼,大多數情況下,為項目在Makefile檔案中使用下面這樣一行:
CC=CROSS_COMPILE-gcc
假如這樣,所有使用者都需要從命令列運行make命令覆蓋CC變數:
make CC=dietc
這將導致makefile為c編譯器調用diet,儘管看起來很誘人,不要在這個宏中添加參數,用CFLAGS變數代替,例如:
make CC="gcc -Os"
應該是:
make CC=gcc CFLAGS="-Os"
這個很重要,因為某些規則將調用CC編譯,參數將沒有意義,並會產生錯誤。
回到根檔案系統
在選擇了c庫後,所有在根檔案系統中的代碼需要用新的編譯器編譯,那樣代碼就可以使用最近的、更小的c庫。在這一點上,值得對靜態與共用庫進行評估,對於目標究竟該選擇哪個,如果裝置將運行任意的代碼,而且在部署時該代碼是未知的,共用庫是最好的選擇。如:裝置可能暴露一個API允許終端使用者或專業工程師編寫模組。假如這樣,裝置上的庫應該為這些新特徵實現提供最大的靈活性。
如果系統包括許多分隔的程式共用庫也是最佳的選擇,假如這樣,共用代碼的拷貝將比複製幾個檔案的相同代碼更小。
當只有幾個程式在使用時,最佳做法是為每種用途建立一個系統然後比較最後的大小,大多數情況下,較小的系統是沒有共用庫的,而且還有一個額外的受益,沒有共用庫的系統載入和啟動程式時更快(因為沒有串連這一步了),因此使用者從效率角度來說也受益了。
總結
儘管沒有象魔術一樣的工具使系統變得更小,但也不缺少工具協助使系統僅可能變得更小,而且,使Linux變小比減小核心大小更困難,根檔案系統需要嚴格檢查,因為這個組件比核心消耗得更多空間,本文主要敘述了可執行映像大小,減少運行中程式記憶體需求。
資源
1、Linux-tiny補丁: www.selenic.com/linux-tiny.一系列減少核心映射大小和運行時資源消耗的小補丁,這裡面的許多補丁已經整合到核心中了。
2、GNU C庫: www.gnu.org/software/libc. GNU C標準庫是c庫的規範實現,可以在幾乎所有平台運行,而且可以向後相容。
3、uClibc: www.uclibc.org. 更小的c庫。
4、Newlib: sourceware.org/newlib. Red Hat的小C庫。
5、dietlibc: www.fefe.de/dietlibc. 最小的c庫