Tuesday, October 4, 2011

Rootfs 與 ramfs 和 initramfs的差別

Rootfs, Cramfs, JFFS2, ramfs, initramfs, ...
 

Ref: http://tw.myblog.yahoo.com/blue-comic/article?mid=41&prev=48&next=40&l=f&fid=7
Ref: http://rritw.com/a/bianchengyuyan/PHP/20110619/92256.html


檔案所有提到核心 build 的部分,我都解譯成了 Kbuild。

一、補充文件系統知識 
 
    Linux根文件系統是存放tool軟件、lib文件、script(腳本)、配置文件、其他特殊文件、自己開發的應用程序的地方。嵌入式linux的根文件系統rootfs就像windows操作系統的C、D盤這種概念機制,FLASH對應硬盤。但linux是掛載點的概念,根文件系統是一個整體,組織到一個樹狀的目錄結構中。這種文件組織遵守文件系統科學分類標准FHS,一種國際標准。運行、維護系統所必須的各種
在開發、測試階段,基本上都是使用NFS(網络文件系統),NFS文件系統是在你的開發主機HOST裏,不是在你的板子裏。制作嵌入式產品根文 件系統,就是要把NFS指向的文件系統文件夾裏所有的內容,通過其他少數幾種的Linux文件系統打包生產一個2進制鏡像文件,然後以某種方式燒寫到 FLASH芯片內。開發階段,特別是應用程序開發、動態加載的驅動模塊開發,使用NFS,可以省去麻煩的根文件系統鏡像文件的下載和燒寫過程,便可以在自 己板子上運行這些開發的程序和模塊。
 
    Linux支持超過50種文件系統,但是只有少數幾種在嵌入式OS中常用,見下圖。linux嵌入式產品的根文件系統是需要燒寫到Flash芯片裏。
 
什麼是 Cramfs?-------------
    Cramfs文件系統:(Compressed ROM File System)是個簡單,壓縮的,只讀的文件系統。除了可以用於MTD下只讀並且極少更新img的NOR/NAND,IDE的CF,FTL/NFTL的 NOR/NAND,可作为RAM disk的IMG存儲。由於簡單,Cramfs有一些限制,例如最大支持16M(?256M),沒有當前(.)和父(..)目錄,頁大小固定为 4096,gid(8位)最大为255,所有的鏈接文件計數都是1等等。在cramfs文件系統中,每一頁(4KB)被單獨壓縮,可以隨機頁訪問,其壓縮 比高達2:1,为嵌入式系統節省大量的Flash存儲空間,使系統可通過更低容量的FLASH存儲相同的文件,從而降低系統成本。Cramfs文件系統以 壓縮方式存儲,在運行時解壓縮,所以不支持應用程序以XIP方式運行,所有的應用程序要求被拷到RAM裏去運行,但這並不代表比Ramfs需求的RAM空 間要大一點,因为Cramfs是采用分頁壓縮的方式存放檔案,在讀取檔案時,不會一下子就耗用過多的內存空間,只針對目前實際讀取的部分分配內存,尚沒有 讀取的部分不分配內存空間,當我們讀取的檔案不在內存時,Cramfs文件系統自動計算壓縮後的資料所存的位置,再即時解壓縮到RAM中。另外,它的速度 快,效率高,其只讀的特點有利於保護文件系統免受破壞,提高了系統的可靠性。由於以上特性,Cramfs在嵌入式系統中應用廣泛。但是它的只讀屬性同時又 是它的一大缺陷,使得用戶無法對其內容對進擴充。
 
制作Cramfs根文件系統命令如下:
 
   #mkcramfs [-h] [-e edition] [-i file] [-n name] dirname outfile
 
mkcramfs的各個参數解釋如下:
 -h:顯示幫助信息
-e edition:設置生成的文件系統中的版本號
-i file:將一個文件映像插入這個文件系統之中(只能在Linux2.4.0以後的內核版本中使用)
-n name:設定cramfs文件系統的名字
dirname:指明需要被壓縮的整個目錄樹
outfile:最終輸出的文件
cramfsck的命令格式:
cramfsck [-hv] [-x dir] file
cramfsck的各個参數解釋如下:
-h:顯示幫助信息
-x dir:釋放文件到dir所指出的目錄中
-v:輸出信息更加詳細
file:希望測試的目標文件
 
例子:#mkcramfs rootfs rootfs.cramfs生成rootfs.cramfs文件系統鏡像.其中rootfs是你調試的根文件系統的文件夾,rootfs.cramfs是生產鏡像文件的名字。
Squashfs文件系統:是壓縮的只讀的fs,保留了Cramfs很多優點,去掉它的限制。壓縮比更高。是一個不錯的文件系統。命令为:#mksquashfs [source] [dest]
 
什麼是 JFFS2?-------------
    JFFS2文件系統:可寫、保留數據、壓縮、掉電保護,提供wear leveling,适合板載NOR、NAND和Doc。JFFS2提供垃圾回收機制,通常可以很好地工作。但是當文件系統接近極限或者要求更新一個非常大 的文件,垃圾回收的時間會很長,將會延遲文件系統的操作,這會對沒有考慮到這種情況的某些實時要求的軟件帶來負面的影響。當空間滿的時候,嘗試更新或者階 段文件內容會被提示文件系統已滿並失敗,即使看起來不要求添加存儲block,這是因为日志結構文件系統,需要在log中增加log。在使用的時候,我們 應保證存儲空間的充足,上面的應用能夠容忍時延(避免快滿時候垃圾回收的消耗影響)。 JFFS2用途很廣,但是對於現在容量巨大的閃存不适合,新的UBI文件系統出現。JFFS2的不可擴展性是最大的問題,作为JFFS2的升 級,JFFS3支持大容量的閃存。JFFS2使用MTD工具:

    #mkfs.jffs2 –r ottfs/ –o images/rootfs-jffs2.img –e 128kiB 其中128是指擦寫block的大小,如果超過實際閃存的大小,會引起文件系統的崩溃,如果太小,block內有部分存儲空間沒能利用。
 
    #sumtool –i rootfs-jffs2.img –o rootfs-jffs2-summed.img –e 128KiB 增減erase block的summary node。
 
什麼是 YAFFS2?-------------
YAFFS2文件系統:是可寫,下電保護的文件系統,廣泛引用於Linux和RTOSeS。和JFFS2相比,它減少一些功能,因此速度更快、 占用內存資源更少。YAFFS2自帶NAND芯片的驅動,是專門为NAND閃存設計的。以前的YAFFS只支持512-BYTE/Page的NAND,而 YAFFS2不但兼容512-BYTE/Page,還支持目前流行的2048-BYTE/Page大容量NAND FLASH。YAFFS2文件系統的鏡像工具是mkyaffs2img。隨便在GOOGLE上輸入mkyaffs2img就可以找到滿屏幕的制作方法,這 裏不用介紹。


什麼是 ramfs?-------------
ramfs 是一種非常簡單的的檔案系統,可動態調整大小,並且是基於記憶體的。他為Linux
實現磁片暫存機制,比如 page cache 和 dentry cache。
一般情況下,Linux會把所有的檔案都暫存在記憶體中。那些包含著從存儲設備(一般是檔案
系統所掛的區塊設備)中讀來的數據的頁會暫存有記憶體中,以便再次使用,但該頁被標明是
乾淨的(可以使用的),以便VM在需要記憶體的時候可以使用該頁。同樣,一旦數據被寫回
到存儲設備,該數據所占空間就被標明是乾淨的,但出於暫存的目的,該數據仍保留在
記憶體中,直到VM再次分發該記憶體。一種相似的機制(dentry cache)極大的提升了訪問目
錄的速度。
對於 ramfs,無須回寫。ramfs中的檔案同樣會分發 dentries 和 page cache,只是這
些檔案的記憶體無須寫到存儲設備上。也就是說,這些頁永遠都不會被標明是乾淨的,所
以當VM回收記憶體的時,這些頁是不會被回收的。
由於所有的工作都由已經存在的Linux暫存架構完成,實現 ramfs 的代碼量是很小的。
基本上,你可以把你掛載的磁片暫存當作一個檔案系統。正因為此,ramfs 是不會佔用
任何的磁片空間的,所以他不是一個可以透過menuconfig 移除的可選項。

ramfs 和 ramdisk----------------
更早的 ram disk 機制在記憶體外手動建立一個區塊設備並將其作為一檔案系統的存儲設備
。該區塊設備的大小是固定的,所以掛載上去的檔案系統的大小也是固定的。使用 ram
disk 和使用 dentries (建立與刪除)一樣,無須從偽設備拷貝記憶體到 page cache (
並將修改部分寫回)。另外,他還需要一個驅動程式,從而能對數據進行操作。
與 ramfs 相比,這種機制浪費記憶體(和記憶體匯流排的帶寬),讓CPU作了一些無用功,並且
還污染了CPU的暫存。(這裡有一些透過使用頁表從而避免拷貝的技巧,但他們非常的複
雜並且其實現代價也和拷貝差不多)另外,由於所有的檔案訪問都要透過 page cache
和 dentry cache,所以 ramfs 所完成的工作無論如何都會發生。ram disk 基本上是
不需要了,ramfs 的內部實現更簡單。
ramdisk 要被放棄的另一個原因是,loopback 設備的引入。loopback 設備提供了更加
靈活、方便的建立人工區塊設備的方法,該方法把用檔案的來實現設備,而不再是大量的
記憶體。相關細節請看 losetup(8)。

ramfs 和 tmpfs--------------
ramfs 的一個缺點就是會儲存你所寫的數據直到你將系統的所有記憶體用光。而 VM 是不
會釋放這些記憶體的,因為VM認為這些數據是要寫回到存儲設備(不是swap space)的。但
是ramfs 根本就沒有這樣的設備。正因為此,只有root(或一個信任的用戶)才能向ramfs
掛載點執行寫操作。
tmpfs 是在 ramfs 的基礎上,增加的大小的限制和向 swap space 寫數據的能力。普通
用戶也可以對 tmpfs 掛載點執行寫操作了。相關細節請看
Documentation/filesystems/tmpfs.txt。


什麼是 rootfs?--------------
rootfs 是 ramfs(或tmpfs,如果可用的話) 的一種特殊實現,其一般出現下 2.6 版本
中。你可以讓進程 init 中止,但你卻不能卸載 rootfs;核心並沒有專門的代碼去檢
查和處理空清單,只是用了更小更簡單的代碼去保正清單不會為空。
大多數的系統只是將另一個檔案系統掛載到 rootfs 上,將其覆蓋並忽略。一個空的
ramfs的所占空間是非常小的。
什麼是 initramfs?-----------------
所有的 2.6 核心都包含了一 "cpio" 格式的壓縮包,當核心啟動時,將該檔案解壓到
rootfs 中。然後,核心檢查 rootfs 是否包含了 "init" 檔案,如果有,就將它作為
1號進程執行。該進程完成系統啟動余下的工作,包括定位和掛載真正的根設備(如果有
的話)。如果 rootfs 在解壓 cpio 檔案之後,沒有包含 init。核心將會透過舊代碼去
定位和掛載根分區,然後執行類似 /sbin/init 的檔案。
與舊的 initrd 的不同主要是以下幾方面︰
- 舊 initrd 經常是一個單獨的檔案,而 initramfs 壓縮檔案是個指向核心映像的
聯接。(目錄 linux-*/usr 在編譯核心時,被用來生成該壓縮檔案。)

- 舊 initrd檔案是一壓縮的檔案系統映像(有些檔案格式,比如 ext2 ,是要將驅動
編譯進核心的),而新的 initramfs 壓縮檔案是一個壓縮的 cpio 檔案(像 tar,
但更簡單,具體請看 cpio(1)和
Documentatin/early-userspace/buffer-format.txt)。核心中對 cpio 解壓的代
碼不只是非常的小,同時也是 __init 數據,這樣的數據會在啟動過後被系統回
收。
- 由舊 initrd 執行的程式(稱為 /initrd,不是 /init) 在完成一些設定工作以後
就返回核心了,而 initramfs 的 init 並不返回核心。(如果 /init 需要交出控
製權,那麼他會把一新的根設備掛載到 /,並將以前的根設備覆蓋掉(譯注︰
mount的覆蓋類似於壓 )),然後執行另一個 init 程式。請看下面的
switch_root 工具。)
- 當改變成另一個根設備時,initrd 要 pivot_root 然後再卸載該 ramdisk。但
initramfs是 rootfs︰你不能 pivot_root rootfs,也不能將其卸載。為了釋放
rootfs 所占記憶體空間,要用一新的根將原有的根覆蓋掉(cd /newmount;
mount --move . /;chroot 。),而不是將 rootfs 中的所有內容刪除。然後將
stdin/stdout/stderr 都指向新的 /dev/console,然後執行新的 init。
因為這是一個要求很高的過程(在你執行這些命令之前,就執行了刪除命令),所
以 klibc 包引入了一幫助程式(utils/run_init.c)來幫你完成這些工作。其它的
包大多數(比如 busybox)都有一個這樣的命令 "switch_root"。
生成 initramfs︰---------------
2.6 的核心在編譯過程中會生成一用gzip 壓縮過的 cpio 格式的 initramfs檔案,並
將其聯接到最終的核心中去。預設情況下,該檔案是空的(在 X86 上,消耗 134的位元組
)。
配置選項 CONFIG_INITRAMFS_SOURCE (出於一些原因,該選項在 menuconfig 的
devices->block下,而存儲在 usr/Kconfig 下 ) 可用於指定用哪些檔案製造
initramfs 檔案,而製造出來的 initramfs 檔案會最終併入核心中。該選項可以指向
一己存在的壓縮了的 cpio 檔案,也可以是一個包含了選定檔案的目錄,或者是一個類
似下面例子的text 配置檔案︰
dir /dev 755 0 0
nod /dev/console 644 0 0 c 5 1
nod /dev/loop0 644 0 0 b 7 0
dir /bin 755 1000 1000
slink /bin/sh busybox 777 0 0
file /bin/busybox initramfs/busybox 755 0 0
dir /proc 755 0 0
dir /sys 755 0 0
dir /mnt 755 0 0
file /init initramfs/init.sh 755 0 0
在核心編譯完成之後,執行 "usr/gen_init_cpio"就可以得到有關上面檔案格式的訊息。
配置檔案的優點就是無須 root 就可以在新的檔案中設定權限或新建設備節點。(注意
,上面的配置檔案會在 linux-2.6.* 目錄下的 "initramfs"目錄中,找名為
"init.sh" 和 "busybox"的兩個檔案。詳情說看
Documentation/early-userspace/README。)
核心並不倚賴於外部的 cpio 工具。如果你使用的是目錄,而不是配置檔案,核心的
Kbuild架構會根據該目錄生成一配置檔案(usr/Makefile 調用
scripts/gen_initramfs_list.sh),然後使用該配置檔案對目錄進行處理(傳給由
usr/gen_init_cpio.c 生成的 usr/gen_init_cpio)。核心編譯期的 cpio 生成代碼和
核心啟動階段的提取代碼都是完全獨立的。
當你在核心編譯時,要把你自己生成一新的,或者是已準備好的 cpio 檔案傳給核心
的時候,你可能就要安裝 cpio 工具了。(不用配置檔案或目錄的模式)
下面的命令就是提取一 cpio 映像到其相應的組件檔案中去(即可以用上的檔案,也可
以用核心的Kbuild)︰
cpio -i -d -H newc -F initramfs_data.cpio --no-absolute-filenames
下面的 shell 腳本能生成一 prebuilt cpio 檔案,你可以在與上面配置檔案相同的
位置使用︰
#!/bin/sh
# Copyright 2006 Rob Landley <rob@landley.net> and TimeSys Corporation.
# Licensed under GPL version 2
if [ $# -ne 2 ]
then
echo "usage:mkinitramfs directory imagename.cpio.gz"
exit 1
fi
if [ -d "$1" ]
then
echo "creating $2 from $1"
(cd "$1"; find . | cpio -o -H newc | gzip ) > "$2"
else
echo "First argument must be a directory"
exit 1
fi
注意︰cpio 的幫助手冊有一些錯誤的建議,如果你按這些建議去作,會破壞你的
initramfs 檔案。 手冊中這樣的話,"一種典型的生成檔案名清單的方法就是用
find 命令;你應該用 -depth 選項來最小化因目錄是不可寫的或是不可查詢而帶來的
權限問題"。當生成 initramfs.cpio.gz 映像時,不要這樣作,因為這它不起作用。
Linux 核心的 cpio 提取代碼是不會在不存在的目錄中新建檔案的,所以在新建檔案之
前,要先新建那些檔案所在的目錄。上面的腳本就是這種正確的順序執行的。
initramfs 映像外部︰------------------
如果核心打開的 initrd 支援,外部的 cpio.gz 檔案將代替 initrd 傳給一 2.6 核心
。此時,核心會自動檢測其類型(initramfs,不是 initrd),然後在執行 /init 之前
將該 cpio 檔案解壓到 rootfs 中。
這樣的話,initramfs(沒 ramdisk 區塊設備)在記憶體的使用上是有一定的效率優勢的,但
是卻要把 initrd 分別打包(如果你要用非 GPL 代碼在 initramfs中,這是個好消息,
因為這些代碼是不會合併到遵守 GPL 的 Linux 核心中去的)。
該映像同時也是對核心內部的 initramfs 映像的一個補充。在外部映像中的檔案會覆
蓋掉所有與之相衝突的在內部映像中的檔案。有些發行版本也傾向於使用一個特殊的
initramfs 映像來自定義其核心,而不是重新編譯。
initramfs 的內容︰------------------
一 initramfs 檔案 對於Linux來說,是一完全獨立的根檔案系統。如果你還不是很明
白什麼是共享庫、設備和路徑,那你就需要一個小型的根檔案系統並營運它,這裡有
此相關資料︰
http://www.tldp.org/HOWTO/Bootdisk-HOWTO/
http://www.tldp.org/HOWTO/From-PowerUp-To-Bash-Prompt-HOWTO.html
http://www.linuxfromscratch.org/lfs/view/stable/
包 "klibc"(http://www.kernel.org/pub/linux/libs/klibc)就是被設計用來靜態聯接
早期用戶空間代碼的小型 C 庫,並有一些相關的工具。其遵守的是 BSD 協議。
我用的是 uClibc (http://www.uclibc.org) 和 我自己的busybox
(http://www.busybox.net),分別遵守 LGPL 和 GPL 。(一獨立的 initramfs 包將包
含在 busybox 1.3中)
理論上你是可以用 glibc 的,但它對於小型嵌入式設備並不是很適合。(一個
"hello world" 程式聯接 glibc 後大於400K,而用 uClibc 只有7K。同樣要注意的是,
當 glibc 做名字解析要用打開共享庫的模式打開 libnss,即使其它所需要的庫都是靜
態聯接。)
一個好的開始就是讓 initramfs 把一靜態聯接的"hello world"的程式當作 init 執行
,並在類比器 qemu (www.qemu.org)或 在用戶模式Linux下進行測試,就像這樣︰
cat > hello.c << EOF
#include <stdio.h>
#include <unistd.h>
int main(int argc,chr *argv[])
{
printf("Hello world!\n");
sleep(999999999);
}
EOF
gcc -static hello.c -o init
echo init | cpio -o -H newc | gzip > test.cpio.gz
# Testing external initramfs using the initrd loading mechanism.
qemu -kernel /boot/vmlinuz -initrd test.cpio.gz /dev/zero
當調試一普通的根檔案系統時,有一很好的模式是就使用啟動選項 "init=/bin/sh"。
替代 initramfs 可以用 "rdinit=/bin/sh",這也是很有用的。
為什麼是 cpio 而不是 tar?-------------------------
這決定是在 2001年的12月決定的。討論開始在︰
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1538.html
然後是第二個(主要是 tar VS. cpio ),開始在︰
http://www.uwsg.iu.edu/hypermail/linux/kerel/0112.2/1587.html
這裡有一快速但混亂的總結(這並不是用來代替上面的那個網頁)︰
1) cpio 是一個標準。它的歷史非常的長(從 AT&T 時期就開始了),並且已經在Linux
上得到了廣泛的應用(包括 RPM,Red Hat's 設備驅動盤)。這裡有一篇寫於1996年的關
於它的 Linux Journal 文章︰
http://www.linuxjournal.com/article/1213
道統的 cpio 命令行要求相當可怕的命令行參數,所以就不像 tar 那樣的流行。並
且它並沒有提及壓縮檔案的格式,也沒有提可以替代它的工具,比如︰
http://freshmeat.net/projects/afio/
2) 核心所選擇的 cpio 檔案格式要比眾多的 tar 檔案格式要更加的簡單,清晰(因此
也就更容易建立和掃描)。完整的 initramfs 檔案格式在 buffer-format.txt中,檔案
的建立是由 usr/gen_init_cpio.c 完成,檔案的提取 init/initramfs.c 完成。三個
檔案加起來小於26K。
3) GNU 項目對 tar 標準化類似於 Windows 平台上對 zip 標準化。但Linux 不是這兩
者中任一者的一部分,所以可以自由的做出自己的技術決定。
4) 因為這是個核心內部格式,它可以被認為是比較新的。無論如何,核心使用自己的
工具去新建和提取該格式的訊息。使用一個已存在的標準會更好,但這不是必須的。
5) Al Viro 所作的決定 (原話︰ tar 是實在是太醜陋了,所以核心是不會支援它的。)︰
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1540.html
解譯如下︰
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1550.html
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1638.html
並且,還有最重要的東西,initramfs 的代碼設計與實現。
未來的方向:-----------
現下(2.6.16),initramfs 經常被編譯進核心,但一般不使用。核心要向後兼容遺留的
啟動代碼,而這些代碼只有在 initramfs 不包含 /init 程式時才使用。所依靠的遺
留代碼要確保無縫的轉換並允許早期啟動功能漸漸的移動到"early userspace"(比如,
initramfs)。
移動到早期用戶空間是有必要的,因為查找和掛載一真實的根設備是很複雜的。根分區
可能擴展成多個設備(raid 或 分開的日誌)。可以是在網路上(要有 dhcp,設定一特殊的
mac 位址,登錄到一個伺服器上,等等)。也可以是可移動設備,採用動態分發
major/minor 數字的模式,如果要永久註冊的話,需要使用 udev 來實現查找。還可以
是壓縮的,加密的,寫時拷貝的,loopback 掛載的,不規則分區的,等等等等。
此種複雜程度(很明顯,這包括的策略),應該在用戶空間處理。klibc 和
busybox/uClibc 都是工作在這種簡單的 initramfs 上 ,然後打起包放入核心Kbuild中
去的。
klibc 包已經被 Andrew Morton 的 2.6.17-mm 所接受。當前核心中的早期啟動代碼
(分區檢測等)有可能會被整合進一預設的 initramfs 中,由核心的Kbuild自動建立和
使用。

. MarkS  個人見解! rootfs是所謂的file system, 而squash則是file format; 意思就像是說,file system就像是說大家只能在遊樂園區內遊玩,不可超出此範圍, 而file format則是指明說每個遊樂設施(/tmp, /lib, /sbin, ...etc.) 有它使用遊玩的方法就是了! 要是更不懂,,就好比水上設施與陸上設施! 或者用另外一種方式了解它, rootfs指的是剛mount於Embeded於user space的root file system, 而此root file system使用的格式是squash.

MarkS  個人見解關於上述的file type可在Embedded System 中,透過mount的指令查詢你所mount的file type (Ex: ramfs, jffs2, tmpfs).

MarkS  個人見解!最近有研究一下自己所移植的rc檔案,才了解原來使用的方式與作法.
 Q&A : 透過u-boot 的參數設定, launch rootfs=/etc/init且為squash的root file system格式, Kernel執行起來時, 把rootfs 建立且來後, 再使用ramfs的方式把/dev/ram掛載(mount)於/dev上, 重新建立所有相關node與mount使用的folder. (自己所使用的系統非使用busybox的mdev)

No comments:

Post a Comment