Openwrt學習筆記(二)——Flash Layout and file system

在大多數系統中,閃存不像RAM一樣可以直接執行指令,所以閃存中的數據和指令需要拷貝到RAM中執行,比如存放在flash中的kernel需要bootload的幫忙,將kernel拷貝到RAM裏面才能運行。

大多數router都是沒有硬盤的,它使用閃存來完成相應的存儲功能(包括存儲固件以及系統數據),這種非易失性的閃存可以避免掉電丟失的問題。

我們常接觸的閃存主要有兩種:NOR flash 和NAND flash。如果閃存芯片和SOC直接連接並且又linux直接尋址,我們將它稱作raw flash; 如果在SOC和flash芯片間有多加一個外部控制芯片,我們稱它作“FTL (Flash Translation Layer) flash”。我們大多數嵌入式系統都是用raw flash,很少用SSD和USB這樣的FTL flash。

老的router上用nor flash多,但是新的router上很多開始用nand flash和emmc等;一般nor flash有4/8/16/32M,相對nand flash來說存儲量較小,價格更貴,但是它的優點是不會壞塊, nand flash一般都是32~256M或者更大,但是它容易壞塊,openwrt系統中squashFS的一個致命弱點是不能很好的處理壞塊問題,一旦壞塊就好重新燒寫系統了,一個可選擇的方式就是使用UBIFS文件系統。當然當前也有另外一種方案,就是使用emmc,壞塊問題交由外部處理器完成。

There is a generic problem when running SquashFS on NAND: The issue is that SquashFS has no bad block management at all and requires all blocks on order; but for proper NAND bad block management you also need to be able to skip bad blocks and occasionally relocate blocks (see squashfs and NAND flash). That's why raw SquashFS is a bad idea on NAND (it works if you use a FTL like UBIFS).


如果使用的是raw flash,openwrt將會將這個尋址空間看作MTD,我們可以通過在bootload或者kernel中對這塊存儲空間進行分區的劃分,我們通常描述爲"partitionkernel starts at offsetx and ends at offsety". 一般我們都會對每個分區進行命名,方便更好的操作。

一般我們都會對flash進行如下分區的劃分,從左到右分別存放bootloader,bootloader相關參數,kernel,文件系統,用戶數據等.



下面是一些router的flash劃分狀況:


layer0 表示這個flash一共有8M

layer1表示這個flash總共劃分了4個部分,分別存放uboot, uboot-env, factory參數(MAC,PIN)和FW;命名爲MTD0,1,2,3

layer2表示firmware又分成兩個部分,分別是kernel和rootfs,命名爲MTD4,5
layer3表示rootfs分成兩個部分/dev/root和rootfs_data

需要注意的是,mtd3和mtd4的起始位置是一樣的,只是大小不一樣。openwrt將文件系統分成兩類,/dev/root分區使用squash FS,rootfs_data分區使用JFFS2;我們知道squash FS是隻讀不可寫的,但是JFFS2可讀可寫,所以我們修改系統文件以後,一般保存在rootfs_data分區。
當前系統flash分區的劃分信息可以在開機log中找到,也可以在/proc/mtd中查看:



文件系統

在當前的嵌入式操作系統開發中,Linux 操作系統通常被壓縮成 Image 後存放在 Flash 設備中。在系統啓動過程中,這些 Image 被直接掛載到根文件系統, 然而這時的根文件系統是隻讀的, 用戶不能在這個文件系統中進行任何寫的操作。 如果把 Image 解壓後直接拷貝到內存中,也可以實現寫的功能,但是嵌入式系統一直存在內存大小方面的限制,所以將整個 Linux 系統拷入內存是不可取的。

在非嵌入式系統中,文件系統通常儲存在可直接讀寫的硬盤上,因此直接掛載到根目錄後(例如:mount /dev/sda1 /mnt)就可以進行讀寫操作。

在嵌入式系統中,它存放的是一個壓縮的文件系統,大小通常是好幾百兆,解壓後的大小都超過 1G,如果直接 mount 到系統目錄,那麼系統目錄是隻讀的,不可進行寫入操作。而如果把它加壓到內存中可以實現讀寫的操作,但是這麼大的文件直接解壓到內存中對於嵌入式設備來說是不可接受的。因此我們需要找到一種不拷貝 rootfs 到內存中,同時又可以對最終的根文件系統進行讀寫的方法。

在嵌入式的環境之下,內存和外存資源都需要節約使用。如果使用 RAMDISK(把內存當作 disk)方式來使用文件系統,那麼在系統運行之後,首先要把外存 (Flash) 上的映像文件解壓縮到內存中,構造起 RAMDISK 環境,纔可以開始運行程序。但是它也有很致命的弱點。在正常情況下,同樣的代碼不僅在外存中佔據了空間 ( 以壓縮後的形式存在 ),而且還在內存中佔用了更大的空間 ( 以解壓縮之後的形式存在 ),這違背了嵌入式環境下儘量節省資源的要求。以下兩種方案的誕生就是爲了解決這個問題:

CramFS

CramFS 文件系統是專門針對閃存設計的只讀壓縮的文件系統,它並不需要一次性地將文件系統中的所有內容都解壓縮到內存之中,而只是在系統需要訪問某個位置的數據的時侯,馬上計算出該數據在 CramFS 中的位置,將其實時地解壓縮到內存之中,然後通過對內存的訪問來獲取文件系統中需要讀取的數據。CramFS 中的解壓縮以及解壓縮之後的內存中數據存放位置都是由 CramFS 文件系統本身進行維護的,用戶並不需要了解具體的實現過程,因此這種方式增強了透明度,對開發人員來說,既方便,又節省了存儲空間。

SquashFS

SquashFS 也是一個只讀的文件系統,它可以將整個文件系統壓縮在一起,存放在某個設備,某個分區或者普通的文件中。如果您將其壓縮到一個設備中,那麼您可以將其直接 mount 起來使用,而如果它僅僅是個文件的話,您可以將其當爲一個 loopback 設備使用。

更多信息請參考“SquashFS”和“CramFS”。


掛載點

對於使用openwrt的嵌入式系統來說,因爲硬件絕大多數採用flash,因此一般使用squashfs文件系統和jffs2文件系統。前者是隻讀的,後者是可寫的。我們一般會把jffs2 mount到某個目錄下。這樣就存在某些目錄如/bin是隻讀的(squashfs),某些目錄是可讀寫的(jffs2),這樣對文件的操作會依賴於文件系統的屬性和文件的路徑。    

openwrt使用了mini_fo文件系統,從用戶的使用角度看,會覺得整個文件系統都是可寫的。用戶可以任意修改、刪除、添加文件。這種文件系統可以認爲是在squashfs和jffs2的文件系統之上實現了一個符號鏈接,如果用戶讀取只讀文件,則鏈接到squashfs文件系統讀取,如果用戶對只讀文件進行了修改,則將修改的文件放到jffs2文件系統上,並修改鏈接。    如果用戶的系統不採用jffs2系統,openwrt會使用ramfs,依舊可以實現上面的功能,不過系統重啓後,修改會丟失。

“/”是整個文件系統,它由“/rom”和“/overlay”組成,前者是squashFS,後者是JFFS2,我們平常訪問文件的時候可以不用管這兩個文件,只要訪問“/”就可以了。

“/rom”目錄包含一些基本的文件,比如busybox, dropbear or iptables,也包括一些基本的配置文件,但是不包括kernel,哪些文件全都放在SquashFS分區,因此這些文件不能修改與刪除。但是因爲我們使用OverlayFS文件系統,所以overlay-whiteout-symlinks可以創建在JFFS2分區上。

“/overlay”是一個可寫的文件系統,它和“/rom”合併,同時在根目錄下創建一個惟一的/-tree。它可以記錄在router啓動以後所有文件的修改,比如配置文件的修改或者包的安裝。

每次系統訪問“/”目錄下的文件的時候,它首先會搜索“/overlay”文件系統,如果沒有找到,然後搜索“/rom”文件系統。因此,“/overlay”覆蓋在“/rom”文件系統上,從而創建出一種“/”根目錄可寫的效果,並且保證大多數內容是安全的,能夠快速的訪問“/rom”下面的內容。

如果需要刪除一個“/rom”系統中的文件,那麼系統就會在“/overlay”裏面創建一個文件入口(或理解爲一個透明軟鏈接),這個軟鏈接指向overlay-whiteout,而當我們訪問overlay-whiteout的時候,返回的結果就是“該文件不存在”。

需要區分的是“/tmp”文件系統,這個文件系統是運行在RAM中的,並不是在flash中,所以/tmp是可實時讀寫的,但是squashFS是一個高度壓縮的文件系統,存放在flash中,所以不能寫入,寫入的問題由JFFS2格式的overlay來解決,下面舉個例子:

1. 在flash中的rootfs分區有兩個文件系統,一個是squashFS(/dev/root),一個是JFFS2(rootfs_data,開機時創建)

2. 在文件系統掛載的時候會將“/rom”掛載到“/”, 將“/overlay”掛載到“/etc”目錄(當然我們也可以掛載到其他目錄,比如掛載到“/”就可以讓整個文件系統可讀寫),這時候可以理解爲給“/etc”目錄鋪上了一條通往jffs2文件系統的通道,這時“/etc”目錄下的文件就可讀可寫了,因爲jffs2文件系統是可寫的。

3. 假設/etc目錄下有一個文件b.conf和c.config,這時我們需要修改b.conf中的內容,那麼系統就會先到/overlay/etc下找有沒有b.conf,因爲一開始b.conf沒有改動過,所以在/overlay/etc下是找不到b.conf的。它接着會到/rom/etc目錄下找,找到以後就會提取到RAM緩存讓用戶修改,修改完以後就會將b.conf保存到/overlay/etc目錄下,並寫進jffs2文件系統中,這時在/overlay/etc下就多了一個b.conf。這樣,給我們的感覺就是,我們成功修改並寫入了/etc/b.conf文件;如果我們要修改/sbin下面的文件,因爲沒有到達/overlay文件系統的通道,所以是不能寫入並保存的,它會提示錯誤,給我們的感覺就是/sbin目錄不可寫。

4. 如果我們需要刪除一個文件c.config, 那麼系統就會在/overlay目錄下面創建一個c.config文件的軟鏈接,這軟鏈接指向“overlay whiteout”,有點像我們常用的/dev/null,在JFFS2文件系統中,只要該文件指向“overlay whiteout”就表示該文件不存在,下面是在/overlay下找文件的command:


總結:

在路由器的FLASH上,內核中所使用的驅動是MTD設備驅動。

MTD(Memory Technology Devices,內存技術設備)是用於訪問內存類設備(ROM、FLASH)的Linux驅動子系統。它的主要目的使FLASH類設備更加容易被訪問,爲此它在硬件和上層提供了一個抽象的接口,使得在操作系統下我們可以像操作硬盤一樣操作這個設備。仔細觀察過Linux啓動信息的朋友會看到這麼一段話:

[ 0.690000] 5 tp-link partitions found on MTD device spi0.0
[ 0.700000] Creating 5 MTD partitions on "spi0.0":
[ 0.700000] 0x000000000000-0x000000020000 : "u-boot"
[ 0.710000] 0x000000020000-0x00000012a290 : "kernel"
[ 0.730000] 0x00000012a290-0x0000007f0000 : "rootfs"
[ 0.760000] 0x000000300000-0x0000007f0000 : "rootfs_data"
[ 0.760000] 0x0000007f0000-0x000000800000 : "art"
[ 0.770000] 0x000000020000-0x0000007f0000 : "firmware"

這些信息表示當前系統識別到的FLASH分區。我們可以用電腦中的計算器計算一下,打開計算器,選擇科學型、十六進制,輸入名爲art的分區容量用(800000-7f0000)結果爲10000(十六進制),這個時候點擊十進制,系統會自動將結果轉換爲十進制,再除以1024結果爲64(K)表示這個分區容量爲64k。在openwrt的系統中現在對atheros方案實現了自動查找分區結尾。

上面的幾個分區,我來說明下(分區名稱、分區容量、分區作用):

  • "u-boot":128KB,設備初始化程序+引導程序代碼本身
  • "kernel" :1MB,存放系統內核的二進制代碼,按照x86下的講法是Raw分區,就是這裏只有內核的二進制,不存在文件系統。
  • "rootfs":6.7MB,完整的系統文件包含只讀和可寫
  • "rootfs_data":4.9MB,在rootfs中的可寫部分的位置
  • "art":64KB,EEPROM分區,在Atheros的方案中這個分區保存了無線的硬件參數
  • "firmware":7.9MB,完整的固件位置包含了除"u-boot"和"art"之外全部的內容

看的暈了? 這,我馬上畫個簡單的圖給大家看看:


這個是它的分區邏輯。請不要太在意這個地方,有點暈也沒關係,繼續往後面看,這個地方留着後邊慢慢理解。

在系統中,可以執行以下指令查看當前系統分區:

每個分區在flash中的位置是/dev/mtdblockX這樣的位置,比如你想把art分區裏的數據讀出來看看,那麼就執行:

然後執行hexdump -C /tmp/1就可以看到這個分區的內容了。

系統的文件結構

前面說過系統在第一次啓動的時候會格式化"可寫分區",這在邏輯上到底是啥關係呢?

  1. 首先uboot啓動了kernel完成之後,由kernel加載"ROM分區"(就是rootfs減去rootfs_data得到的那一塊分區)
  2. ROM分區採用的是Linux內核支持的squashFS文件系統(一種壓縮只讀文件系統),加載完畢後將其掛載到/rom目錄(同時也掛載爲根文件系統)。
  3. 系統將使用JFFS2文件系統格式化rootfs_data這部分並且將這部分掛載到/overlay目錄。
  4. 將/overlay透明掛載爲/分區。
  5. 將一部分內存掛載爲/tmp目錄。

這個時候大家一定有一個問題:到底根文件系統是哪個?這個是OpenWRT設計的一個優點,它採用了一種叫Overlay透明掛載技術,首先將/rom掛載爲/根文件,然後再用/overlay覆蓋在/之上,這樣,當你進行文件系統的變更,修改,所做的操作將在overlay中記錄。rom是不改變的。而最簡單的恢復出廠設置方法,即是刪除掉/overlay下所有文件。

但是有時候,我們希望我們的系統是可寫的,但是重開機以後希望上次的改動不要保留,恢復到原來的狀態,那麼應該有三種辦法:

1. 關機的時候刪除/overlay下面的所有內容(在reboot或reset命令裏面做)

2. 開機的時候刪除/overlay下面的所有內容(在/etc/init.d/boot腳本里面做,或者preinit中完成overlay文件系統掛載以後做)

3. 創建/tmp/root, 將root文件系統("/")掛載到 /tmp/root目錄下,然後將/overlay掛載到“/”,這樣就可以實現文件可讀寫,而且重開機以後修改的文件恢復原貌。因爲/tmp文件是可寫的,所以往/overlay中寫入的東西實際存放到了RAM中,重開機的時候就會不見,從而繼續從/rom中搜索。

後面將分析一下openwrt是怎麼開機掛載文件系統的。

root:/# mount
rootfs on / type rootfs (rw)
/dev/root on /rom type squashfs (ro,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
root on /tmp/root type tmpfs (rw,noatime,mode=755)
overlayfs:/tmp/root on / type overlayfs (rw,noatime,lowerdir=/,upperdir=/tmp/root)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600)
debugfs on /sys/kernel/debug type debugfs (rw,noatime)
root:/# df
Filesystem           1K-blocks      Used Available Use% Mounted on
rootfs                  253324        68    253256   0% /
/dev/root                12288     12288         0 100% /rom
tmpfs                   253324       504    252820   0% /tmp
root                    253324        68    253256   0% /tmp/root
overlayfs:/tmp/root     253324        68    253256   0% /
tmpfs                      512         0       512   0% /dev

下面這種掛載方式是重啓後文件修改還在的:

root@OpenWrt:/# mount
rootfs on / type rootfs (rw)
/dev/root on /rom type squashfs (ro,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
/dev/mmcblk0p16 on /overlay type ext4 (rw,noatime,data=ordered)
overlayfs:/overlay on / type overlayfs (rw,noatime,lowerdir=/,upperdir=/overlay)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600)
debugfs on /sys/kernel/debug type debugfs (rw,noatime)
root@OpenWrt:/# df
Filesystem           1K-blocks      Used Available Use% Mounted on
rootfs                 1257344      2144   1173280   0% /
/dev/root                14592     14592         0 100% /rom
tmpfs                   247992       332    247660   0% /tmp
/dev/mmcblk0p16        1257344      2144   1173280   0% /overlay
overlayfs:/overlay     1257344      2144   1173280   0% /
tmpfs                      512         0       512   0% /dev
root@OpenWrt:/# 


更多關於openwrt文件系統的介紹

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章