- 窺探BusyBox的源代碼
如果要添加Applet,首先必須瞭解BusyBox的源代碼結構。
在BusyBox的魅力一文中,我們已經知道了BusyBox就是一個Applet的集合體。在設定BusyBox的時候,每個Applet會有一個目錄,其目錄下就保存了對應的源代碼。比如「editors」目錄下,保存了BusyBox的「Editors」項目的Applet(如果是「vi」,則源代碼是「editors/vi.c」)。
除此以外,我們還需要知道「libbb」。libbb是各個Applet間使用的函數庫,正因爲有了它才最大限度的縮小了BusyBox的尺寸。其源代碼保存在「libbb」目錄下。
- Applet是怎樣啓動的
爲了更好地理解Applet的源代碼,我們需要了解其啓動過程。
我們已經知道BusyBox中實際執行的各個命令都是link到/bin/busybox,其關鍵代碼就是libbb/appletlib.c。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int main(int argc ATTRIBUTE_UNUSED, char **argv) { applet_name = argv[0]; if (applet_name[0] == '-') applet_name++; applet_name = bb_basename(applet_name); parse_config_file(); run_applet_and_exit(applet_name, argv); full_write2_str(applet_name); full_write2_str(": applet not found\n"); xfunc_die(); } |
該main函數是BusyBox的main函數。argv[0]是applet_name,即實際的命令名稱。比如「/bin/ls」的情況下,applet_name就是「/bin/ls」。第6行用bb_basename函數去除applet_name中的目錄名,得到命令名。「/bin/ls」的情況下,就是「ls」。在第10行通過run_applet_and_exit調用對應的Applet。實際調用的是<Applet名>_main這樣的一個函數。比如applet_name是「ls」的情況下,調用的函數就是ls_main。
如果在源代碼文件「coreutils/ls.c」下,我們就可以找到以下的函數定義。
1 |
int ls_main(int argc UNUSED_PARAM, char **argv) |
這回我們添加「mtd-utils」中的「mtd_debug」命令。「mtd-utils」經常被用到嵌入式系統中來處理Flash存儲器,但是一般沒有包含在BusyBox中。「mtd_debug」命令是用來瀏覽,寫入Flash存儲器信息的命令。
下面是添加Applet的步驟:
- 編譯菜單中添加
- 製作Applet的原型
- 修改Makefile
- 移植Applet
- 調試Applet
- 編譯菜單中添加
我們需要在BusyBox的設定畫面中添加「mtd_debug」Applet的選項。而配置菜單是由「Config.in」文件管理的。該文件存在於各個子目錄中。
首先,爲保存我們的Applet,我們製作一個「mtd-utils」目錄。
1 |
# mkdir mtd-utils |
接下來,創建「mtd-utils/Config.in」文件,首先在根目錄下的「Config.in」文件中追加:
1 2 3 |
source modutils/Config.in source mtd-utils/Config.in ←追加 source util-linux/Config.in |
「mtd-utils/Config.in」的文件內容如下所示:
1 2 3 4 5 6 7 8 9 |
menu "MTD utils" config MTD_DEBUG bool "mtd_debug" default n help Enable support mtd_debug endmenu |
實際的顯示如下圖所示:
BusyBox的主畫面
選擇MTD utils後畫面
- 製作Applet的原型
接下來,我們來設計一個Applet原型,簡單起見,只輸出「hello」字符。大體需要下面3部:
- 在Applet源文件中定義<Applet_name>_main函數
- 在「include/applets.h」文件中添加<Applet_name>_main函數聲明
- 在「include/usage.h」文件中添加Applet幫助
- 在Applet源文件中定義<Applet_name>_main函數
- 建立「mtd-utils/mtd_debug.c」文件,內容如下所示:
1 2 3 4 5 |
#include "libbb.h" int mtd_debug_main(int argc, char **argv) { printf("hello"); } |
- 在「include/applets.h」文件中添加<Applet_name>_main函數聲明
- 「applets.h」中添加以下代碼:
1 2 3 |
USE_MT(APPLET(mt, _BB_DIR_BIN, _BB_SUID_NEVER)) USE_MTD_DEBUG(APPLET(mtd_debug, _BB_DIR_SBIN, _BB_SUID_NEVER)) ←這裏 USE_MV(APPLET(mv, _BB_DIR_BIN, _BB_SUID_NEVER)) |
mtd_debug是Applet的名稱,_BB_DIR_SBIN是指把該Applet鏈接到「/sbin」目錄,_BB_SUID_NEVER是指「busybox」執行文件的suid位無效。另外,添加USE_MTD_DEBUG的時候,要注意前後的字母順序,如果不按順序,有可能出錯。
在applets.h代碼中可以看到以下的數組
1 2 3 |
static struct bb_applet applets[] = { // ... } |
該數組就是BusyBox調用<Applet_name>_main函數的時候使用的。我們剛纔追加的USE_MTD_DEBUG(APPLET(mtd_debug,_BB_DIR_SBIN, _BB_SUID_NEVER)) 就被展開爲 {mtd_debug_main, 2, 0}。
- 在「include/usage.h」文件中添加Applet幫助
- BusyBox啓動某個Applet時如果遇到錯誤,會表示一段幫助信息,現在,我們就添加這段信息。在「include/usage.h」文件中像以下輸入,這裏我們輸入的是空信息。
1 2 3 4 5 6 |
#define mtd_debug_trivial_usage \ "\n" #define mtd_debug_full_usage "\n\n" \ "\n" #define mtd_example_usage \ "\n" |
<applet_name>_trivial_usage是簡單使用幫助, <applet_name>_full_usage是詳細解釋,<applet_name>_example_usage是舉例說明。
- 修改Makefile
在Makefile中需要指定編譯「mtd-utils」目錄的信息,如下所示:
1 2 3 4 5 6 7 8 9 10 |
libs-y:= \ archival/ \ (...) modutils/ \ mtd-utils/ \ ←這裏 (..) # 將mtd_debug.c文件作爲編譯對象 lib-y:= lib-$(CONFIG_MTD_DEBUG) += mtd_debug.o |
- 動作確認
make menuconfig後選擇「mtd_debug」後編譯。如果有以下輸出則表示修改正確。
1 2 |
$ ./busybox mtd_debug hello |
- 移植,調試Applet
有了以上的實踐,我們將實際的「mtd_debug」移植過來。
使用「nandsim」來模擬Flash存儲器(在PC的RAM上虛擬一塊區域來使用)。nandsim 內嵌在 Linux 內核中,作爲標準NAND-Flash的模擬器來使用。首先來搭建「nandsim」的環境:
- 1. 用root身份建立/加載設備文件
1 2 |
# mknod /dev/mtd0 c 90 0 # modprobe nandsim |
このカーネルモジュールは、RAM上に仮想的なNANDフラッシュメモリを構築します。カーネルのメッセージバッファの內容
を表示する「dmesg」コマンドで、カーネルメッセージを確認してみると、以下のように128Mbytesの仮想的なNANDフラッシュ
メモリが作られていることが分かります。
用 dmesg 命令,我們可以查看在RAM上已經建立了一個128Mbytes的虛擬NAND-Falsh。
1 2 |
Creating 1 MTD partitions on "NAND 128MiB 1,8V 8-bit": 0x00000000-0x08000000 : "NAND simulator partition 0" |
- 2. 編寫並移植Applet
在 這裏 下載「mtd-utils」的源代碼。解壓後,在「mtd-utils-1.2.0」目錄中打開「mtd_debug.c」文件。
注意到下面一行代碼
1 |
#include <mtd/mtd-user.h> |
由於是編譯時所需的頭文件,將其拷貝到「/usr/include」下:
1 |
# cp mtd-utils-1.2.0/include/mtd /usr/include/ -a
|
接下來,因爲在BusyBox中使用的是「mtd_debug_main」函數,而不是main函數,所以將其替換。另外,添加#include"libbb.h"一行。最後把修改好的mtd_debug.c文件拷貝到BusyBox的「mtd-utils」目錄下就好了。
1 2 3 4 5 6 7 8 |
#include <fcntl.h> #include <mtd/mtd-user.h> #include "libbb.h" ←添加 ... int main (int argc,char *argv[]) ↓ int mtd_debug_main (int argc,char *argv[]) |
- mtd_debug Applet功能確認
首先,用「mtd_debug info <設備名>」來確認「/dev/mtd0」的容量等信息是否正確。
1 2 3 4 5 6 7 8 |
$ ./busybox mtd_debug info /dev/mtd0 mtd.type = MTD_NANDFLASH mtd.flags = MTD_CAP_NANDFLASH mtd.size = 134217728 (128M) mtd.erasesize = 16384 (16K) mtd.writesize = 512 mtd.oobsize = 16 regions = 0 |
- 調試Applet
BusyBox中的Applet需要儘量做到短小精幹,即使1byte也不能多。實際上,BusyBox內部就準備了許多減小尺寸的技巧。
- 用最簡單的代碼表述
- 簡單的錯誤處理
一般的程序都有許多謹慎的錯誤處理功能。比如打開文件失敗的情況需要釋放內存,然後返回調用函數,最後表示錯誤信息,最後exit(1)結束程序等步驟。
而BusyBox中釋放內存,錯誤信息表示等的步驟都被省略,直接調用exit(1)終了處理。這樣一來,尺寸肯定是減小了。BusyBox中的觀點是沒有釋放的內存由OS來釋放,這樣就沒有內存泄露的問題了。
來比較一下修改前後的代碼:
之前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int flash_to_file (int fd,u_int32_t offset,size_t len,const char *filename) { outfd = creat (filename,O_WRONLY); if (outfd < 0) { perror ("creat()"); goto err1; } err1: if (buf != NULL) free (buf); return (1); } |
之後
1 2 3 4 5 6 7 8 9 10 |
int flash_to_file (int fd,u_int32_t offset,size_t len,const char *filename) { outfd = creat (filename,O_WRONLY); if (outfd < 0) { perror ("creat()"); exit(1); } return (1); } |
- 用簡單的信息表示
CUI程序經常用printf來輸出一些信息。這些信息字符串也被作爲程序的一部分保存在執行文件當中。BusyBox中用將信息字符串簡化,共有等方式等減小程序整體的大小。
- 使用libbb
libbb是各個Applet間使用的函數庫,利用它可以提高BusyBox的高效,減小其尺寸。其函數都在「include/libb.h」文件中定義,這裏介紹「x函數」「bb_show_usage」「getopt32」3個被經常用到的函數。
- x函數
x函數是以x開頭的一系列函數。比如「xopendir」「xmalloc」「xstrdup」「xstrndup」「xmalloc」「xzalloc」「xfopen」「xopen」等。這些標準函數做到了簡單地錯誤處理,簡單的信息表示。比如用xfopen函數打開文件失敗的情況下,只顯示"can't open <file_name>"後,即調用exit(1)退出。
比如
1 2 3 4 5 |
if ((fd = open (argv[2],O_SYNC | open_flag)) < 0) { perror ("open()"); exit (1); } |
中的open函數就可以簡單地用xopen函數來替換。
- bb_show_usage
上面,我們在「usage.h」文件中添加了Applet的幫助信息。使用bb_show_usage函數,就可以將添加到usage.h中的信息顯示相互來。這些放在「usage.h」文件中的信息字符串,所有的Applet都可以共用,所以一定程度上節約了文件大小。
「mtd_debug」的例子中將「showusage」函數用 bb_show_usage() 函數替換就可以了。
- getopt32
getopt32類似於linux上的getopt函數,用來解釋命令行。
比如
1 |
flags = getopt32(argv, “ab”); |
如果輸入「-a」命令行參數時,「flags」的第1bit是「1」,輸入「-b」時,「flags」的第2bit是「1」。
另外,像以下的使用也是可以的
1 |
flags = getopt32(argv, "a:b”, &value); |
「a:」表示輸入「-a」命令行參數。具體的值保存到「value」中。關於「getopt32」的使用,可以參考「libbb/getopt32.c」文件。