目錄
概述
本文記錄了根文件系統的一些知識點,Busybox 工具的使用和 最小根文件系統的制作。
概念
根文件系統是什么
根文件系統是特殊用途的文件系統,必須屬於某種文件系統格式。那么文件系統是用來干嘛的?
- 首先,存儲設備(塊設備,像硬盤、 flash 等) 是分塊(扇區)的,物理上底層去訪問存儲設備時是按照塊號(扇區號)來訪問的。這就很麻煩。
- 其次,文件系統是一些代碼,是一套軟件,這套軟件的功能就是對存儲設備的扇區進行管理,將這些扇區的訪問變成了對目錄和文件名的訪問。我們在上層按照特定的目錄和文件名去訪問一個文件時,文件系統會將這個目錄 + 文件名轉換成對扇區號的訪問。
- 最后,不同的文件系統的差異在於對這些扇區的管理策略和方法不同,如壞塊管理、 碎片管理。
根文件系統中有什么
- 最重要的就是 linuxrc
- dev 目錄下的設備文件。在 linux 中一切皆是文件,硬件設備被虛擬化成一個設備文件。
- sys 和 proc 目錄。可以為空但是必須有,和驅動有關。 屬於 linux 中的虛擬文件系統。
- etc 目錄。存放運行時配置文件。 /etc 目錄下的所有配置文件會直接或者間接的被 /linuxrc 所調用執行,完成操作系統的運行時配置。 etc 目錄是制作 rootfs 的關鍵。
- lib 目錄下放的是當前操作系統中的動態和靜態鏈接庫文件。
根文件系統的形式
-
鏡像文件形式
- 使用專用工具軟件制作的可供燒錄的鏡像文件
- 鏡像中包含了根文件系統中的所有文件
- 燒錄此鏡像類似於對相應分區格式化。
- 鏡像文件系統具有一定的格式,格式是內化的,跟文件名后綴是無關的。
-
文件夾形式
- 根文件系統其實就是一個包含特定內容的文件夾而已
- 根文件系統可由任何一個空文件夾添加必要文件構成而成
- 根文件系統的雛形就是在開發主機中構造的文件夾形成的
-
總結
-
鏡像文件形式的根文件系統主要目的是用來燒錄到塊設備上,設備上的內核啟動后去掛載它。鏡像文件形式的根文件系統是由文件夾形式的根文件系統使用專用的鏡像制作工具制作而成的。
-
最初在開發主機中隨便 mkdir 創建了一個空文件夾,然后向其中添加一些必要的文件(包括etc 目錄下的運行時配置文件、/bin 等目錄下的可執行程序、 /lib 目錄下的庫文件等···)后就形成了一個文件夾形式的 rootfs。 然后這個文件夾形式的 rootfs 可以被 kernel 通過 nfs 方式來遠程掛載使用,但是不能用來燒錄塊設備。我們為了將這個 rootfs 燒錄到塊設備中,於是用一些專用的軟件工具,將其制作成可供燒錄的一定格式的根文件系統鏡像。
-
文件夾形式的 rootfs 是沒有格式的,制作成鏡像后就有了一定的 rootfs 格式了, 格式是由我們的鏡像制作過程和制作工具來決定的。 每一種格式的鏡像制作工具的用法都不同。
-
Busybox 簡介
-
busybox是一個C語言寫出來的項目,里面包含了很多.c文件和.h文件。
-
busybox 這個程序開發出來就是為了在嵌入式環境下構建根文件系統(以下簡稱 rootfs)使用的,也就是說他就是專門開發的 init 進程應用程序。
-
busybox 為當前系統提供了一整套的 shell 命令程序集。譬如 vi、cd、mkdir、ls 等。在桌面版的 linux 發行版(譬如 ubuntu、redhat等)中 vi、cd、ls 等都是一個一個的單獨的應用程序。但是在嵌入式 linux 中,為了省事我們把 vi、cd 等所有常用的 shell 命令集合到一起構成了一個 shell 命令包,起名叫 busybox。
-
Busybox 在編寫過程中對文件大小進行了優化,並考慮了系統資源有限(比如內存等)的情況。與一般的 GNU 工具集動輒幾 M 的體積相比,動態鏈接的 Busybox 只有幾百 K,即使是采用靜態鏈接也只有1 M 左右。Busybox 按模塊設計,可以很容易地加入、去除某些命令,或增減命令的某些選項。
什么是 linuxrc
- linuxrc 是一個可執行的應用程序。 是應用層的,和內核源碼一點關系都沒有。
- linuxrc 如果是靜態編譯連接的那么直接可以運行;如果是動態編譯連接的那么我們還必須給他提供必要的庫文件才能運行。但是因為我們 linuxrc 這個程序是由內核直接調用執行的, 因此用戶沒有機會去導出庫文件的路徑,因此實際上 linuxrc 沒法動態連接,一般都是靜態連接的。
- linuxrc 執行時引出用戶界面。操作系統啟動后在一系列的自檢運行配置之后,最終會給用戶一個操作界面(cmdline 或 GUI),這個用戶操作界面就是由 /linuxrc 帶出來的。用戶界面等很多事並不是在 /linuxrc 程序中負責的,用戶界面有自己專門的應用程序,但是用戶界面的應用程序是直接或者間接的被 linuxrc 調用執行的。用戶界面程序和其他的應用程序就是進程 2、3、4 ···,這就是我們說的進程 1(init 進程,也就是 /linuxrc)是其他所有應用程序進程的祖宗進程。
- linuxrc 負責系統啟動后的配置。操作系統啟動起來后也不能直接用,要配置下。操作系統啟動后的應用層的配置(一般叫運行時配置,英文簡寫 etc),是為了讓我們的操作系統用起來更方便,更適合個人的愛好或者實用性。
VFS 簡介
-
VFS 是 linux 內核的一種設計理念、設計機制。全拼 vitrual file system,叫虛擬文件系統。
-
具體的一些文件系統如 FAT、 NTFS、 ext2、 ext3、 jffs2、 yaffs2、 ubi 等主要設計目的是為了管理塊設備(硬盤、 Nand···)
-
VFS 是借鑒了文件系統的設計理念(通過文件系統將底層難以管理的物理磁盤扇區式訪問,轉換成目錄 + 文件名的方式來訪問),將硬件設備的訪問也虛擬化成了對目錄 + 文件的訪問。所以有了 VFS 后我們可以通過設備文件(如/dev/mmcblk0p2) 的方式來訪問系統中的硬件設備。
-
VFS 將對硬件設備的訪問和對普通文件的訪問給接口統一化了(linux 中一切皆是文件)。VFS 成了一個隔離層,隔離了下層的不同文件系統的差異性,對上層應用提供一個統一的接口。
-
VFS 將不同文件系統和下層硬件設備(塊設備)驅動之間的細節也給屏蔽了。不同類型的文件系統在本身設計時是 不用考慮各種不同的硬件設備的具體操作差異的,這里有一個類似於 VFS 的設計理念。
-
VFS 機制和 rootfs 掛載與其他文件系統的掛載都是有關聯的。內核中有一些 sys proc 這種虛擬文件系統,也是和 VFS 機制有關。 /dev/目錄下的設備文件都和 VFS 有關。
Busybox 工具
Busybox 目錄結構
| 目錄 | |
|---|---|
| applets | 主要是實現applets框架的文件 |
| applets_sh | 一些有用的腳本,例如:dos2unix、unix2dos等 |
| archival | 與壓縮有關的命令源文件,例如:bzip2、gzip等 |
| configs | 自帶的一些默認配置文件 |
| console-tools | 與控制台相關的一些命令,例如:setconsole |
| coreutils | 常用的核心命令,例如:cat、rm等 |
| editors | 常用的編輯命令,例如:vi、diff等 |
| findutils | 用於查找的命令,例如:find、grep等 |
| init | init進程的實現源文件 |
| networking | 與網絡相關的命令,例如:telnetl、arp等 |
| shell | 與shell相關的實現,例如:ash、msh等 |
| util-linux | Linux下常用的命令,主要是與文件系統相關的,例如:mkfs_ext2等 |
Menuconfig 選項說明
Busybox Settings ---> # BusyBox的通用配置,一般采用默認值即可。
Archival Utilities ---> # 壓縮、解壓縮相關工具。
Coreutils ---> # 最基本的命令,如cat、cp、ls等。
Console Utilities ---> # 控制台相關命令。
Debian Utilities ---> # Debian操作系統相關命令。
Editors ---> # 編輯工具,如vi、awk、sed等。
Finding Utilities ---> # 查找工具,如find、grep、xargs。
Init Utilities ---> # BusyBox init相關命令。
Login/Password Management Utilities ---> # 登陸、用戶賬號/密碼等方面的命令。
Linux Ext2 FS Progs ---> # ext2文件系統的一些工具。
Linux Module Utilities ---> # 加載/卸載模塊等相關的命令。
Linux System Utilities ---> # 一些系統命令。
Miscellaneous Utilities ---> # 一些不好分類的命令,如crond、crontab。
Networking Utilities ---> # 網絡相關的命令和工具。
Print Utilities ---> # print spool服務及相關工具。
Mail Utilities ---> # mail相關命令。
Process Utilities ---> # 進程相關命令,如ps、kill等。
Runit Utilities ---> # runit程序。
Shells ---> # shell程序。
System Logging Utilities ---> # 系統日志相關工具,如syslogd、klogd。
編譯 BusyBox
-
解壓 Busybox 源碼后,進入 Busybox 目錄,打開圖像界面配置菜單:
make menuconfig -
進入配置菜單后,進行相關配置。我們可以靜態或者動態編譯Busybox,選擇動態編譯,使得Busybox可執行文件更小,選項開關如下:
[ ] Build BusyBox as a static binary (no shared libs) -
經過上訴步驟之后,這個時候選擇配置界面的 Exit 退出,保存剛剛的配置,之后就可以看到在源代碼目錄下多了一個 .config 文件。.config 配置文件里面的內容記錄了我們剛剛選中的功能。如下所示:
CONFIG_PLATFORM_LINUX=y每一個都是名值對的形式,名稱是一個環境變量,后面的值如果為 Y 代表選中,注釋行代表裁減掉的功能。
-
開始編譯並安裝
make make install生成的文件將默認安裝到目錄 _install。
使用 BusyBox 進行調試
BusyBox 編譯成功后,在 目錄 _install 下可以看到生成的 文件,我們可以繼續添加文件,做成一個最小根文件系統。或是直接操作 Busybox 這個可執行文件,用法如下:
./BusyBox 命令 [參數]
其中 命令 是編譯 BusyBox 時選中的命令,用法同 Linux 下的命令,只是加上 ./BusyBox 。比如我們在編譯時選中的 flashcp 命令,編譯成功后,我們把 BusyBox 放到開發板中,就可以直接使用新加入的 flashcp 命令了,而不用重新編譯和燒錄根文件系統到開發板中:
./BusyBox flashcp /tmp/nfs/usr.sqsh4 /dev/mtd7
完善根文件系統
添加 inittab 文件
inittab 被 /linuxrc(也就是 busybox)執行時所調用而起作用。存放在/etc 目錄下,屬於一個運行時配置文件,是文本格式的。實際工作的時候 busybox 會(按照一定的格式)解析這個 inittab文本文件,然后根據解析的內容來決定要怎么工作。inittab 的格式在 busybox 中定義的。
將 inittab 文件放到到我們制作的 rootfs 的根目錄下的 /etc/ 目錄下,啟動並且掛載 rootfs 進入了控制台命令行。當前制作的最小 rootfs 就成功了。典型的 inittab :
#/etc/inittab # 井號是注釋
::sysinit:/etc/init.d/rcS # 系統啟動以后運行 /etc/init.d/rcS 這個腳本文件
console::askfirst:-/bin/sh # 將 console 作為控制台終端,也就是 ttymxc0。
::restart:/sbin/init # 重啟的話運行/sbin/init。
::ctrlaltdel:-/sbin/reboot # 按下 ctrl+alt+del 組合鍵的話就運行 /sbin/reboot(重啟系統)
::shutdown:/bin/umount -a -r # 關機的時候執行 /bin/umount,也就是卸載各個文件系統
::shutdown:/sbin/swapoff -a # 關機的時候執行 /sbin/swapoff,也就是關閉交換分區。
-
冒號在里面是分隔符,分隔開各個部分。以行為單位的,行與行之間沒有關聯,每一行的配置項都是由 3 個冒號分隔開的 4 個配置值共同確定的( id : runlevels : action : process )。有些配置值可以空缺,空缺后冒號不能空缺。
-
action 是一個條件/狀態,process 是一個可被執行的程序的 pathname。合起來的意思就是:當滿足 action 的條件時就會執行 process 這個程序:busybox 最終會進入一個死循環,在這個死循環中去反復檢查是否滿足各個 action 的條件,如果某個 action 的條件滿足就會去執行對應的 process。
動作 描述 sysinit 在系統初始化的時候 process 才會執行一次。 respawn 當 process 終止以后馬上啟動一個新的 askfirst 和 respawn 類似,在運行 process 之前在控制台上顯示“Please press Enter to activate this console.”。只要用戶按下“Enter”鍵以后才會執行 process。 wait 告訴 init,要等待相應的進程執行完以后才能繼續執行。 once 僅執行一次,而且不會等待 process 執行完成。 restart 當 init 重啟的時候才會執行 procee。 ctrlaltdel 當按下 ctrl+alt+del 組合鍵才會執行 process。 shutdown 關機的時候執行 process。
- inittab 的解析是在 busybox/init/init.c/init_main 函數中。執行邏輯是:先通過 parse_inittab 函數解析 /etc/inittab(解析的重點是將 inittab 中的各個 action 和 process 解析出來),然后后面先直接執行 sysinit 和 wait 和 once(只執行一遍),然后在 while(1) 死循環中去執行 respwan 和 askfirst。
添加登陸系統
-
之前添加了 /bin/hostname 在 /etc/sysconfig/HOSTNAME 文件中定義了一個 hostname,實際效果是:命令行下 hostname 命令查到的 host 名字正確。但是命令行的提示符是沒有顯示的。這個問題的解決就要靠profile 文件。將提供的 profile 文件放入 /etc/ 目錄下即可。添加之后命令行提示符前面顯示:
[@aston210 ]# -
profile 文件也是被 busybox(init 進程)自動調用的,所以是認名字的。
-
在 intttab 中有一個配置項 ::askfirst:-/bin/sh,這個配置項作用就是當系統啟動后就去執行 /bin/sh,執行這個就會出現命令行。因此我們這樣的安排就會直接進入命令行而不會出現登錄界面。我們要出現登錄界面,就不能直接執行/bin/sh,而應該執行一個負責出現登錄界面並且負責管理用戶名和密碼的一個程序,busybox 中也集成了這個程序(就是/bin/login 和/sbin/gettty),因此我們要在 inittab 中用 /bin/login 或者 /sbin/getty 去替代 /bin/sh:
: :askfirst : -/bin/sh # 啟動后按回車不用登陸直接進入命令行 #: : respawn : -/bin/sh # 可去除按回車的步驟。自己進入命令行 : : sysinit : /bin/login # 啟動后進入用戶登陸程序,
-
passwd 文件中存儲的是用戶的密碼設置,shadow 文件中存儲的是加密后的密碼。我們直接復制 ubuntu 系統中的/etc/passwd 和/etc/shadow 文件到當前制作的 rootfs 目錄下,然后再做修改即可。修改方法:只保留 root 那一行,如
root:x:0:0:root:/root:/bin/sh # passwd 文件,最后是 sh root :tyLxf271Ytqok:0:0:99999:7::: # shadow 文件:123456 的密文 -
重置密碼
- ubuntu 中,普通用戶登陸后可以使用 su passwd root 給 root 用戶設置密碼。
- busybox 中沒有普通用戶,直接使用 passwd root 給 root 用戶設置密碼。
- 平時有時候我們忘記了自己的操作系統的密碼,怎么辦?有一種解決方法就是用其他系統( WindowsPE 系統或者 ubuntu 的單用戶模式等···)來引導啟動,啟動后掛載到我們的硬盤上,然后找到 /etc/shadow 文件,去掉密文密碼后保存。然后再重啟系統后密碼就沒了。
-
getty 實戰
-
inittab 中最常見的用於登錄的程序不是 /bin/login,反而是 /sbin/getty。在 busybox 中這兩個是一樣的,都是 busybox 的符號鏈接而已,因此不用嚴格區分這兩個。
-
我們可以在 inittab 中用 getty 替換 login 程序來實現同樣的效果。
s3c2410_serial2::respawn:/sbin/getty -L s3c2410..... 115200 vt100
-
添加 rcS 文件
精簡 rcS 文件示例
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATH # 導出上面這些環境變量
mount -a # 掛載所有的文件系統,需存在/etc/fstab 文件
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug # 這兩行跟/dev 有關
mdev -s
/bin/hostname -F /etc/sysconfig/HOSTNAME # 設置主機名為 HOSTNAME 寫的名字
/etc/init.d/rcS 文件是 linux 的運行時配置文件中最重要的一個,其他的一些配置都是由這個文件引出來的。這個文件可以很復雜也可很簡單,里面可以有很多的配置項。可以通過修改 rcS 實現開機自啟動,例如添加:
runlevel=S # 表示將系統設置為單用戶模式
umask 022 # 設置 linux 系統的 umask 值
ifconfig eth0 192.168.1.10 # 設置 IP,默認為 bootcmd 設置的 IP 地址
-
正式產品中的 rcS 是一個引入,不是真正干活的。真正干活的/etc/init.d/中的其他腳本。
-
創建好文件/etc/init.d/rcS 以后一定要給其可執行權限!
-
當提示文件不存在時,可以通過開發板 vi 查看該文件,看看是不是結尾多出來了一個 ^M,有的話刪掉即可。這是因為 rcS 文件在 windows 下創建時,行尾換行符為'\r\n',這個換行符 ubuntu 的 vi 自動進行優化,但是開發板的 vi 卻沒有,所以只能以^M 顯示。
-
啟發:shell 腳本文件如果格式不對,運行時可能會被提示文件不存在。有時候一個應用程序執行時也會提示文件不存在,問題可能是這個程序所調用的一個動態鏈接庫找不到。
umask 測試
-
umask 是 linux 的一個命令,作用是設置 linux 系統的 umask 值。umask 值決定當前用戶在創建文件時的默認權限。
-
測試結果:
- umask 是 022 的時候,默認 touch 創建一個文件的權限是 644
- umask 是 044 的時候,默認 touch 創建一個文件的權限是 622
- umask 是 444 的時候,默認 touch 創建一個文件的權限是 222
-
總結:umask 的規律就是:umask 值和默認創建文件的權限值加起來是 666。
PATH 測試
- PATH=xxx:這一行定義了一個變量 PATH,值等於后面的字符串。后面用 export 導出了這個 PATH,那么 PATH 就變成了一個環境變量。
- 測試結果:我們的 rcS 文件還沒添加,系統啟動就有了 PATH 中的值?原因在於 busybox 自己用代碼硬編碼為我們導出了一些環境變量,其中就有 PATH。因此看不出什么差別。
runlevel 測試
- runlevel 也是一個 shell 變量,並且被導出為環境變量。
- 測試結果:runlevel 執行結果一直是 unknown,因為 busybox 並不支持 runlevel 這個特性。
mount 測試
-
mount命令是用來掛載文件系統的。mount -a 是掛載所有的應該被掛載的文件系統,在 busybox中 mount -a 時 busybox 會去查找一個文件 /etc/fstab 文件,這個文件按照一定的格式列出來所有應該被掛載的文件系統(包括了虛擬文件系統)
-
測試結果:掛載時全部出錯:
mount: mounting proc on /proc failed: No such file or directory mount: mounting sysfs on /sys failed: No such file or directory mount: mounting tmpfs on /var failed: No such file or directory mount: mounting tmpfs on /tmp failed: No such file or directory mount: mounting tmpfs on /dev failed: No such file or directory -
原因:根文件系統中找不到掛載點。所謂掛載點就是我們要將目標文件系統(當然這里都是虛擬文件系統)掛載到當前文件系統中的某一個目錄中,這個目錄就是掛載點。
-
解決方案:自己在制作的 rootfs 根目錄下創建這些掛載點目錄即可。
mdev 測試
- mdev 是 udev 的嵌入式簡化版本,udev/mdev 是用來配合 linux 驅動工作的一個應用層的軟件,udev/mdev 的工作就是配合 linux 驅動生成相應的 /dev 目錄下的設備文件。
- 在 rcS 文件中沒有啟動 mdev 的時候,/dev 目錄下啟動后是空的;在 rcS 文件中添加上 mdev 有關的 2 行配置項后,再次啟動系統后發現/dev 目錄下生成了很多的設備驅動文件。
- /dev 目錄下的設備驅動文件就是 mdev 生成的,這就是 mdev 的效果和意義。
hostname 測試
- hostname 是 linux 中的一個 shell 命令。命令(hostname xxx)執行后可以用來設置當前系統的主機名為 xxx,直接 hostname 不加參數可以顯示當前系統的主機名。
- /bin/hostname -F /etc/sysconfig/HOSTNAME -F 來指定了一個主機名配置文件(這個文件一般文件名叫 hostname 或者 HOSTNAME),可以這這個配置文件里更改主機名。
- 需要加入 profile 文件才能在命令行前顯示相關前綴。
動態鏈接庫的拷貝
靜態鏈接:arm-linux-gcc hello.c -o hello_satic -static //加 -static 命令即可
動態鏈接:arm-linux-gcc hello.c -o hello_dynamic
實驗結果:靜態編譯連接后生成的 hello_satic 已經可以成功運行。而動態編譯鏈接程序不能執行。
分析:靜態鏈接時,把需要的庫文件都鏈接到了一起,可以獨立運行,但是體積大(10 倍)
Linux下運行程序時,無論程序是動態編譯還是靜態編譯,都需要一些基本庫的支持。若是根文件系統中沒有任何庫文件,直接運行程序將提示找不到可執行文件,即使該可執行文件就在當前目錄下。為了完善根文件系統,我們需要繼續 在 rootfs 中創建 /lib 目錄,並添加必要的庫文件。在不知道需要哪些庫文件的情況下,我們可以直接復制 gcc 中的動態庫文件。步驟如下:
-
找到動態鏈接庫文件:從交叉編譯工具鏈文件夾中尋找,可使用 find -name “libm.so”定位到目標目錄。一般安裝的編譯工具鏈目錄下,如:/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib ( 僅供參考)
-
復制動態鏈接庫到 roots/lib 目錄下。
cp lib/*so* /rootfs/lib/ -rdf # -rdf:保留符號鏈接而非實體 -
使用 strip 工具去掉庫中符號信息:動態鏈接庫 so 文件中包含了調試符號信息,這些符號信息在運行時是沒用的(調試時用的),占用空間。在傳統的嵌入式系統中 flash 空間是有限的,為了節省空間常常把這些符號信息去掉。這樣節省空間並且不影響運行。
arm-linux-strip *so* # 執行后從 30 M 縮小到了 3.8 M
最小 rootfs 目錄結構
- dev 目錄。在 linux 中一切皆是文件,因此一個硬件設備也被虛擬化成一個設備文件來訪問,在 linux 系統中 /dev/xxx 就表示一個硬件設備
- sys 和 proc 目錄。該目錄在最小 rootfs 中也是,屬於 linux 中的虛擬文件系統。
| 目錄 | 說明 | 補充 |
|---|---|---|
| linuxrc | 最重要 | 必須存在 |
| bin | 可執行文件,最主要的是 busybox | 必須存在 |
| sbin | 可執行文件,鏈接指向 /bin/busybox | 必須存在 |
| usr | 系統用戶所有的一些文件的存放地,busybox 安裝時默認創建 | 可以刪除 |
| etc | 存放配置文件,被 /linuxrc 所調用執行 | 必須存在 |
| lib | 存放的是當前操作系統中的動態和靜態鏈接庫文件。 | 必須存在 |
| dev | 設備文件 | 必須存在 |
| sys | 虛擬文件系統,不可省略的,但是只要創建了空文件夾即可 | 必須存在 |
| proc | 虛擬文件系統,不可省略的,但是只要創建了空文件夾即可 | 必須存在 |
| mnt | 用來掛載 | 可以刪除 |
