さて,おつぎは busyboxというプログラムについて.
init, sh, cat, ls, cp など,UNIX の基本コマンドを集めたプログラムです. GNU の fileutils などに比べオプションが一部省略されているコマンドもあります.
が,最大の特長は「小さなフットプリント」です. 1 つの実行ファイルでサポートしている全てのコマンドに対応しています.
busybox では,1 つの実行ファイルで複数のコマンドに対応しています. つまり,同一の実行ファイルイメージであっても,ファイル名を別の名前に替えただけで違ったコマンドとして動作するのです. どのようなしくみで動作してるのでしょう.
(UNIX 上での) C の main 関数は
int main ( int argc, char *argv[] );
という形式で,親プロセスから引数を受け取っています.
で,argv[0] には,プログラム名を渡すのが慣習となっています.
つまり,ここを見れば「親プロセスが自分をどういうプログラム名として呼び出したか」ということがわかるわけです.
というわけで,そういうプログラムの例.
/*
* argv[0] 判別による複数コマンドの同居
*/
#include <stdio.h>
#include <string.h>
#include <libgen.h>
/*
* hoge コマンドの main
*/
int hoge_main ( int argc, char *argv[] )
{
printf ( "ほげ\n" );
return 0;
}
/*
* huga コマンドの main
*/
int huga_main ( int argc, char *argv[] )
{
printf ( "ふが\n" );
return 0;
}
/*
* funi コマンドの main
*/
int funi_main ( int argc, char *argv[] )
{
printf ( "ふに\n" );
return 0;
}
/*
* ほんとの main
*/
int main ( int argc, char *argv[] )
{
char *cmdname = basename ( argv[0] ); /* コマンド名のディレクトリ部を削除 */
if ( strcmp ( cmdname, "hoge" ) == 0 ) {
return hoge_main ( argc, argv );
} else if ( strcmp ( cmdname, "huga" ) == 0 ) {
return huga_main ( argc, argv );
} else if ( strcmp ( cmdname, "funi" ) == 0 ) {
return funi_main ( argc, argv );
}
/* 指定したコマンドが見つからない */
return -1;
}
コンパイルしてみます. 同一の実行ファイルを別のプログラム名として呼び出すのに,ここではハードリンクを使っています. シンボリックリンクでも問題ありません.
$ gcc hoge.c -o hoge
$ ln hoge huga
$ ln hoge funi
$ ls -li hoge huga funi
6438936 -rwxr-xr-x 3 imai users 13941 Oct 2 04:20 funi*
6438936 -rwxr-xr-x 3 imai users 13941 Oct 2 04:20 hoge*
6438936 -rwxr-xr-x 3 imai users 13941 Oct 2 04:20 huga*
$ ./hoge
ほげ
$ ./huga
ふが
$ ./funi
ふに
呼び出されたプログラム名によって別々の動作をしていますね.
わざわざこのような手法を用いるのは,スタティッリンク時のフットプリントを改善するためです.
普通に,複数のプログラムを別々の実行ファイルとして実装した場合を考えてみます. a いうプログラムと b というプログラムがあるとします. そして,a の中でも b の中でも printf() 関数が呼び出されているとします. この場合,それぞれの実行ファイル内に同一の printf() 関数のコードが存在していることになります. ストレージの制限がきつい組み込み環境では,これは大きな問題です.
が,busybox のように 1 つの実行ファイルにリンクしてしまえば,この問題は解決します. 1 つの実行プログラムなので,printf() 関数のコードは 1 つのみで,別々の実行ファイルとした場合のコードの重複を避けることができます.
ライブラリ関数のコードの重複を避けるという意味では,シェアードライブラリを用いても同様のことは実現できます. が,こちらには「使用しない関数のコードまで付いてくる」という問題があります. (使用しない関数のコードを削除してリンクし直す,という手もありますが…)
というわけで,この「argv[0] による複数コマンドの同居」というワザも,場合によっては自作のプログラムに応用できることもあるかもしれません. 頭の片隅にでも覚えておきましょう.
蛇足ながら,busybox のコンパイルとインストールについて.
まずは busybox のページからソースを入手します. ここでは,busybox-1.00-pre3.tar.bz2 を使用します.
この tarball を展開し,まずはトップディレクトリにある INSTALL ファイルを読んでみます. 要するに
というわけで,まずは make menuconfig. すると,Linux のカーネルコンパイルのようなメニュー画面が出てきます. Linux カーネルからここらは拝借してるのかな.
+------------------------- BusyBox Configuration -------------------------+
| Arrow keys navigate the menu. <Enter> selects submenus --->. |
| Highlighted letters are hotkeys. Pressing <Y> selectes a feature, |
| while <N> will exclude a feature. Press <Esc><Esc> to exit, <?> for |
| Help. Legend: [*] feature is selected [ ] feature is excluded |
| +---------------------------------------------------------------------+ |
| | General Configuration ---> | |
| | Build Options ---> | |
| | Installation Options ---> | |
| | Archival Utilities ---> | |
| | Coreutils ---> | |
| | Console Utilities ---> | |
| | Debian Utilities ---> | |
| | Editors ---> | |
| | Finding Utilities ---> | |
| | Init Utilities ---> | |
| | Login/Password Management Utilities ---> | |
| +-----------v(+)------------------------------------------------------+ |
+-------------------------------------------------------------------------+
| <Select> < Exit > < Help > |
+-------------------------------------------------------------------------+
(copy & paste すると文字化けするので,枠の部分は改竄してあります)
まぁ,いろいろとたくさんの設定オプションがありますが,今回はデフォルトの設定から「 Build Options→Build BusyBox as a static binary (no shared libs) 」のみを y に設定して,ビルドしてみます.
$ make dep
…
$ make
…
$ su
…
# make PREFIX=/root/tree install
…
これで,/root/tree ディレクトリにディレクトリツリーがインストールされました. 中を覗いてみましょう.
# ls tree
bin sbin usr
残念ながら,セットアップしてくれたのはコマンドディレクトリだけです. /dev ディレクトリがありませんね. 面倒なので,ホスト環境の /dev ディレクトリをそのまま拝借してしまいましょう.
/dev の下を丸ごとコピーするとファイル数が多くなるので,find と wc でファイル数を数えておきます.
# cd tree
# cp -a /dev .
# find . | wc -l
5694
#
hello,world の時と同じ要領で initrd イメージを作成します. mke2fs のデフォルト値だと i-ノード数(書き込めるファイル・ディレクトリの数)が不足するので,-N オプションで i-ノード数を直接指定してやります.
# dd if=/dev/zero of=myinitrd.img bs=3309568 count=1
# mke2fs -N 5800 myinitrd.img
…
#
initrd イメージをマウントし,ディレクトリツリーをコピーします.
# mount -o loop myinitrd.img /mnt/tmp0
# cd /mnt/tmp0
# cp -a /root/tree/* .
# cd
# umount /mnt/tmp0
#
作成した myinitrd.img をブートフロッピーのイメージに仕込みます.
# mount -o loop boot.img /mnt/tmp0
# gzip -9 < myinitrd.img >/mnt/tmp0/initrd.img
# umount /mnt/tmp0
#
この boot.img を使って起動してみましょう.
usb.c: registered new driver hub
usb.c: registered new driver hiddev
usb.c: registered new driver hid
hid-core.c: v1.8.1 Andreas Gal, Vojtech Pavlik <vojtech@suse.cz>
hid-core.c: USB HID support drivers
mice: PS/2 mouse device common for all mice
NET4: Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP
IP: routing cache hash table of 512 buckets, 4Kbytes
TCP: Hash tables configured (established 2048 bind 2048)
NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
RAMDISK: Compressed image found at block 0
Freeing initrd memory: 384k freed
VFS: Mounted root (ext2 filesystem) readonly.
Freeing unused kernel memory: 104k freed
init started: BusyBox v1.00-pre3 (2003.10.27-17:24+0000) multi-call binary
Bummer, could not run '/etc/init.d/rcS': No such file or directory
Please press Enter to activate this console.
BusyBox v1.00-pre3 (2003.10.27-17:24+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.
#
とりあえず,シェルは起動したようです.
が,この状態では色々問題があったりします. というわけで,次節へ続く.