組み込み機器向けLinuxで使われるbusyboxの使い方を整理してみました。
Android向けのandroid_ndk_defconfigも試してみました。
1. busyboxの概要
busyboxとはUNIX系ユーティリティツールを一つの小さな実行ファイルにまとめたパッケージです。
UNIX系ユーティリティツールにはcoreutils系のツール(lsやrm等)やinit等の初期化処理用ツールが含まれます。
ただし各種ツールの完全互換ではない点に留意する必要があります。
例えば、 coreutilsのlsでは-Uオプションというものがあり、lsで表示される結果がファイルシステムで管理される内容そのままで表示されます。
-U do not sort; list entries in directory order
私の環境でbusyboxのディレクトリでls -lUを実行すると以下の結果が得られました。
$ ls -lU total 7236 -rw-r--r-- 1 <me> <me> 327680 Feb 21 07:39 GPATH drwxrwxr-x 2 <me> <me> 4096 Feb 21 07:40 findutils drwxrwxr-x 2 <me> <me> 4096 Feb 21 07:40 debianutils drwxrwxr-x 2 <me> <me> 4096 Feb 21 07:40 init -rw-rw-r-- 1 <me> <me> 11584 Feb 21 06:51 TODO -rw-rw-r-- 1 <me> <me> 23948 Feb 21 07:40 busybox_unstripped.out -rw-rw-r-- 1 <me> <me> 8763 Feb 21 06:51 README <snip>
同様にbusyboxのlsで-lUオプションを指定するとUオプションが不正であるという出力が得られました。
$ ./busybox ls -lU ls: invalid option -- 'U' BusyBox v1.23.0.git (2014-02-21 07:39:58 JST) multi-call binary. Usage: ls [-1AaCxdLHRFplinsehrSXvctu] [-w WIDTH] [FILE]... List directory contents -1 One column output -a Include entries which start with . <snip>
このように完全互換ではありませんが(-Uオプションを使う機会なんて滅多にないと思いますが)、サポートされているコマンド群に比べてサイズは極めて小さいものになっております。
freebsd_defconfigを用いた場合に、185個のコマンドが含まれるにも関わらず、lsのサイズの4倍程度で済んでおります(動的ライブラリのサイズは考慮しておりません)。
$ ls -l /bin/ls -rwxr-xr-x 1 root root 105840 Nov 20 2012 /bin/ls $ ls -l busybox -rwxrwxr-x 1 <me> <me> 414496 Feb 21 07:40 busybox $ ./busybox --list [ [[ addgroup ar ash awk <snip>
2. 使い方
busyboxを使うには幾つかの方法があります。
2.1. サポートしているコマンドの確認
busyboxがサポートしているコマンドは--listオプションあるいは--helpオプションで確認できます。busybox --helpの出力は以下の通りです。
Currently defined functions以降がサポートしているコマンドです。
$ ./busybox --help BusyBox v1.23.0.git (2014-02-21 07:39:58 JST) multi-call binary. BusyBox is copyrighted by many authors between 1998-2012. Licensed under GPLv2. See source distribution for detailed copyright notices. Usage: busybox [function [arguments]...] or: busybox --list[-full] or: busybox --install [-s] [DIR] or: function [arguments]... BusyBox is a multi-call binary that combines many common Unix utilities into a single executable. Most people will create a link to busybox for each function they wish to use and BusyBox will act like whatever it was invoked as. Currently defined functions: [, [[, addgroup, ar, ash, awk, basename, bunzip2, bzcat, bzip2, cal, cat, catv, chattr, chgrp, chmod, chown, chroot, chrt, cksum, clear, cmp, comm, cp, cpio, crontab, cut, dc, dd, delgroup, diff, dirname, dnsd, dnsdomainname, dos2unix, du, echo, ed, egrep, env, expand, expr, fakeidentd, false, fgrep, find, flock, fold, fstrim, fsync, ftpd, ftpget, ftpput, getopt, grep, groups, gunzip, gzip, hd, head, hexdump, hostid, hostname, httpd, id, install, iostat, ipcalc, kill, killall, killall5, less, ln, logger, logname, logread, lpq, lpr, ls, lsof, lspci, lsusb, lzcat, lzma, lzop, lzopcat, md5sum, microcom, mkdir, mkfifo, mktemp, more, mpstat, mv, nc, nice, nohup, od, patch, pgrep, pipe_progress, pkill, pmap, powertop, printenv, printf, ps, pscan, pstree, pwd, pwdx, readlink, readprofile, realpath, renice, reset, resize, rev, rm, rmdir, rpm, rpm2cpio, run-parts, scriptreplay, sed, seq, setserial, setsid, sh, sha1sum, sha256sum, sha3sum, sha512sum, sleep, smemcap, sort, split, strings, sum, sync, sysctl, syslogd, tail, tar, tee, telnet, telnetd, test, tftp, tftpd, timeout, touch, tr, true, tty, ttysize, ubimkvol, ubirmvol, ubirsvol, ubiupdatevol, uname, uncompress, unexpand, uniq, unix2dos, unlzma, unlzop, unxz, unzip, usleep, uudecode, uuencode, vi, volname, watch, wc, wget, which, whoami, whois, xargs, xz, xzcat, yes, zcat
busybox --listの出力は以下の通りです。
--helpと異なり、コマンド実行後にサポートしているコマンドが出力されます。
$ ./busybox --list [ [[ addgroup ar ash awk <snip>
2.2. 第一引数にコマンド名を指定
busyboxの第一引数にコマンド名を指定することで、busyboxがサポートするコマンドを実行することができます。
$ ./busybox ls applets examples miscutils applets_sh findutils modutils arch GPATH networking archival GRTAGS printutils AUTHORS GTAGS procps busybox include README busybox_unstripped init runit busybox_unstripped.map INSTALL scripts busybox_unstripped.out libbb selinux Config.in libpwdgrp shell configs LICENSE sysklogd console-tools loginutils testsuite coreutils mailutils TODO debianutils Makefile TODO_unicode docs Makefile.custom util-linux e2fsprogs Makefile.flags editors Makefile.help
第二引数以降に第一引数のコマンドの引数を指定できます。
$ ./busybox ls -l -a drwxr-xr-x 35 <me> <me> 4096 Feb 21 07:40 . drwxrwxr-x 14 <me> <me> 4096 Feb 21 06:50 .. drwxrwxr-x 2 <me> <me> 4096 Feb 21 07:40 applets drwxrwxr-x 2 <me> <me> 4096 Feb 21 06:51 applets_sh drwxrwxr-x 3 <me> <me> 4096 Feb 21 06:51 arch drwxrwxr-x 3 <me> <me> 4096 Feb 21 07:40 archival -rw-rw-r-- 1 <me> <me> 5171 Feb 21 06:51 AUTHORS -rwxrwxr-x 1 <me> <me> 414496 Feb 21 07:40 busybox <snip>
2.3. シンボリックリンク経由でコマンド名を指定
以下のコードでコマンドをそのまま実行した場合とシンボリックリンク経由でコマンドを実行した場合のargv[0]を表示します。
$ cat argv0.c #include <stdio.h> int main(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) printf("%s\n", argv[i]); return 0; }
コマンドをそのまま実行した場合はコマンドのファイル名が出力され、シンボリックリンク経由で実行した場合はシンボリックリンクのファイル名が出力さ れます。
$ ln -s argv0 argv0_sym $ ./argv0 ./argv0 $ ./argv0_sym ./argv0_sym
busyboxではこの仕組みを利用し、シンボリックリンク経由でコマンドを実行することを可能にしています。
$ ln -s busybox ls $ ./ls -la drwxr-xr-x 35 <me> <me> 4096 Feb 21 08:39 . drwxrwxr-x 14 <me> <me> 4096 Feb 21 06:50 .. drwxrwxr-x 2 <me> <me> 4096 Feb 21 07:40 applets drwxrwxr-x 2 <me> <me> 4096 Feb 21 06:51 applets_sh drwxrwxr-x 3 <me> <me> 4096 Feb 21 06:51 arch drwxrwxr-x 3 <me> <me> 4096 Feb 21 07:40 archival -rw-rw-r-- 1 <me> <me> 5171 Feb 21 06:51 AUTHORS -rwxrwxr-x 1 <me> <me> 414496 Feb 21 07:40 busybox
busyboxのシンボリックリンクによるコマンド実行は@ITの記事が分かりやすいです。
なお、ハードリンクの場合も同様になります。
2.4. ラッパースクリプト経由でのコマンド指定
以下のような実行権限を持つラッパースクリプトを作成します。シェバングでargv0を指定しています。
$ cat argv0_scr #!argv0
実行結果は以下のようになります。argv[1]にラッパースクリプトのファイル名が設定されます。
busybox lsと似た形になりますね。
$ ./argv0_scr argv0 ./argv0_scr
busyboxでは各種コマンドのファイルをbusyboxへのシンボリックリンク、ハードリンク、ラッパースクリプトのいずれかで提供します(コンフィグで切り替え可能)。
3. ビルド方法
以下からソースコードをgitで取得します。リリース版のアーカイブも公開されてます。
$ git clone git://busybox.net/busybox.git
3.4. defconfig
いくつかのデフォルトコンフィグレーションが用意されてます。Linuxカーネルと同様に、make <defconfig>で使用できます。
$ ls configs/ android2_defconfig cygwin_defconfig TEST_noprintf_defconfig android_defconfig freebsd_defconfig TEST_rh9_defconfig android_ndk_defconfig TEST_nommu_defconfig $ make freebsd_defconfig
android_ndk_defconfigがありますね。
3.2. make menuconfig
ncursesライブラリを使用したCUIベースのコンフィグレーション編集ができます。
$ make menuconfig
3.3. ビルド
CROSS_COMPILEでクロスコンパイラを指定することで、クロスコンパイルが可能です。
$ CROSS_COMPILE=<path-to-prefix> make
3.4. インストール
CONFIG_PREFIXでインストール先を指定します。
コンフィグの段階で設定することもできます。
デフォルトはCONFIG_PREFIX=_installになっています。
make install CONFIG_PREFIX=<path> <path>//bin/ash -> busybox <path>//bin/cat -> busybox <path>//bin/catv -> busybox <snip>
4. x86で起動できるルートファイルシステムを作成
ここではbusyboxのinitから起動し、ログインプロンプト表示、パスワード入力、シェル起動までを試してみます。
4.1. コンフィグの設定
allnoconfigを実行した後、make menuconfigで各種コンフィグを有効にします。
4.1.1. allnoconfig
今回はallnoconfigを使用して、コンフィグをできる限りOFFにしてみます。
これとは逆にallyesconfigもあります。
$ make allnoconfig
4.1.2. ライブラリの静的リンク
ライブラリを静的リンクにしてbusyboxを作成します。
本コンフィグを有効にすることで/lib/libxxxのインストールが不要になります。
Symbol: STATIC [=y] Prompt: Build BusyBox as a static binary (no shared libs) Defined at Config.in:482 Location: -> Busybox Settings -> Build Options
4.1.3. init
init経由で起動するので、initコマンドを導入します。
Symbol: INIT [=y] Prompt: init Defined at init/Config.in:72 Location: -> Init Utilities Selects: FEATURE_SYSLOG
4.1.4. login
ログインプロンプトを表示するので、loginコマンドを導入します。
Symbol: LOGIN [=y] Prompt: login Defined at loginutils/Config.in:216 Location: -> Login/Password Management Utilities Selects: FEATURE_SYSLOG
4.1.5. passwd
ログインプロンプトにてパスワード入力を有効にする為、passwdコマンドを導入します。
Symbol: PASSWD [=y] Prompt: passwd Defined at loginutils/Config.in:262 Location: -> Login/Password Management Utilities Selects: FEATURE_SYSLOG
4.1.6. getty
tty経由でのログインを可能にする為、gettyを導入します。
Symbol: GETTY [=y] Prompt: getty Defined at loginutils/Config.in:197 Location: -> Login/Password Management Utilities Selects: FEATURE_SYSLOG
4.1.7. ash
シェル起動する為、ashを導入します。busyboxではash以外に、機能を絞りサイズがより小さいhushというシェルもあります。
Symbol: ASH [=y] Prompt: ash Defined at shell/Config.in:9 Depends on: !NOMMU Location: -> Shells
加えて、/bin/shのリンクをashにします。
Symbol: FEATURE_SH_IS_ASH [=y] Prompt: ash Defined at shell/Config.in:307 Depends on: <choice> && ASH && !NOMMU Location: -> Shells -> Choose which shell is aliased to 'sh' name (<choice> [=y])
4.2. ビルド/インストール
make all installを実行し、_installにインストールします。
$ make all install $ tree _install/ _install/ ├── bin │ ├── ash -> busybox │ ├── busybox │ ├── login -> busybox │ └── sh -> busybox ├── linuxrc -> bin/busybox ├── sbin │ ├── getty -> ../bin/busybox │ └── init -> ../bin/busybox └── usr └── bin └── passwd -> ../../bin/busybox
4.3. 必要なファイルの追加
このままでは設定ファイルやデバイスノードが開けず、起動が失敗する為、いくつかのファイルを追加します。
4.3.1. /etc/inittab
initが/etc/inittabを必要とするので、examples/inittabをベースに以下の変更を加え、_install/etc/に配置します。
diff --git a/examples/inittab b/examples/inittab index 01ceaef..1fd06c5 100644 --- a/examples/inittab +++ b/examples/inittab @@ -47,7 +47,7 @@ # Boot-time system configuration/initialization script. # This is run first except when booting in single-user mode. # -::sysinit:/etc/init.d/rcS +#::sysinit:/etc/init.d/rcS # /bin/sh invocations on selected ttys # @@ -57,18 +57,18 @@ # this yourself... # # Start an "askfirst" shell on the console (whatever that may be) -::askfirst:-/bin/sh +#::askfirst:-/bin/sh # Start an "askfirst" shell on /dev/tty2-4 -tty2::askfirst:-/bin/sh -tty3::askfirst:-/bin/sh -tty4::askfirst:-/bin/sh +#tty2::askfirst:-/bin/sh +#tty3::askfirst:-/bin/sh +#tty4::askfirst:-/bin/sh # /sbin/getty invocations for selected ttys -tty4::respawn:/sbin/getty 38400 tty5 -tty5::respawn:/sbin/getty 38400 tty6 +#tty4::respawn:/sbin/getty 38400 tty5 +#tty5::respawn:/sbin/getty 38400 tty6 # Example of how to put a getty on a serial line (for a terminal) -#::respawn:/sbin/getty -L ttyS0 9600 vt100 +::respawn:/sbin/getty -L ttyS0 115200 vt100 #::respawn:/sbin/getty -L ttyS1 9600 vt100 # # Example how to put a getty on a modem line. @@ -78,6 +78,6 @@ tty5::respawn:/sbin/getty 38400 tty6 ::restart:/sbin/init # Stuff to do before rebooting -::ctrlaltdel:/sbin/reboot -::shutdown:/bin/umount -a -r -::shutdown:/sbin/swapoff -a +#::ctrlaltdel:/sbin/reboot +#::shutdown:/bin/umount -a -r +#::shutdown:/sbin/swapoff -a
カーネル起動パラメータにconsole=ttyS0,115200を加えた状態で::askfirst:-/bin/shと::respawn:/sbin/getty -L ttyS0 115200 vt100が混在するとシェルとログインプロンプトが同時に起動して妙なことになるので注意してください。
rcSも導入できますが、簡易化する為、コメントアウトしてます。
以上の変更を加えて、_install/etcに設置します。
$ mkdir _install/etc $ cp examples/inittab _install/etc/ $ grep -v -e "^#" -e "^$" _install/etc/inittab ::respawn:/sbin/getty -L ttyS0 115200 vt100 ::restart:/sbin/init
4.3.2. /etc/passwd
passwdコマンドが/etc/passwdを参照するので、root用のエントリが記入された/etc/passwdを下記の内容で追加します。
$ cat _install/etc/passwd root::0:0:root:/root:/bin/sh
passwdは空にしております。shadowパスワードは導入しておりません。
4.3.3. /dev/null
gettyが/dev/nullのデバイスノードを必要とするので作成します。
$ mkdir _install/dev $ sudo mknod _install/dev/null c 1 3
4.4. 起動
以下の通り、無事起動しました。lsを入れてないので、echo *で代用してます。
VFS: Mounted root (nfs filesystem) on device 0:10. devtmpfs: mounted Freeing unused kernel memory: 296k freed Write protecting the kernel text: 2788k Write protecting the kernel read-only data: 884k 192.168.0.3 login: root login: can't change directory to '/root' Feb 21 05:46:45 login[29]: root login on 'ttyS0' # echo * bin dev etc linuxrc sbin usr #
5. android_ndk_defconfig
以下のデフォルトコンフィグレーションを使用してみます。
$ make android_ndk_defconfig
5.1. コンフィグの変更
とりあえず.configのCONFIG_CROSS_COMPILER_PREFIXとCONFIG_SYSROOTを私の 境に合わせつつ、CROSS_COMPILEを指定してビルドしましたが、カーネルとのインターフェースで不整合が起こりビルドできないファイルがちらほら。
ndkに梱包されている/usr/include/linux/versions/を確かめてみました。
$ grep "LINUX_VERSION_CODE" <path-to-ndk>/platforms/android-19/arch-arm/usr/include/linux/version.h #define LINUX_VERSION_CODE 132626
2.6.12って・・・なんでこんなに古いやつになってるんですかね?
カーネルのバージョンに左右されるようなndkのコードを書くなということでしょうか?
よく分かりません。
カーネルとのインターフェースで不整合が起こるものについては全てコンフィグを無効にします。android_ndk_defconfigに比べて、以下の通りに変更しま した。
diff -uprN .config dot_config --- .config 2014-02-21 16:31:34.449234473 +0900 +++ dot_config 2014-02-21 16:31:14.045130461 +0900 @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Busybox version: 1.23.0.git -# Fri Feb 21 16:31:34 2014 +# Fri Feb 21 16:29:00 2014 # CONFIG_HAVE_DOT_CONFIG=y @@ -63,8 +63,8 @@ CONFIG_FEATURE_SYSLOG=y # CONFIG_FEATURE_INDIVIDUAL is not set # CONFIG_FEATURE_SHARED_BUSYBOX is not set # CONFIG_LFS is not set -CONFIG_CROSS_COMPILER_PREFIX="arm-linux-androideabi-" -CONFIG_SYSROOT="/opt/android-ndk/platforms/android-9/arch-arm" +CONFIG_CROSS_COMPILER_PREFIX="<path-to-ndk>/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin/arm-linux-androideabi-" +CONFIG_SYSROOT="<path-to-ndk>/platforms/android-17/arch-arm" CONFIG_EXTRA_CFLAGS="-DANDROID -D__ANDROID__ -DSK_RELEASE -nostdlib -march=armv7-a -msoft-float -mfloat-abi=softfp -mfpu=neon -mthumb -mthumb-interwork -fpic -fno-short-enums -fgcse-after-reload -frename-registers" CONFIG_EXTRA_LDFLAGS="-Xlinker -z -Xlinker muldefs -nostdlib -Bdynamic -Xlinker -dynamic-linker -Xlinker /system/bin/linker -Xlinker -z -Xlinker nocopyreloc -Xlinker --no-undefined ${SYSROOT}/usr/lib/crtbegin_dynamic.o ${SYSROOT}/usr/lib/crtend_android.o" CONFIG_EXTRA_LDLIBS="dl m c gcc" @@ -187,7 +187,7 @@ CONFIG_CAT=y CONFIG_TEST=y CONFIG_FEATURE_TEST_64=y CONFIG_TOUCH=y -CONFIG_FEATURE_TOUCH_NODEREF=y +# CONFIG_FEATURE_TOUCH_NODEREF is not set CONFIG_FEATURE_TOUCH_SUSV3=y CONFIG_TR=y CONFIG_FEATURE_TR_CLASSES=y @@ -528,12 +528,12 @@ CONFIG_DEFAULT_DEPMOD_FILE="modules.dep" # CONFIG_BLOCKDEV=y CONFIG_FSTRIM=y -CONFIG_MDEV=y -CONFIG_FEATURE_MDEV_CONF=y -CONFIG_FEATURE_MDEV_RENAME=y -CONFIG_FEATURE_MDEV_RENAME_REGEXP=y -CONFIG_FEATURE_MDEV_EXEC=y -CONFIG_FEATURE_MDEV_LOAD_FIRMWARE=y +# CONFIG_MDEV is not set +# CONFIG_FEATURE_MDEV_CONF is not set +# CONFIG_FEATURE_MDEV_RENAME is not set +# CONFIG_FEATURE_MDEV_RENAME_REGEXP is not set +# CONFIG_FEATURE_MDEV_EXEC is not set +# CONFIG_FEATURE_MDEV_LOAD_FIRMWARE is not set CONFIG_REV=y # CONFIG_ACPID is not set # CONFIG_FEATURE_ACPID_COMPAT is not set @@ -852,15 +852,15 @@ CONFIG_FEATURE_TUNCTL_UG=y # CONFIG_FEATURE_UDHCPD_WRITE_LEASES_EARLY is not set # CONFIG_FEATURE_UDHCPD_BASE_IP_ON_MAC is not set CONFIG_DHCPD_LEASES_FILE="" -CONFIG_UDHCPC=y -CONFIG_FEATURE_UDHCPC_ARPING=y -CONFIG_FEATURE_UDHCP_PORT=y -CONFIG_UDHCP_DEBUG=9 -CONFIG_FEATURE_UDHCP_RFC3397=y -CONFIG_FEATURE_UDHCP_8021Q=y -CONFIG_UDHCPC_DEFAULT_SCRIPT="/usr/share/udhcpc/default.script" -CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS=80 -CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS="-R -n" +# CONFIG_UDHCPC is not set +# CONFIG_FEATURE_UDHCPC_ARPING is not set +# CONFIG_FEATURE_UDHCP_PORT is not set +CONFIG_UDHCP_DEBUG=0 +# CONFIG_FEATURE_UDHCP_RFC3397 is not set +# CONFIG_FEATURE_UDHCP_8021Q is not set +CONFIG_UDHCPC_DEFAULT_SCRIPT="" +CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS=0 +CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS="" CONFIG_UDPSVD=y CONFIG_VCONFIG=y CONFIG_WGET=y
5.2. ビルド/インストール
CROSS_COMPILEを指定してビルドとインストールを実行します。
$ CROSS_COMPILE=<path-to-ndk>/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin/arm-linux-androideabi- \ make all install
5.3. 実行
android emulatorでARMのAVDを立ち上げて、作成したbusyboxを転送します。
$ adb shell root@android:/data # mkdir /data/_install root@android:/data # exit $ adb push _install /data/_install push: _install/bin/zcat -> /data/_install/bin/zcat push: _install/bin/yes -> /data/_install/bin/yes <snip> $ adb shell root@android:/data # /data/_install/bin/busybox ls / acct init sbin cache init.goldfish.rc sdcard config init.rc sys d init.trace.rc system data init.usb.rc ueventd.goldfish.rc default.prop mnt ueventd.rc dev proc vendor etc root root@android:/data # /data/_install/bin/ls / acct init sbin cache init.goldfish.rc sdcard config init.rc sys d init.trace.rc system data init.usb.rc ueventd.goldfish.rc default.prop mnt ueventd.rc dev proc vendor etc root
busybox lsと/data/_install/bin/lsが上手く動作することが確認できます。
6. まとめ
busyboxの使い方を簡単にまとめてみました。
androidの/usr/include/version.hが2.6.12相当なのが心残りではありますが・・・。
.configをgithubに置きました。