Linuxカーネルのテスト実行とデバッグ (1) :QEMU編

2017年7月20日

Linuxカーネルに新機能を追加したり、デバッグなどの変更をしたら、動作を確認するテストを実行するわけですが、実機で実行しようとするといろいろ面倒です。そこでQEMUの登場です。QEMUはシステムエミュレータの1つで、x86 PCをはじめARM, SPARC, MIPS, PPCなど、いろいろなプロセッサアーキテクチャをサポートした非常に高機能なエミュレータです。また、KVMでデバイスをエミュレーションする用途でも使われています。

QEMUをカーネルの開発に使うことには以下のメリットがあります。

  • 開発用PCをいちいちリブートしたり、またはもう1台テスト用のPCを用意したりする必要が無い。
  • ビルドしたカーネルをすぐに実行できるため、変更・ビルド・テストのサイクルを高速にまわせる。
  • デバッグメッセージやパニック時のメッセージをターミナルで見れ、必要ならばファイルに保存できる。

手間がかからずに、ビルドしたカーネルを試せるのは、非常に便利です。

今回は、QEMUでLinuxカーネルを実行する環境を構築するところまでを見ていきたいと思います。

QEMUの用意

http://www.qemu.org/download/ からバイナリまたはソースコードダウンロードするか、https://github.com/qemu/qemu からcloneします。ソースコードをダウンロード (またはgit clone) した場合はコンパイルする必要がありますが、自分の用途に合わせて configure 可能です。

x86_64のLinuxカーネルだけ実行できれば十分という場合は、例えば以下のように configure を実行します。

% ./configure --target-list=x86_64-softmmu --enable-kvm --enable-vnc --disable-vnc-sasl

softmmuというのが、カーネルを実行可能なプロセッサエミュレータを指定するオプションになります。コンパイルするために必要なライブラリが足りないと configure がエラーになりますので、足りないものを追加するか、必要ないと思ったらエラーを出したオプションをdisableします。上記の例では、コンソールをVNCで表示可能にするオプションはenableするけど、saslでの認証はdisableしています。

configure できたら make するだけです。

% make -j

コンパイルが終了したら x86_64-softmmu/qemu-system-x86_64 が出来ています。

Linuxカーネルのコンフィグとビルド

当然ですが、LinuxカーネルをQEMUがサポートするデバイスに合わせて、また必要ないデバイスや機能は外してコンフィグすれば、コンパイルする必要のあるファイルが減りますので、ビルドの時間も短くなります。QEMU がサポートするデバイスはドキュメンテーション http://www.qemu.org/documentation/ に書かれています。また、QEMUでお手軽にLinuxカーネルを実行するには、ローダブルモジュールは使わず、vmlinuxに全部リンクしてしまいましょう。

QEMU上でのLinuxカーネルの実行

Linuxカーネルがビルドできたら早速実行してみましょう。

% qemu/x86_64-softmmu/qemu-system-x86_64 -m 256 -nographic -kernel linux-stable/arch/x86_64/boot/bzImage -append console=ttyS0

オプションは -m の後にはメモリサイズ (MB)、-kernel の後にカーネル (bzImage) を指定します。今回は、ターミナルにカーネルからのメッセージが表示されるようにシリアルコンソールを使用します。そのために、QEMUのオプションして -nographic を指定し、Linuxカーネルの方には console=ttyS0 が渡されるように -append の後に付けておきます。

[ 0.000000] Linux version 4.4.76 (shui@blazar) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) ) #1 SMP Fri Jul 14 19:09:12 JST 2017
[ 0.000000] Command line: console=ttyS0
...
[ 0.781373] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[ 0.782059] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.4.76 #1
[ 0.782059] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.10.2-0-g5f4c7b1-prebuilt.qemu-project.org 04/01/2014
[ 0.782059] 0000000000000000 ffff88000ecabdf8 ffffffff8120d92f ffffffff814bd790
[ 0.782059] ffffea00003bf780 ffff88000ecabe70 ffffffff810c861f ffff880000000010
[ 0.782059] ffff88000ecabe80 ffff88000ecabe20 61200035316d6172 ffff88000ecabe88
[ 0.782059] Call Trace:
[ 0.782059] [] dump_stack+0x4d/0x6e
[ 0.782059] [] panic+0xbd/0x1d4
[ 0.782059] [] mount_block_root+0x1fd/0x287
[ 0.782059] [] mount_root+0x67/0x6a
[ 0.782059] [] prepare_namespace+0x167/0x19f
[ 0.782059] [] kernel_init_freeable+0x192/0x1a0
[ 0.782059] [] ? initcall_blacklist+0xa7/0xa7
[ 0.782059] [] ? rest_init+0x80/0x80
[ 0.782059] [] kernel_init+0x9/0xe0
[ 0.782059] [] ret_from_fork+0x3f/0x70
[ 0.782059] [] ? rest_init+0x80/0x80
[ 0.782059] Kernel Offset: disabled
[ 0.782059] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

ブートに失敗しました。QEMUを終了するには Ctrl-A x を押します。ちなみに、Ctrl-A h と押すとヘルプが表示され、Ctrl-A c と押すと QEMU monitor に入ります。QEMU monitor では、レジスタダンプやメモリダンプ、その他プロセッサの情報を取得することができます。

ブートメッセージには Unable to mount root fsと表示されています。ストレージデバイスを指定していませんので、ルートファイルシステムがないわけですね。

Initrdの作成

initrd (initramfs) は、Linuxカーネルが起動直後にメモリ上に読み込み(その名の通り)ラムディスクに展開するファイルシステムのイメージです。ブートローダが、カーネルと一緒に読み込みメモリ上に展開するので、カーネルが起動直後に使用可能になります。様々なストレージのためのデバイスドライバやファイルシステムを全てカーネルに組み込むとカーネルが巨大化してしまいますので、ターゲットシステムのルートファイルシステムをマウントするために必要となるドライバ類はinitrdが提供するようにすると、カーネル自体は必要最小限に抑えることができるようになります。

通常は、本来のストレージ上のルートファイルシステムをマウントした段階で、initrdが提供した仮のルートファイルシステムは不要になります。しかしながら、テスト用のプログラムを入れておき、実行することもできますので、ここではその目的のために使用します。

initrdのイメージは、その内容としたいディレクトリ構造をcpioでアーカイブし、gzipにより圧縮したものです。そこで、initrdを作成するためには、まず適当なディレクトリを作成し、initrdの内容とするファイルを集めます。例えば、シェルとしてbusyboxを使用することにすると、そのディレクトリをインストール先にします。以下は、そのようにして作成したものになります。

% ls
bin/   etc/   init*  lib64/ lib@   sbin/  usr/   

libはlib64へのシンボリックリンクです。initがinitrdの内容をマウント直後に実行するプログラムとなります。例えば以下の内容にします。

#!/bin/sh
[ -d /dev ] || mkdir -m 0755 /dev
[ -d /proc ] || mkdir -m 0755 /proc
[ -d /sys ] || mkdir -m 0755 /sys
echo "Mounting filesystems."
mount -a
mkdir /dev/pts
mount /dev/pts
exec /sbin/init

/sbin/init は busybox へのシンボリックリンクです。mount -a で /dev, /proc, /sys をマウントできるように、以下のetc/fstabも用意しておきます。

#
proc	/proc	proc	nodev,noexec,nosuid	0 0
none	/sys	sysfs	nodev,noexec,nosuid	0 0
none	/dev	devtmpfs	mode=0755	0 0
none	/dev/pts	devpts	noexec,nosuid,noauto	0 0

このディレクトリで以下のコマンドを実行するとinitrd.imgというファイル名でinitrdを作成できます。

% find . | cpio --quiet -H newc -o | gzip > ../initrd.img

QEMUにinitrdのファイルイメージを読み込ませるには -initrd オプションでinitrdファイルを指定します。

% qemu/x86_64-softmmu/qemu-system-x86_64 -m 256 -nographic -kernel linux-stable/arch/x86_64/boot/bzImage -append console=ttyS0 -initrd initrd.img

これでカーネルをブートすると、以下のようにカーネルがブート後にinitrdがマウントされシェルが使える状態になります。

Mounting filesystems.
can't run '/etc/init.d/rcS': No such file or directory
Please press Enter to activate this console.
/ # ls
bin dev etc init lib lib64 proc root sbin sys usr
/ #

/etc/init.d/rcSがないと怒られていますが、initの内容を移せば表示されなくなります。

おわりに

今回は、Linuxカーネルのテスト実行を行うために、QEMUを用いる方法を紹介しました。ルートファイルシステムをinitrdとすることで、気軽にカーネルをブート、テスト実行、終了することができます。次回は、QEMUにGDBを接続してみましょう。

About Author

追川 修一

1996年慶應義塾大学より博士(工学).2004年筑波大学大学院システム情報工学研究科助教授,2016年筑波大学システム情報系教授を経て,現在,株式会社フィックスターズに勤務.システムソフトウェア等に関する研究開発に従事.

Leave a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Recent Comments

Social Media