擴充BusyBox,追加Applet的方法

缺省情況下,BusyBox是對桌面linux的一個簡化,如果要定製比較特殊的功能,比如像操作文件系統一樣操作Flash存儲器,那麼就需要預先定製BusyBox的Applet。這一次,我們就介紹一下爲BusyBox追加功能(Applet)的方法。
窺探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配置

BusyBox的主畫面


自定義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」文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章