2010年02月25日
QEMUのもうひとつの使い方: ユーザーモードエミュレーションとbinfmtとchrootの組み合わせ
QEMUの上でDebianなどが動いていると、apt-get で簡単にコンパイラなどもインストールすることができます。しかし、そこで実際にビルドを行うととんでもなく時間がかかります。一晩かけても終わらないこともあります。
QEMUには今まで紹介してきたような、システム全体をエミュレートするものの他に、Linuxのユーザーモードのみをエミュレートするものがあります。今回はユーザーモードエミュレーションを紹介します。
ユーザーモードエミュレーションを利用することでビルドにかかる時間を1/10に短縮することができました。
rootstockに隠された技
前回Ubuntuのインストールの時にルートファイルシステムを構築するのにrootstockというコマンドを使いました。これはshell scriptなので何をやっているのかを見ることができます。
debootstrapというコマンドでルートファイルシステムを構成するパッケージを取ってくるのですが、それぞれのパッケージを配置した後にパッケージ内のスクリプトを必要なものがあります。そのスクリプトをクロス環境で実行するために、QEMUを使っています。
実は同じrootstockでも9.04と9.10では動作が異なります。9.04ではQEMUが必要なところでは全てシステムエミュレーション(qemu-system-arm)を利用しています。9.10では可能な部分ではユーザーモードエミュレーションのqemuを使っています。ユーザーモードエミュレーションのqemuを使うところは/usr/bin/build-arm-chrootというコマンドにまとめられています。これもshell scriptです。
私がなぜこの違いに気がついたかといえば、それは実行時間が明らかに違っていたからです。正確には測っていませんが、同じ規模のルートファイルシステムを作るのに、9.10では9.04の1/3くらいの時間で済みました。
ユーザーモードエミュレーションQEMU + binfmt + chroot
Linuxのbinfmtの仕組みを使うとARMのバイナリを実行しようとしたときに自動的にユーザーモードエミュレーションQEMUを介在させて動かすことができます。この仕組みはWineでWindowsのexeファイルを実行できるようにするときにも使われています。
さらにchrootというコマンドを使うと、あるディレクトリをあたかもルートディレクトリであるかのようにみせかけることができます。
スタティックリンクされたユーザーモードエミュレーションのQEMU
通常のユーザーモードエミュレーションQEMUの実行ファイルは以下のようにダイナミックリンクされています。
$ file /usr/bin/qemu-arm /usr/bin/qemu-arm: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped $ $ ldd /usr/bin/qemu-arm linux-vdso.so.1 => (0x00007fff4ffff000) libm.so.6 => /lib/libm.so.6 (0x00007fab7755f000) libpthread.so.0 => /lib/libpthread.so.0 (0x00007fab77343000) librt.so.1 => /lib/librt.so.1 (0x00007fab7713b000) libc.so.6 => /lib/libc.so.6 (0x00007fab76dc9000) /lib64/ld-linux-x86-64.so.2 (0x00007fab777e4000) $
つまり、/usr/bin/qemu-armを実行するためにはこれらのダイナミックリンクライブラリが必要ですが、ARMだけの実行ファイルがあるルートファイルシステムにchrootしようとすると、これらのライブラリがARMのものと重なってしまいます。
でもこの問題を実にスマートに解決するのが、スタティックリンクされたユーザーモードエミュレーションQEMUを使うことです。それが/usr/bin/qemu-arm-staticです。このファイルをひとつだけchrootする先のディレクトリツリーにコピーしておけば動きます。
$ file /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.15, stripped
bimfmt
ARMのバイナリ用のbimfmtの登録は以下のようになっています。
$ update-binfmts --display arm arm (enabled): package = qemu-arm-static type = magic offset = 0 magic = \x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00 mask = \xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff interpreter = /usr/bin/qemu-arm-static detector = $
chrootでarmのbashを動かす
/export/debian_lenny_armel/root は前回NFSrootのときに作成しました。この下にある/bin/bash はもちろんARMのバイナリです。
$ file /export/debian_lenny_armel/root/bin/bash /export/debian_lenny_armel/root/bin/bash: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.14, stripped $
これをchrootして動かしてみます。Ubuntu 9.10ではsudo apt-get install rootstock とすれば必要な設定は全て完了しています。
$ uname -m x86_64 $ ROOT=/export/debian_lenny_armel/root $ sudo cp /usr/bin/qemu-arm-static $ROOT/usr/bin $ sudo chroot $ROOT /bin/bash # # uname -m armv5tel
chrootの前後でuname -mで表示されるCPUアーキテクチャのタイプが変わっているのがわかります。
実際にconfigureしてビルド。その時間を比較
スーパーユーザーのままビルドを行うのは嫌なので、一般ユーザのアカウントにsu しておきます。
# su user $
今回はbinutilsのソースアーカイブを展開して、それをconfigureしてネイティブビルドしたときの時間を比較してみます。
$ tar xvf binutils-2.20.tar.bz2 $ mkdir obj $ cd obj $ ../binutils-2.20/configure --disable-werror --disable-nls --disable-shared $ time make
最初はシステムエミュレーションのQEMU(qemu-system-arm)です。
ARMのkernelからまるごとひとつのプロセスのひとつのスレッドで動いています。
real 80m40.513s user 46m53.320s sys 28m37.350s
次に、ユーザーモードエミュレーションのQEMU(qemu-arm-static + chroot)
real 28m14.252s user 26m54.345s sys 1m2.736s
実際に使用しているホストのLinuxはクワッドコアなので、makeに-j4オプションを付加してみます。
qemu-arm-static + chroot (make -j4)
real 8m48.824s user 25m15.499s sys 0m56.852s
最初の例で80分かかっていたものが、8分にまで短縮できました。
最後の例でmake -j4 の実行中に他の端末からps uコマンドでプロセスの様子を見てみました。
koba 13651 0.0 0.1 21452 4532 pts/1 Ss+ 11:44 0:00 bash koba 19336 0.8 0.1 129984 4176 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /bin/bash -c r=`${PWDCMD-pwd}`; export r; \?s=`cd ../binutils-2.2 koba 19342 0.3 0.0 130012 3400 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /bin/bash -c r=`${PWDCMD-pwd}`; export r; \?s=`cd ../binutils-2.2 koba 19352 7.8 0.1 129668 4608 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /usr/bin/make DESTDIR= RPATH_ENVVAR=LD_LIBRARY_PATH TARGET_SUBDIR koba 19683 15.8 0.1 129548 4504 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /usr/bin/make all-recursive koba 19684 2.2 0.1 129952 4436 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /bin/bash -c failcom='exit 1'; \?for f in x $MAKEFLAGS; do \? ca koba 19692 20.0 0.1 129700 4648 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /usr/bin/make all-am koba 19706 20.5 0.1 130996 5496 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /bin/bash ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H - koba 19707 21.5 0.1 131012 5516 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /bin/bash ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H - koba 19708 19.5 0.1 130996 5496 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /bin/bash ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H - koba 19709 20.0 0.1 130996 5500 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /bin/bash ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H - koba 19776 0.5 0.0 128056 3380 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /usr/bin/gcc -DHAVE_CONFIG_H -I. -I../../binutils-2.20/bfd -I. -I koba 19778 1.5 0.0 128060 3384 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /usr/bin/gcc -DHAVE_CONFIG_H -I. -I../../binutils-2.20/bfd -I. -I koba 19780 66.5 0.5 144392 20980 pts/15 R+ 21:25 0:01 /usr/bin/qemu-arm-static /usr/lib/gcc/arm-linux-gnueabi/4.4.1/cc1 -quiet -I. -I../../binut koba 19781 1.5 0.0 128056 3384 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /usr/bin/gcc -DHAVE_CONFIG_H -I. -I../../binutils-2.20/bfd -I. -I koba 19783 1.5 0.0 128052 3376 pts/15 S+ 21:25 0:00 /usr/bin/qemu-arm-static /usr/bin/gcc -DHAVE_CONFIG_H -I. -I../../binutils-2.20/bfd -I. -I koba 19784 63.0 0.7 142640 30572 pts/15 R+ 21:25 0:01 /usr/bin/qemu-arm-static /usr/lib/gcc/arm-linux-gnueabi/4.4.1/cc1 -quiet -I. -I../../binut koba 19786 66.0 0.7 142776 30664 pts/15 R+ 21:25 0:01 /usr/bin/qemu-arm-static /usr/lib/gcc/arm-linux-gnueabi/4.4.1/cc1 -quiet -I. -I../../binut koba 19787 64.0 0.5 143956 20976 pts/15 R+ 21:25 0:01 /usr/bin/qemu-arm-static /usr/lib/gcc/arm-linux-gnueabi/4.4.1/cc1 -quiet -I. -I../../binut koba 19788 0.0 0.0 15436 1140 pts/16 R+ 21:25 0:00 ps u
圧巻ですね。bashやgccなどがqemu-arm-static を介して動いている様子がよくわかります。
参考までに、binutils-2.20を同じLinuxマシンでネイティブビルドした時の時間は以下の通りです。(make -j4) この差分がqemu-arm-static によるオーバーヘッドと見ることができます。
real 0m36.680s user 1m16.541s sys 0m27.254s