天天看點

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 的源代碼樹組織得很好。這些工具都基于它們的用途進行了分類,并存儲在單獨的子目錄中。例如,網絡工具和守護程序(如 ​

​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 映像的壓縮,就需要記住兩件事情:

  1. 永遠不要編譯為靜态二進制檔案(這會将所有需要的庫都包含到映像檔案中)。相反,如果我們是編譯為一個共享映像,那麼它會使用其他應用程式使用的庫(例如​

    ​/lib/libc.so.X​

    ​)。
  2. 使用 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 社群。

作者:​​Leo Chin​​

本部落格文章,大多系網絡中收集,轉載請注明出處

【作者】​​張昺華​​

【微信公衆号】 張昺華

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀