ramfs, rootfs 和 initramfs

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


什麼是 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自動建立和
使用。

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()