BusyBox 簡化嵌入式 Linux 系統
轉載:http://www.ibm.com/developerworks/cn/linux/l-busybox/為小環境準備的一個小工具包
BusyBox 是很多標準 Linux® 工具的一個單個可執行實現。BusyBox 包含了一些簡單的工具,例如 cat 和 echo,還包含了一些更大、更復雜的工具,例如 grep、find、mount 以及 telnet(不過它的選項比傳統的版本要少);有些人將 BusyBox 稱為 Linux 工具裡的瑞士軍刀。本文將探索 BusyBox 的目標,它是如何工作的,以及為什麼它對於記憶體有限的環境來說是如此重要。
1
BusyBox 的誕生
BusyBox 最初是由 Bruce Perens 在 1996 年為 Debian GNU/Linux 安裝盤編寫的。其目標是在一張軟盤上建立一個可引導的 GNU/Linux 系統,這可以用作安裝盤和急救盤。一張軟盤可以儲存大約 1.4-1.7MB 的內容,因此這裡沒有多少空間留給 Linux 核心以及相關的使用者應用程式使用。
BusyBox 許可證
BusyBox 是按照 GNU General Public License(GPL)許可證發行的。這意味著如果我們在一個專案中使用 BusyBox,就必須遵守這個許可證。我們可以在 BusyBox Web 站點(請參看本文後面 參考資料 一節的內容)上看到這個許可證的內容。BusyBox 團隊似乎正忙於監視違反這個許可證的情況。實際上,他們維護了一個 “Hall of Shame” 頁面來說明違反者的情況。
BusyBox 揭露了這樣一個事實:很多標準 Linux 工具都可以共享很多共同的元素。例如,很多基於檔案的工具(比如 grep
find
)都需要在目錄中搜索檔案的程式碼。當這些工具被合併到一個可執行程式中時,它們就可以共享這些相同的元素,這樣可以產生更小的可執行程式。實際上,BusyBox 可以將大約 3.5MB 的工具包裝成大約 200KB 大小。這就為可引導的磁碟和使用 Linux 的嵌入式裝置提供了更多功能。我們可以對 2.4 和 2.6 版本的 Linux 核心使用 BusyBox。
BusyBox 是如何工作的?
為了讓一個可執行程式看起來就像是很多可執行程式一樣,BusyBox 為傳遞給 C 的 main 函式的引數開發了一個很少使用的特性。回想一下 C 語言的 main 函式的定義如下:
POSIX 環境
儘管 BusyBox 的目標 是提供一個相當完整的 POSIX(可移植作業系統介面)環境,這是一個期望,而不是一種需求。這些工具雖然並不完整,但是它們提供了我們期望的主要功能。
清單 1. C 的 main 函式
int main( int argc, char *argv[] )
在這個定義中,argc
是傳遞進來的引數的個數(引數數量),而 argv
是一個字串陣列,代表從命令列傳遞進來的引數(引數向量)。argv
的索引 0 是從命令列呼叫的程式名。
清單 2 給出的這個簡單 C 程式展示了 BusyBox 的呼叫。它只簡單地列印 argv
向量的內容。
清單 2. BusyBox 使用 argv[0]
來確定呼叫哪個應用程式
// test.c #include <stdio.h> int main( int argc, char *argv[] ) { int i; for (i = 0 ; i < argc ; i++) { printf("argv[%d] = %s\n", i, argv[i]); } return 0; }
呼叫這個程式會顯示所呼叫的第一個引數是該程式的名字。我們可以對這個可執行程式重新進行命名,此時再呼叫就會得到該程式的新名字。另外,我們可以建立一個到可執行程式的符號連結,在執行這個符號連結時,就可以看到這個符號連結的名字。
清單 3. 在使用新命令更新 BusyBox 之後的命令測試
$ gcc -Wall -o test test.c $ ./test arg1 arg2 argv[0] = ./test argv[1] = arg1 argv[2] = arg2 $ mv test newtest $ ./newtest arg1 argv[0] = ./newtest argv[1] = arg1 $ ln -s newtest linktest $ ./linktest arg argv[0] = ./linktest argv[1] = arg
BusyBox 使用了符號連結以便使一個可執行程式看起來像很多程式一樣。對於 BusyBox 中包含的每個工具來說,都會這樣建立一個符號連結,這樣就可以使用這些符號連結來呼叫 BusyBox 了。BusyBox 然後可以通過 argv[0]
來呼叫內部工具。
配置並編譯 BusyBox
我們可以從 BusyBox 的 Web 站點上下載最新版本的 BusyBox(請參看 參考資料 一節的內容)。與大部分開放原始碼程式一樣,它是以一個壓縮的 tarball 形式釋出的,我們可以使用清單 4 給出的命令將其轉換成原始碼樹。(如果我們下載的版本不是 1.1.1,那就請在這個命令中使用適當的版本號以及特定於這個版本號的命令。)
清單 4. 展開 BusyBox
$ tar xvfz busybox-1.1.1.tar.gz
$
結果會生成一個目錄,名為 busybox-1.1.1,其中包含了 BusyBox 的原始碼。要編譯預設的配置(其中包含了幾乎所有的內容,並禁用了除錯功能),請使用 defconfig
make 目標:
BusyBox 原始碼樹
BusyBox 的原始碼樹組織得很好。這些工具都基於它們的用途進行了分類,並存儲在單獨的子目錄中。例如,網路工具和守護程序(如 httpd
、ifconfig
等)都在 ./networking 目錄中;標準的模組工具(包括insmod
、rmmod
和 lsmod
)都在 ./modutils 目錄中;編輯器(例如 vi
和流編輯器,如 awk
和 sed
)都在 ./editors 目錄中。makefile 配置、編譯和安裝所使用的各個文件都在這個目錄樹的根目錄中。
清單 5. 編譯預設的 BusyBox 配置
$ cd busybox-1.1.1 $ make defconfig $ make $
結果是一個相當大的 BusyBox 映像,不過這只是開始使用它的最簡單的方法。我們可以直接呼叫這個新映像,這會產生一個簡單的 Help 頁面,裡面包括當前配置的命令。要對這個映像進行測試,我們也可以對一個命令呼叫 BusyBox 來執行,如清單 6 所示。
清單 6. 展示 BusyBox 命令的執行和 BusyBox 中的 ash shell
$ ./busybox pwd /usr/local/src/busybox-1.1.1 $ ./busybox ash /usr/local/src/busybox-1.1.1 $ pwd /usr/local/src/busybox-1.1.1 /usr/local/src/busybox-1.1.1 $ exit $
在這個例子中,我們呼叫了 pwd
(列印工作目錄)命令,使用 BusyBox 進入了 ash
shell,並在 ash
中呼叫了 pwd
。
手工配置
如果您正在構建一個具有特殊需求的嵌入式裝置,那就可以手工使用 menuconfig
make 目標來配置 BusyBox 的內容。如果您熟悉 Linux 核心的編譯過程,就會注意到 menuconfig
與配置 Linux 核心的內容所使用的目標相同。實際上,它們都採用了相同的基於 ncurses 的應用程式。
使用手工配置,我們可以指定在最終的 BusyBox 映像中包含的命令。我們也可以對 BusyBox 環境進行配置,例如包括對 NSA(美國國家安全代理)的安全增強 Linux(SELinux),指定要使用的編譯器(用來在嵌入式環境中進行交叉編譯)以及 BusyBox 應該靜態編譯還是動態編譯。圖 1 給出了 menuconfig
的主介面。在這裡我們應該可以看到可以為 BusyBox 配置的不同型別的應用程式(applet)。
圖 1. 使用 menuconfig 配置 BusyBox
多體系結構支援
可以簡單地為 BusyBox 指定交叉編譯器意味著我們可以為很多體系結構編譯 BusyBox。要為您的目標體系結構編譯 BusyBox,我們需要一個交叉編譯器和一個已經為特定目標體系結構編譯好的 C 庫(uClibc 或 glibc)。
要手工配置 BusyBox,請使用下面的命令:
清單 7. 手工配置 BusyBox
$ make menuconfig $ make $
這為我們提供了可以呼叫的 BusyBox 的二進位制檔案。下一個步驟是圍繞 BusyBox 構建一個環境,包括將標準 Linux 命令重定向到 BusyBox 二進位制檔案的符號連結。我們可以使用下面的命令簡單地完成這個過程:
清單 8. 構建 BusyBox 環境
$ make install
$
預設情況下,這會建立一個新的本地子目錄 _install,其中包含了基本的 Linux 環境。在這個根目錄中,您會找到一個連結到 BusyBox 的linuxrc
程式。這個 linuxrc
程式在構建安裝盤或急救盤(允許提前進行模組化的引導)時非常有用。同樣是在這個根目錄中,還有一個包含作業系統二進位制檔案的 /sbin 子目錄。還有一個包含使用者二進位制檔案的 /bin 目錄。在構建軟盤發行版或嵌入式初始 RAM 磁碟時,我們可以將這個 _install 目錄遷移到目標環境中。我們還可以使用 make 程式的 PREFIX
選項將安裝目錄重定向到其他位置。例如,下面的程式碼就使用 /tmp/newtarget 根目錄來安裝這些符號連結,而不是使用 ./_install 目錄:
清單 9. 將符號連結安裝到另外一個目錄中
$ make PREFIX=/tmp/newtarget install
$
使用 install
make 目標建立的符號連結都來自於 busybox.links 檔案。這個檔案是在編譯 BusyBox 時建立的,它包含了已經配置的命令清單。在執行 install
時,就會檢查 busybox.links 檔案確定要建立的符號連結。
到 BusyBox 的命令列連結也可以使用 BusyBox 在執行時動態建立。CONFIG_FEATURE_INSTALLER
選項就可以啟用這個特性,在執行時可以這樣執行:
清單 10. 在執行時建立命令連結
$ ./busybox --install -s
$
-s
選項強制建立這些符號連結(否則就建立硬連結)。這個選項要求系統中存在 /proc 檔案系統。
BusyBox 編譯選項
BusyBox 包括了幾個編譯選項,可以幫助為我們編譯和除錯正確的 BusyBox。
表 1. 為 BusyBox 提供的幾個 make 選項
make 目標 | 說明 |
---|---|
help |
顯示 make 選項的完整列表 |
defconfig |
啟用預設的(通用)配置 |
allnoconfig |
禁用所有的應用程式(空配置) |
allyesconfig |
啟用所有的應用程式(完整配置) |
allbareconfig |
啟用所有的應用程式,但是不包括子特性 |
config |
基於文字的配置工具 |
menuconfig |
N-curses(基於選單的)配置工具 |
all |
編譯 BusyBox 二進位制檔案和文件(./docs) |
busybox |
編譯 BusyBox 二進位制檔案 |
clean |
清除原始碼樹 |
distclean |
徹底清除原始碼樹 |
sizes |
顯示所啟用的應用程式的文字/資料大小 |
在定義配置時,我們只需要輸入 make
就可以真正編譯 BusyBox 二進位制檔案。例如,要為所有的應用程式編譯 BusyBox,我們可以執行下面的命令:
清單 11. 編譯 BusyBox 二進位制程式
$ make allyesconfig $ make $
壓縮 BusyBox
如果您非常關心對 BusyBox 映像的壓縮,就需要記住兩件事情:
- 永遠不要編譯為靜態二進位制檔案(這會將所有需要的庫都包含到映像檔案中)。相反,如果我們是編譯為一個共享映像,那麼它會使用其他應用程式使用的庫(例如
/lib/libc.so.X
)。 - 使用 uClibc 進行編譯,這是一個對大小進行過優化的 C 庫,它是為嵌入式系統開發的;而不要使用標準的 glibc (GNU C 庫)來編譯。
BusyBox 命令中支援的選項
BusyBox 中的命令並不支援所有可用選項,不過這些命令都包含了常用的選項。如果我們需要知道一個命令可以支援哪些選項,可以使用 --help
選項來呼叫這個命令,如清單 12 所示。
清單 12. 使用 --help 選項呼叫命令
$ ./busybox wc --help
BusyBox v1.1.1 (2006.04.09-15:27+0000) multi-call binary
Usage: wc [OPTION]... [FILE]...
Print line, word, and byte counts for each FILE, and a total line if
more than one FILE is specified. With no FILE, read standard input.
Options:
-c print the byte counts
-l print the newline counts
-L print the length of the longest line
-w print the word counts
$
這些特定的資料只有在啟用了 CONFIG_FEATURE_VERBOSE_USAGE
選項時才可以使用。如果沒有這個選項,我們就無法獲得這些詳細資料,但是這樣可以節省大約 13 KB 的空間。
向 BusyBox 中新增新命令
向 BusyBox 新增一個新命令非常簡單,這是因為它具有良好定義的體系結構。第一個步驟是為新命令的原始碼選擇一個位置。我們要根據命令的型別(網路,shell 等)來選擇位置,並與其他命令保持一致。這一點非常重要,因為這個新命令最終會在 menuconfig 的配置選單中出現(在下面的例子中,是 Miscellaneous Utilities 選單)。
對於這個例子來說,我將這個新命令稱為 newcmd
,並將它放到了 ./miscutils 目錄中。這個新命令的原始碼如清單 13 所示。
清單 13. 整合到 BusyBox 中的新命令的原始碼
#include "busybox.h" int newcmd_main( int argc, char *argv[] ) { int i; printf("newcmd called:\n"); for (i = 0 ; i < argc ; i++) { printf("arg[%d] = %s\n", i, argv[i]); } return 0; }
接下來,我們要將這個新命令的原始碼新增到所選子目錄中的 Makefile.in
中。在本例中,我更新了 ./miscutils/Makefile.in
檔案。請按照字母順序來新增新命令,以便維持與現有命令的一致性:
清單 14. 將命令新增到 Makefile.in 中
MISCUTILS-$(CONFIG_MT) += mt.o
MISCUTILS-$(CONFIG_NEWCMD) += newcmd.o
MISCUTILS-$(CONFIG_RUNLEVEL) += runlevel.o
接下來再次更新 ./miscutils 目錄中的配置檔案,以便讓新命令在配置過程中是可見的。這個檔名為 Config.in,新命令是按照字母順序新增的:
清單 15. 將命令新增到 Config.in 中
config CONFIG_NEWCMD bool "newcmd" default n help newcmd is a new test command.
這個結構定義了一個新配置項(通過 config
關鍵字)以及一個配置選項(CONFIG_NEWCMD
)。新命令可以啟用,也可以禁用,因此我們對配置的選單屬性使用了 bool
(Boolean)值。這個命令預設是禁用的(n
表示 No),我們可以最後放上一個簡短的 Help 描述。在原始碼樹的 ./scripts/config/Kconfig-language.txt 檔案中,我們可以看到配置語法的完整文法。
接下來需要更新 ./include/applets.h 檔案,使其包含這個新命令。將下面這行內容新增到這個檔案中,記住要按照字母順序。維護這個次序非常重要,否則我們的命令就會找不到。
清單 16. 將命令新增到 applets.h 中
USE_NEWCMD(APPLET(newcmd, newcmd_main, _BB_DIR_USER_BIN, _BB_SUID_NEVER))
這定義了命令名(newcmd
),它在 Busybox 原始碼中的函式名(newcmd_main
),應該在哪裡會為這個新命令建立連結(在這種情況中,它在 /usr/bin 目錄中),最後這個命令是否有權設定使用者 id(在本例中是 no)。
倒數第二個步驟是向 ./include/usage.h 檔案中新增詳細的幫助資訊。正如您可以從這個檔案的例子中看到的一樣,使用資訊可能非常詳細。在本例中,我只添加了一點資訊,這樣就可以編譯這個新命令了:
清單 17. 向 usage.h 新增幫助資訊
#define newcmd_trivial_usage "None" #define newcmd_full_usage "None"
最後一個步驟是啟用新命令(通過 make menuconfig
,然後在 Miscellaneous Utilities 選單中啟用這個選項)然後使用 make
來編譯 BusyBox。
使用新的 BusyBox,我們可以對這個新命令進行測試,如清單 18 所示。
清單 18. 測試新命令
$ ./busybox newcmd arg1 newcmd called: arg[0] = newcmd arg[1] = arg1 $ ./busybox newcmd --help BusyBox v1.1.1 (2006.04.12-13:47+0000) multi-call binary Usage: newcmd None None
就是這樣!BusyBox 開發人員開發了一個優秀但非常容易擴充套件的工具。
結束語
BusyBox 是為構建記憶體有限的嵌入式系統和基於軟盤系統的一個優秀工具。BusyBox 通過將很多必需的工具放入一個可執行程式,並讓它們可以共享程式碼中相同的部分,從而對它們的大小進行了很大程度的縮減,BusyBox 對於嵌入式系統來說是一個非常有用的工具,因此值得我們花一些時間進行探索。