CramFS在Linux嵌入式環境的應用

CramFS在Linux嵌入式環境的應用

一, 前言
相信從事過嵌入式Linux環境開發的讀者們,都會面臨到要如何利用最少的系統資源,來建構一個完整嵌入式環境的問題,通常會採取的方式不外乎把Linux Kernel依所要執行的目的環境量身打造,建構一個符合該硬體平台的Linux Kernel,避免不必要的核心功能與驅動程式。
此外,我們還能動手的就是縮減動態函式庫的大小,依據目標系統執行檔呼叫的動態函式庫來進行整理,過濾用不到的函式庫。甚至可以進一步的過濾掉函式庫中用不到的函式本身。
透過這些方式,我們可以得到一個精簡的Linux執行環境,不過我們還必須考慮到的部分就是,嵌入式環境中Linux執行環境所使用記憶體儲存媒體。在Linux嵌入式環境中,許多人會採用的方式就是透過RAMDISK來儲存檔案系統的內容,所謂的RAMDISK就是在開機時,我們把一部份的記憶體虛擬成磁碟,並且把之前所準備好的檔案系統映像檔解壓縮到該RAMDISK環境中。
利用RAMDISK,我們必須要耗用部分的記憶體做為儲存的媒介,對於嵌入式的環境中,這就象徵可以使用的記憶體資源減少了。舉個例子來說,如果檔案系統的壓縮率為50%,我們在一個8MB的Flash上面放置我們的執行環境,其中包括了Embedded QT、SendMail Server、Apache Server……等,假設在不壓縮的情況下需要16MB的儲存空間才能安置所有的執行環境,經過壓縮後恰好可以放入一個8MB的Flash上。當我們啟動這個嵌入式的Linux環境後,Linux核心必須配置一個16MB的RAMDISK記憶體空間來儲存原本8MB Flash上面解壓縮出來的執行環境。我們在開機完成後,便可以在這16MB 的RAMDISK環境裡進行我們的工作,可是我們會注意到的一點就是我們總共耗費了16 MB的動態記憶體與8MB的Flash空間。而且這兩個儲存媒體其實所要儲存的資料都是一樣的,只不過一個經過壓縮,而一個是解壓縮後的環境。
因為有了這樣的問題存在,所以在嵌入式Linux上面對於壓縮式的檔案系統有了它存在的必要性,因此CramFS的出現正好可以解決這樣的問題。
CramFS是Linus Torvalds在Transmeta任職時,所參與開發的檔案系統,筆者在本篇文章中所採用的Linux Kernel版本為2.4.3,把Linux Kernel原始碼解開後,各位可以在”linux/fs/cramfs”中找到CramFS的原始碼。不過目前CramFS為一個唯讀的檔案系統,也就是說使用CramFS的話,如果我們的嵌入式環境需要儲存暫時性的資料,就必須另外保留一個Flash空間做為儲存資料之用。
在我們採用了CramFS之後,如同之前RAMDISK所舉的例子,我們可以把原本的執行環境壓縮到8MB的Flash中,如果我們現在要瀏覽目錄或是要讀取Flash中的檔案時,CramFS檔案系統會動態的去算出壓縮後的資料所儲存的位置,在即時的解壓縮到記憶體中,對於使用者來說,使用CramFS與RAMDISK是感覺不出使用上的差異性,在筆者的測試過程中﹝PII 350的機器﹞幾乎感覺不出系統在解壓縮上的延遲。
相信各位讀者到此,應該對於RAMDISK與CramFS兩者的不同有所了解,如下圖﹝一﹞所示就是一個RAMDISK環境的啟動流程,我們可以在圖的左邊看到實體的記憶體中,必須要劃分出一塊區域做為RAMDISK虛擬磁碟裝置所需的記憶體空間,而剩下的實體記憶體才是Linux Kernel與使用者程式所能使用的部分。
圖﹝一﹞,RAMDISK的啟動流程
接下來,我將為各位介紹CramFS的架構與應用,透過實際的例子我們可以了解CramFS相較於RAMDISK所帶來的好處與優勢。
二, 應用CramFS
想要在系統中提供CramFS的能力,我們必須要在編輯核心時把CramFS的選項加入,如下
Compressed ROM file system support (CONFIG_CRAMFS) [Y/m/n/?]
筆者選擇直接把CramFS編入核心中,而不透過Module的方式動態載入,包括接下來所介紹的內容,我都是以直接把CramFS編入的Linux核心做為我們解說的範例。
目前許多使用Linux的PDA都採用了CramFS檔案系統來降低檔案系統對於記憶體資源的耗用,不過對於大多數的讀者來說,手邊隨手可得的就是X86的桌上型電腦,筆者將以X86平台做為我們使用CramFS的實例,讓各位可以立刻親身體驗CramFS所帶來的好處。
當我們把Linux的原始碼解開後,可以在” linux/scripts/cramfs”找到工具程式mkcramfs的原始碼,在這目錄下可以透過 make 指令來把mkcramfs原始碼編譯為可執行檔。首先,mkcramfs其實就是一個包含在Linux核心原始碼環境中,用來製作CramFS檔案系統映像檔的工具程式,我們只要把規劃好的使用者環境,透過這個工具來壓縮處理,就可以生成屬於CramFS檔案系統的映像檔,而透過mkcramfs生成的映像檔將會包含有Superblock與完整的檔案系統結構……等,因為有了這些基本的CramFS檔案系統結構,所以我們可以直接把製作好的CramFS檔案系統映像檔裝置到我們目標儲存裝置中。
如下圖﹝二﹞所示,就是一個CramFS檔案系統映像檔的結構,首先我們可以看到最前面就是CramFS的Superblock,大小共76 bytes。之後便是CramFS的inode結構,最需要注意的一點就是每個cramfs_inode的結構大小為12 bytes,而每個cramfs_inode所代表的檔案名稱直接就會接在cramfs_inode的後面,以0x00結尾。並且 ”cramfs_inode + 檔案名稱”的長度必須為4的倍數,如果不足的部份就會補0,如果長度恰好為4的倍數,那就不補0直接連接下一個cramfs_inode。
如此 ”cramfs_inode + 檔案名稱”+ ”cramfs_inode + 檔案名稱” + ”cramfs_inode + 檔案名稱”……..,的方式就構成了CramFS檔案系統映像檔的目錄結構。

圖﹝二﹞CramFS檔案系統映像檔的結構
筆者自行寫了一個測試CramFS檔案系統映像檔的工具程式check_cramfs,它主要的原理就是去讀取CramFS檔案系統的映像檔,再根據cramfs_super與cramfs_inode來分析Superblock與各個inode的內容,執行結果如下所示
[root@hlchou /cramfs]# ./check_cramfs /dev/hdc2
file len:835584 bytes
size of superblock:76 bytes
Suprtblock
===============
magic:28cd3d45h
size:10000h
flags:0h
future:0h
signature:
43 6f 6d 70 72 65 73 73 C o m p r e s s
65 64 20 52 4f 4d 46 53 e d R O M F S
fsid:
1b 27 36 d8 ce f2 43 f6 ‘ 6 C
0d 4c 90 17 09 b7 04 d4 L
name:
43 6f 6d 70 72 65 73 73 C o m p r e s s
65 64 00 00 00 00 00 00 e d
mode:41edh uid:0h size:188 gid:0h namelen:0 offset:19 根目錄
mode:41edh uid:0h size:0 gid:0h namelen:3 offset:0 lost+found
mode:41edh uid:0h size:1208 gid:0h namelen:1 offset:66 bin
mode:45edh uid:0h size:3536 gid:f4h namelen:1 offset:368 dev
mode:45edh uid:0h size:264 gid:f4h namelen:1 offset:1252 etc
mode:41edh uid:0h size:184 gid:0h namelen:1 offset:1330 lib
mode:a1ffh uid:0h size:9 gid:0h namelen:2 offset:163260 linuxrc
mode:45edh uid:0h size:0 gid:f4h namelen:1 offset:0 proc
mode:45edh uid:0h size:176 gid:f4h namelen:1 offset:1376 sbin
mode:41edh uid:0h size:0 gid:0h namelen:1 offset:0 tmp
mode:45edh uid:0h size:0 gid:f4h namelen:1 offset:0 usr
mode:41edh uid:0h size:0 gid:0h namelen:1 offset:0 var
我們可以發現有幾個重要的參數,像是magic為一個任意值,在cramfs.h中我們可以看到如下的設定
“#define CRAMFS_MAGIC 0x28cd3d45 ”
magic目前預設為0x28cd3d45,參數size 目前尚未被使用,而Superblock的參數name為 ”C o m p r e s s e d”。
相信讀者們也都發現到,Superblock結構的最後面就是一個
” struct cramfs_inode root; ”
也就是說,Superblock最後面12個bytes,是用來記錄CramFS檔案系統映像檔的根目錄,在Linux底下我們所使用的根目錄為 “/”。之後,就依序秀出根目錄之後的各個目錄與檔案名稱與crsmfs_inode的結構內容。
其中,mode是代表該檔案或是目錄的屬性,我們可以用來辨別是目錄、裝置檔案、執行檔…..等,不同檔案或目錄的型態。uid為該目錄或檔案所屬的User ID,例如屬於root的話uid為0。size為該目錄或檔案在未經過壓縮前所佔的儲存空間大小﹝以bytes為單位﹞。gid為該目錄或檔案所屬的Group ID。namelen為該目錄或檔案的名稱長度,而namelen所秀出的值為該目錄或是檔案名稱在儲存時所需要的記憶體空間大小除以4後的值,也就是說,因為在CramFS儲存目錄結構的資料時是以4的倍數為單位,所以名稱 “bin”需要 1*4 =4 bytes而 lost+found需要 3*4 =12 bytes,多餘的空間就用0x00來補滿。而offset代表了該目錄或是檔案在CramFS檔案系統映像檔中由磁區起始位置開始的偏移位置,而offset的值都是經過除以4後的結果,所以如果要找出目錄內容或是檔案壓縮過的資料儲存位置,就把offset的值乘上4後,再由檔案系統的起始位置向後偏移即可。
在大略介紹過CramFS的檔案系統結構後,接下來我們實際的把CramFS檔案系統應用到我們的執行環境當中。
首先,我們把製作好的執行環境放到目錄tree底下,透過指令du來查看目錄中所有檔案的大小與總和,如下所示,
[root@hlchou /cramfs]# du tree
4.0k tree/lost+found
92k tree/bin
4.0k tree/dev
12k tree/etc/rc.d
72k tree/etc
1.2M tree/lib
4.0k tree/proc
360k tree/sbin
4.0k tree/tmp
4.0k tree/usr
4.0k tree/var
1.8M tree
透過du指令,我們可以發現目前tree目錄底下所有的檔案大小總和為1.8Mbytes,這是目前尚未經過壓縮的大小。
如下,我們透過之前所提過的工具程式mkcramfs來製作新的CramFS檔案系統映像檔,
[root@hlchou /cramfs]# ./mkcramfs tree tree.CramFS
Super block: 76 bytes
lost+found
bin
dev
etc
lib
linuxrc
proc
sbin
tmp
usr
var
‘bin':
…..
……………………
…..
-71.09% (-51852 bytes) libpthread.so.0
-53.75% (-11660 bytes) libcrypt.so.1
-56.82% (-5052 bytes) libutil.so.1
-57.70% (-6088 bytes) libdl.so.2
166.67% (+15 bytes) linuxrc
-45.60% (-69868 bytes) init
-46.45% (-3768 bytes) mingetty
300.00% (+12 bytes) update
-50.62% (-21632 bytes) ifconfig
-50.81% (-19096 bytes) route
-51.99% (-12980 bytes) modprobe
-48.43% (-14160 bytes) insmod
-45.58% (-12328 bytes) syslogd
-49.05% (-9964 bytes) klogd
Everything: 816 kilobytes
[root@hlchou /cramfs]# ls -l *.CramFS
-rw-r–r– 1 root root 835584 Jul 16 18:41 tree.CramFS
我們可以發現原本為1.8Mbytes大小的目錄內容,被壓縮成一個835kbytes的CramFS檔案系統映像檔,
有了這樣的一個映像檔,接下來就是要把這個映像檔與Linux核心整合在一個儲存媒體上。在X86的架構中,我們可以選擇Lilo或是Syslinux做為我們開機程序的核心載入器,在這個例子中筆者以Syslinux為例子來做一個說明。
﹝可到http://www.ibiblio.org/pub/Linux/system/boot/loaders/下載Syslinux﹞
首先,各位可以拿一個IDE介面的硬碟、IDE介面的Flash Disk或是M-System的Flash,基本上只要是可以放置DOS FAT檔案系統的儲存媒體就可以,然後把Syslinux安裝在上面,至於如何安裝Syslinux的流程,讀者可以自行參閱相關文件。
以筆者所使用的16MB IDE Flash Disk來說,我把它透過Linux下的fdisk切割成兩塊磁區,第一個磁區我安裝了Syslinux與Linux核心,第二個磁區我把tree.Cramfs透過dd指令裝入
如下
[root@hlchou /cramfs]# dd if=./tree.CramFS of=/dev/hdc2
1632+0 records in
1632+0 records out
[root@hlchou /cramfs]#
安裝完Syslinux與CramFS檔案系統映像檔的磁區分割內容如下圖﹝三﹞所示,

圖﹝三﹞,使用Syslinux與CramFS映像檔的磁區分割內容
其中,筆者的Syslinux設定檔syslinux.cfg設定內容如下
default linux
prompt 1
timeout 1
label linux
kernel linux
append root=/dev/hdc2
接下來,我們只要重新開機啟動系統,把開機磁碟設成我們安裝”Syslinux與CramFS映像檔”的磁碟機即可﹝可以為一般磁碟或是Flash裝置﹞。
在本文中,筆者所寫的工具程式check_cramfs與筆者所使用的執行環境範例﹝tree目錄﹞,各位可以到以下網址Download
http﹕//。。。。。。/我會寄給你們,請幫我填網址﹞
各位如果有興趣的話,可以自行下載並且把環境給建構起來,親身體驗CramFS的運作與他所帶來的好處。
三,CramFS在系統的架構
如下圖﹝四﹞所示,CramFS在開機的過程中,在VFS檔案系統啟動後就會跟著被初始化,其中CramFS檔案系統的初始化進入點為init_cramfs_fs﹝包含在 linux/fs/cramfs/inode.c的檔案中﹞

圖﹝四﹞,CramFS檔案系統初始化的流程
CramFS初始化是透過呼叫函式init_cramfs_fs﹝﹞來進行的,我們可以由圖中看到在函式init_cramfs_fs﹝﹞的最後,會呼叫register_filesystem﹝﹞對系統進行註冊,以便於在遇到檔案系統型態為 “cramfs”的檔案系統時,可以由系統交給CramFS檔案系統來進行處理。
在系統初始化完成後,我們可以手動的載入CramFS檔案系統的磁區,如下的例子
[root@hlchou cramfs]# mount -t cramfs /dev/hdc2 /c
[root@hlchou cramfs]# ls /c
big dev/ lib/ lost+found/ sbin/ usr/
bin/ etc/ linuxrc@ proc/ tmp/ var/
在我們mount CramFS檔案系統磁區的過程中,系統會執行以下的呼叫流程


圖﹝五﹞,Mount CramFS的流程
在函式cramfs_read_super﹝﹞中,會呼叫函式cramfs_read﹝﹞把Superblock讀取到記憶體中,並且進行Superblock磁區的型態確認,例如
//確認Superblock的參數magic是否為 0x28cd3d45,
//若非則結束函式,並傳回NULL
if (super.magic != CRAMFS_MAGIC)
{
printk(“wrong magic\n”);
goto out;
}
//確認Superblock的參數signature是否為 “Compressed ROMFS”,
//若非則結束函式,並傳回NULL
if (memcmp(super.signature, CRAMFS_SIGNATURE, sizeof(super.signature)))
{
printk(“wrong signature\n”);
goto out;
}
//參數flags預設值為0,而CRAMFS_SUPPORTED_FLAGS值為0xff,
//在&運算後,若為1則結束函式,並傳回NULL
if (super.flags & ~CRAMFS_SUPPORTED_FLAGS)
{
printk(“unsupported filesystem features\n”);
goto out;
}
函式cramfs_read_super﹝﹞的最後,會呼叫函式get_cramfs_inode﹝﹞,取得CramFS檔案系統根目錄的資訊。
如果我們在CramFS的檔案系統中,瀏覽一個目錄的內容,CramFS檔案系統會直接去讀取Directory Structure磁區中的內容,並且把完整的目錄結構秀出來,主要的原因是在於檔案的實體資料本身會透過CramFS來壓縮,可是檔案名稱與目錄的架構,是不會透過CramFS檔案系統壓縮的,所以說這些查詢目錄的過程,就無須透過解壓縮的流程,可以直接透過查詢Directory Structure磁區的內容,來得到我們所要的結果
如下圖﹝六﹞所示,


圖﹝六﹞,查詢CramFS檔案系統的目錄內容
筆者在此把搜尋目錄的流程大略說明一下,首先我們可以由Superblock取得根目錄﹝”/”﹞的cramfs_inode,例如﹕根目錄的offset為19,
mode:41ffh uid:0h size:204 gid:0h namelen:0 offset:19 根目錄
也就是說這個根目錄底下的檔案或是目錄的資料會放在由CramFS磁區起始往後偏移19*4=76 bytes的位置。接下來,我們偏移到76 bytes的位置,依序把”cramfs_inode + name”的結構讀取出來,得到如下的結果
mode:41edh uid:0h size:0 gid:0h namelen:3 offset:0 lost+found
mode:41edh uid:0h size:1208 gid:0h namelen:1 offset:70 bin
mode:45edh uid:0h size:3536 gid:f6h namelen:1 offset:372 dev
mode:45edh uid:0h size:264 gid:f6h namelen:1 offset:1256 etc
mode:41edh uid:0h size:184 gid:0h namelen:1 offset:1334 lib
mode:a1ffh uid:0h size:9 gid:0h namelen:2 offset:163264 linuxrc
mode:45edh uid:0h size:0 gid:f6h namelen:1 offset:0 proc
mode:45edh uid:0h size:176 gid:f6h namelen:1 offset:1380 sbin
mode:41edh uid:0h size:0 gid:0h namelen:1 offset:0 tmp
mode:45edh uid:0h size:0 gid:f6h namelen:1 offset:0 usr
mode:41edh uid:0h size:0 gid:0h namelen:1 offset:0 var
其中,mode的值可以用來判斷目前的cramfs_inode是為目錄或是檔案型態。
如果說我們現在要查看etc目錄下的所有檔案或是目錄名稱,因為”etc”cramfs_inode的offset值為1256,所以etc目錄底下的資料會存放在距離CramFS磁區起始位置偏移1256*4=5024 bytes的cramfs_inode。所以我們現在由CramFS磁區起始位置偏移5024bytes,得到如下的結果
mode:45edh uid:0h size:48 gid:f6h namelen:1 offset:1322 rc.d
mode:81a4h uid:0h size:376 gid:f6h namelen:2 offset:12288 inittab
mode:81edh uid:0h size:21 gid:f6h namelen:2 offset:12331 passwd
mode:81edh uid:0h size:13 gid:f6h namelen:2 offset:12339 group
mode:81a4h uid:0h size:437 gid:f6h namelen:2 offset:12345 profile
mode:81a4h uid:0h size:97 gid:f6h namelen:3 offset:12411 protocols
mode:81a4h uid:0h size:11349 gid:f6h namelen:2 offset:12435 services
mode:81a4h uid:0h size:20 gid:f6h namelen:2 offset:13602 hosts
mode:81a4h uid:0h size:26 gid:f6h namelen:3 offset:13610 host.conf
……….
……………………..
……….
所以囉,透過這樣的方式,我們就可以把CramFS檔案系統映像檔的目錄內容解讀出來囉。不論是目錄的內容或是檔案壓縮過的資料儲存位置,都可以經由Offset值來推算出來,並且讀取解壓縮到記憶體中。
CramFS檔案系統預設是每次都會解壓縮4Kbytes的資料到Linux Cache Memory中。所以說,如果讀者去觀察CramFS的讀取運作時,會發現只有第一次檔案被讀取時才會動態的去解壓縮,第二次與第二次以後的檔案讀取動作就會直接去該檔案目前所對應到的Linux Cache Memory來讀取,而不會再去解壓縮,耗費系統運算資源。這樣的運作原理,與我們一般使用的Linux 檔案系統﹝例如﹕Ext2﹞是一致的,透過一個Cache的機制,讓目前被讀取的檔案不必要每次都從磁碟機中讀取出來,浪費許多磁碟機搜尋的時間,把目前使用的資料暫存在Cache中,可以增加每一次讀取檔案的速度。如果在CramFS檔案系統中,檔案大小超過 4Kbytes的話,就會分多次來解壓縮。
如下圖﹝七﹞所示,在Linux的環境下,解決讀取大型檔案的方式為,當使用者開啟一個大型檔案時,系統並不會一口氣就把該檔的內容讀取到記憶體中,所採取的方式是當使用者讀取到檔案的某個位置時,在依據該檔案目前所讀取內容儲存的磁區,來動態的從磁碟系統中讀取出來,載入到記憶體中。

圖﹝七﹞,CramFS檔案系統讀取檔案內容的示意圖
這樣的方式好處就是,開啟大型檔案時不會一下子就耗用掉過多的記憶體空間,而可以針對目前實際已經讀取的部分才配置記憶體,尚未讀取的部份就不配置記憶體,以節省記憶體的資源。CramFS檔案系統會動態的去把檔案每個區塊,依目前讀取的進度與位置,來動態的解壓縮到記憶體中。
四,結語
本文所介紹的CramFS是Linux在嵌入式應用上,相當不錯的一個選擇。不論是在X86平台或是ARM與MIPS這類其它不同應用的處理器,都可以透過CramFS達到減少硬體資源耗用的功能。不過相對來說,檔案系統唯讀的特性使得它在應用上有了些許的限制。
其實,我們可以考慮這樣規劃系統,一部份的Flash儲存CramFS所製作好的檔案系統映像檔,一部份的Flash規劃成可寫入的部分,而可寫入的部分可以選用自己簡單的檔案配置表或是採用其它Linux上面簡易的檔案系統亦可。
Linux環境的迷人之處在於它開放原始碼的特性,因為如此所以我們有機會結合眾人之力來完成自己想要的產品或是成果。本篇文章的內容,主要就是來自Linux Kernel的原始碼,感謝所有付出的人。
最後,筆者覺得其實軟體技術無分好或壞,主要還是在於它所要滿足的產品需求以及軟體從業人員在規劃系統時所考量的層面,有了CramFS檔案系統的出現,當然可以滿足許多人對於嵌入式環境有限硬體資源的要求,相對的它也犧牲了些微的執行效率。面對不同的考量與需求,主導產品的開發過程中,才可以真正的迎合市場的需要而又不會太過於鑽營在技術的牛角尖。
希望這篇文章,可以真正的為各位讀者帶來有益的收穫,如果各位有何問題的話,歡迎隨時與我聯繫,我的E-Mail是 hlchou@gmail.com