轉載:http://www.oschina.net/question/129540_116839
在當前的嵌入式操作系統開發中,Linux 操作系統通常被壓縮成 Image 后存放在 Flash 設備中。在系統啟動過程中,這些 Image 被直接掛載到根文件系統, 然而這時的根文件系統是只讀的, 用戶不能在這個文件系統中進行任何寫的操作。 如果把 Image 解壓后直接拷貝到內存中,也可以實現寫的功能,但是嵌入式系統一直存在內存大小方面的限制,所以將整個 Linux 系統拷入內存是不可取的。 本文將介紹一種直接掛載 Image 到根目錄下,同時實現文件系統可讀寫的功能。
嵌入式 Linux 啟動過程
本文所描述的的 Linux Image 由 BootLoader、kernel、initrd、rootfs 組成,它們共同存在於一個可以啟動的存儲設備中(本文以 USB 為例)。組成架構如下:
各個模塊的作用如下:
- Boot Loader:由 BIOS 加載,用於將后續的 Kernel 和 initrd 的裝載到內存中
- kernel:為 initrd 運行提供基礎的運行環境
- initrd:檢測並加載各種驅動程序
- rootfs:根文件系統,用戶的各種操作都是基於這個被最后加載的文件系統
其調用順序是 Boot Loader->kernel->initrd->rootfs。
當機器上電時首先 BIOS 會啟動,然后裝載 USB 設備中的 Boot Loader、kernel,、nitrd 到內存中,由於這些文件大小總和小於 10M,所以我們直接拷貝到內存中再執行不會有問題。
最后要加載的 rootfs 是用戶最終進行讀寫操作的文件系統。
- 在非嵌入式系統中,這部分文件通常儲存在可直接讀寫的硬盤上,因此直接掛載到根目錄后(例如:mount /dev/sda1 /mnt)就可以進行讀寫操作。
- 在嵌入式系統中,它是一個壓縮的文件系統,大小通常是好幾百兆,解壓后的大小都超過 1G,如果直接 mount 到系統目錄,那么系統目錄是只讀的,不可進行寫入操作。而如果把它加壓到內存中可以實現讀寫的操作,但是這么大的文件直接解壓到內存中對於嵌入式設備來說 是不可接受的。因此我們需要找到一種不拷貝 rootfs 到內存中,同時又可以對最終的根文件系統進行讀寫的方法。
只讀式壓縮文件系統介紹
在嵌入式的環境之下,內存和外存資源都需要節約使用。如果使用 RAMDISK(把內存當作 disk)方式來使用文件系統,那么在系統運行之后,首先要把外存 (Flash) 上的映像文件解壓縮到內存中,構造起 RAMDISK 環境,才可以開始運行程序。但是它也有很致命的弱點。在正常情況下,同樣的代碼不僅在外存中占據了空間 ( 以壓縮后的形式存在 ),而且還在內存中占用了更大的空間 ( 以解壓縮之后的形式存在 ),這違背了嵌入式環境下盡量節省資源的要求。以下兩種方案的誕生就是為了解決這個問題:
CramFS
CramFS 文件系統是專門針對閃存設計的只讀壓縮的文件系統,它並不需要一次性地將文件系統中的所有內容都解壓縮到內存之中,而只是在系統需要訪問某個位置的數據的 時侯,馬上計算出該數據在 CramFS 中的位置,將其實時地解壓縮到內存之中,然后通過對內存的訪問來獲取文件系統中需要讀取的數據。CramFS 中的解壓縮以及解壓縮之后的內存中數據存放位置都是由 CramFS 文件系統本身進行維護的,用戶並不需要了解具體的實現過程,因此這種方式增強了透明度,對開發人員來說,既方便,又節省了存儲空間。
SquashFS
SquashFS 也是一個只讀的文件系統,它可以將整個文件系統壓縮在一起,存放在某個設備,某個分區或者普通的文件中。如果您將其壓縮到一個設備中,那么您可以將其直接 mount 起來使用,而如果它僅僅是個文件的話,您可以將其當為一個 loopback 設備使用。
本文主要介紹基於 SquashFS 的可讀寫文件系統構建。
Squash 壓縮文件系統的創建
步驟 1:創建空的根文件系統,文件系統的大小為 65536 × 24000/1024/1024=1.5G。接下來我們會在這個空的根文件系統中存放文件。
mke2fs: 將新創建的 rootfs 格式化為 Linux 可識別的文件系統
清單 1. 創建空的根文件系統
1 |
dd if =/dev/zero of=rootfs bs=65536 count=24000 |
2 |
mke2fs -F rootfs |
步驟 2:掛載空的根文件系統,將 1.5G 的文件系統掛載在 mnt 目錄下,然后通過 mnt 目錄將內容寫入根文件系統。
清單 2. 掛載空的根文件系統
1 |
mkdir mnt |
2 |
mount rootfs mnt -o loop |
步驟 3:拷貝根文件目錄的內容到文件系統
清單 3. 拷貝根文件目錄統
1 |
cp -rp yourRootDir mnt |
2 |
umount mnt |
拷貝完后根文件系統的內容,如圖 2 所示:
圖 2. 根文件系統內容
步驟 4:完成根文件系統的創建,這時的 rootfs 沒有被壓縮,接下來我們用工具將其壓縮成 Squash 格式的文件系統
清單 4. 創建根文件系統
1 |
mkdir squashfs- dir |
2 |
mv rootfs squshfs- dir |
3 |
mksquashfs-4.1 squashfs- dir squashRootfs |
到這里我們就完成了 Squash 壓縮文件系統的創建。接下來我們將討論如何在 Linux 啟動的過程中加載這個壓縮文件系統。
加載壓縮文件系統所使用的工具
在加載壓縮文件系統之前,我們需要確定您的 Linux 內核支持這種文件系統。 Device mapper 是 Linux 2.6 內核中提供的一種從邏輯設備到物理設備的映射框架機制,在該機制下,用戶可以很方便的根據自己的需要制定實現存儲資源的管理策略。
確保在 initrd 中已經集成“device-mapper”
加載 Squash 壓縮文件系統
可讀寫文件系統原理如圖 3 所示:
squashRootfs 里面存儲了我們原始的根文件系統,我們在根文件系統中所有的寫操作會直接寫入 cowfile.out(cow:copy-on-write), 當我們讀取根文件系統時,如果讀取的內容沒有變化,將直接從 squashRootfs 中讀取,如果讀取的內容被更新過,將從 cowfile.out 中讀取。cowfile.out 文件的大小一般要比 squshRootfs 小,如果 cowfile.out 被寫滿,根文件系統的讀寫操作將會出錯,因此有必要給 cowfile.out 設置一個合理的大小以防止被寫滿。
由於 rootfs 是被 initrd 加載的,因此我們需要在 initrd 里面加入裝載 rootfs 的代碼。initrd 整個的執行過程是調用 /sbin/init 這個腳本文件,因此我們在這個腳本的最后加入以下代碼邏輯即可。
3. 將 squashRootfs 通過 loop 設備的形式掛載在目錄 /realroot/mnt/ 下
4. 將 /realroot/mnt/rootfs 設置為 loop 設備,並和 /dev/loop1 綁定
5. "|"之前的部分是構建 dmsetup 的參數,其中 $(blockdev --getsize /dev/loop1) 表示創建鏡像文件的大小,/dev/loop1 /dev/loop2 表示鏡像文件是以 /realroot/mnt/rootfs 和 /realroot/mnt/cowfile.out 為藍本進行創建的(在前面的操作中 loop1 和 loop2 分別進行了綁定操作)
清單 5. 構建可讀寫文件系統腳本
01 |
# 加載驅動 ,參見注釋 1 modprobe dm-mirror |
02 |
03 |
# 創建設備 ,參見注釋 2 mknod /dev/zero c 1 5 |
04 |
mknod /dev/loop0 b 7 0 |
05 |
mknod /dev/loop1 b 7 1 |
06 |
mknod /dev/loop2 b 7 2 |
07 |
mkdir /dev/cow |
08 |
mknod /dev/cow/ctl b 241 255 |
09 |
mknod /dev/cow/0 b 241 0 |
10 |
11 |
# 掛載 squash 根文件系統,掛載完后您可以在 /realroot/mnt/ 下找到 rootfs 文件 ,參見注釋 3 mount /realroot/mnt/squashRootfs /realroot/mnt/ -o loop |
12 |
13 |
# 設置 rootfs 為 loop 設備 ,參見注釋 4 losetup /dev/loop1 /realroot/mnt/rootfs |
14 |
15 |
# 創建 cowfile.out 並掛載為 loop 設備,我們將來的寫操作都會寫入 cowfile.out |
16 |
dd if =/dev/zero of=/realroot/mnt/cowfile.out bs=2K count=137500 |
17 |
losetup /dev/loop2 /realroot/mnt/cowfile.out |
18 |
19 |
# 將 /realroot/mnt/rootfs 和 /realroot/mnt/cowfile.out 結合起來創建一個邏輯根文件設備, |
20 |
# 設備文件為 /dev/mapper/root_fs,,參見注釋 5 echo "0 $(blockdev --getsize /dev/loop1) snapshot /dev/loop1 /dev/loop2 p 64" | |
21 |
dmsetup create root_fs |
22 |
23 |
# 將上面創建的邏輯根文件設備 /dev/mapper/root_fs 掛載就可以看到一個可讀寫的根文件系統 |
24 |
mount /dev/mapper/root_fs /realroot/mnt/Image |
25 |
26 |
# 切換到最終可讀寫的根文件系統 |
27 |
cd /realroot/mnt/Image |
28 |
chroot ./sbin/init -i |
加下來您就可以看到 rootfs 的所有內容,如圖 4 所示:
圖 4. 被掛載的根文件系統內容
還可以在這個文件系統中進行寫操作,如圖 5 所示:
最重要的是 rootfs 沒有被拷貝到內存中。
結束語
由於篇幅的限制,本文只給出了基本的描述。希望有更一步了解的讀者可以通過對以下 linux 命令的學習來深入了解。
構建 Squash 壓縮文件系統構所使用的主要命令:
- mksquashfs:創建 Squash 壓縮文件系統
使用 Squash 壓縮文件系統構所使用的主要命令:
- mknod:創建 Squash 壓縮文件系統
- losetup:設置並控制 Loop 設備
- chroot:改變根目錄
- dmsetup:低水平邏輯卷管理