编程 云计算 微软 java wordpress centos shell apache 开源 nginx linux Python Firefox php 程序员 Android Ubuntu mysql Windows google

BusyBox 簡化嵌入式 Linux 系統

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 的源代碼樹組織得很好。這些工具都基於它們的用途進行了分類,並存儲在單獨的子目錄中。例如,網絡工具和守護進程(如 httpdifconfig 等)都在 ./networking 目錄中;標準的模塊工具(包括 insmodrmmod 和 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


使用 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 對於嵌入式系統來說是一個非常有用的工具,因此值得我們花一些時間進行探索。

參考資料

學習

您可以參閱本文在 developerWorks 全球站點上的 英文原文 。

uClibc 是 glibc 一個精簡內存需求的替代品。盡管它需要的資源比 glibc 少,但是將應用程序移植到 uClibc 上通常只需要重新編譯即可。

Open Group 站點上的 POSIX FAQ 可以幫助我們學習更多有關 POSIX 的知識。這個規範的第 3 部分詳細介紹了 shell 和工具的相關內容。

LinuxTiny 是用來減少 2.6 版本的 Linux 內核對內存和磁盤需求的一系列補丁,它只有 2MB 大小的 RAM。如果您對精簡 2.6 版本的 Linux 內核感興趣,請嘗試一下 Matt Mackall 所開發的這個工具。 

在 developerWorks Linux 專區 可以找到為 Linux 開發人員準備的更多資源。 

隨時關註 developerWorks 技術事件和網絡廣播。 

獲得產品和技術

下載 BusyBox 的最新版本。我們可以找到最新的新聞、勘誤以及使用並改進 BusyBox 的教程。

在您的下一個開發項目中采用 IBM 試用軟件,這可以從 developerWorks 上直接下載。 

討論

通過參與 developerWorks blogs 加入 developerWorks 社區。

延伸阅读

评论