UMLによるはじめてのLinuxカーネルHack

はじめに

これまでにLinuxカーネルの解説本やカーネルソースコードを読んでみたものの、
実際にカーネルをビルドしてブートする、という基本的なことを避けてきた。
Linuxカーネルをいじれるようになる(なりたい)、という観点で真剣に考えると、カーネル
ビルドとビルドしたカーネルをブートするという行為は避けて通れない。
いじった結果を観測できないから。フィードバックが無ければやる気も維持できない。
何が障壁になっているのだろう?と考えると、自分でビルドしたカーネル
Linuxにインストールして、ブートできなくなったらどうするの?面倒すぎる。
というのが一番の障壁になっていることがわかった。
この障壁を解決するいい方法は無いだろうか?と考えると、ちょうどいいのがあった。
それがUML(User Mode Linux)だった。
UMLを使えば、ビルドしたカーネルが何らかの原因でブートできなくなっても
大丈夫。大本のLinuxが、フリーズすることは無い。単にやり直せばいい。
UML上でカーネル開発をすると、試行錯誤に要するコスト(時間、精神的イライラ)が、
UMLを使わない場合と比較して、圧倒的に小さい。これがカーネル開発における、
UMLを用いる最大のメリットだと思う。

今回は、UbuntuUMLを構築してみる。UML構築後、カーネルをいじる。
具体的には、printkを用いて、ブートメッセージに「Hello, World!」を出力してみる。
なお、用いたUbuntuは8.0.4。WindowsVMWare Server 2上で長年使っているVMで、アップデートしていないので古い。

カーネルソースコードの取得

gitでカーネルソースコードを取得する。今、まさに開発中のコードをビルドした方が面白いので、そうした。cloneするのに結構時間がかかる。

% cd ~
% git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git linux-2.6

UMLカーネルのビルド

UML本家にあるUMLのビルド方法のドキュメントを見ながらUMLカーネルをビルドする。
http://user-mode-linux.sourceforge.net/source.html

ビルドは5分くらいで完了した。

% cd ~/linux-2.6
% make defconfig ARCH=um
% make ARCH=um
% ls -l linux 
-rwxr-xr-x 2 xxx xxx 25516656 2010-08-29 00:10 linux

UML環境構築

以下のドキュメントを見ながらUML環境を構築する。
https://help.ubuntu.com/community/UserModeLinux

UMLに必要なツールを用意。

% sudo apt-get install user-mode-linux uml-utilities bridge-utils debootstrap

以降、~/uml-ubuntuUML環境を構築していく。

% mkdir ~/uml-ubuntu
% cd ~/uml-ubuntu

4GBのrootファイルシステムを作成。mkfs.xfsコマンドが無かったので、mkfs.ext3で代用。

% dd if=/dev/zero of=uml-root-hardy bs=4096 seek=1M count=1
% mkfs.ext3 uml-root-hardy

作成したファイルシステムを/mntにマウント。

% sudo mount -o loop uml-root-hardy /mnt

残りの作業はrootユーザーで作業する。

% sudo -i

マウントしたパーティションにベースシステムを構築。
ネットからパッケージをダウンロードしてくるので、数十分要した。

# debootstrap --arch i386 hardy /mnt http://us.archive.ubuntu.com/ubuntu

fstabに以下を追記。

# echo "/dev/ubd0  /       xfs     defaults     0 1" > /mnt/etc/fstab
# echo "proc       /proc   proc    defaults     0 0" >> /mnt/etc/fstab

rootファイルシステム用のデバイスファイルの作成とパーミッションの調整。

# mknod --mode=660 /mnt/dev/ubd0 b 98 0
# chown root:disk /mnt/dev/ubd0

hostsファイルに以下を追記。

# echo "127.0.0.1 localhost" > /mnt/etc/hosts

ネットワークインタフェースを設定。

# echo "auto lo" > /mnt/etc/network/interfaces
# echo "iface lo inet loopback" >> /mnt/etc/network/interfaces
# echo "auto eth0" >> /mnt/etc/network/interfaces
# echo "iface eth0 inet dhcp" >> /mnt/etc/network/interfaces 

Add first terminal and serial terminal to secure list.
何をしているのか理解できてない。

# echo "tty0" >> /mnt/etc/securetty
# echo "ttys/0" >> /mnt/etc/securetty

Remove the other terminal events.
これも何をやっているのか理解できていない。

# rm /mnt/etc/event.d/tty2
# rm /mnt/etc/event.d/tty3
# rm /mnt/etc/event.d/tty4
# rm /mnt/etc/event.d/tty5
# rm /mnt/etc/event.d/tty6
# rm /mnt/etc/udev/rules.d/75-persistent-net-generator.rules

以上で完了と言いたいが、/mntをアンマウント。ドキュメントには書いてないけど、これをやっておかないと、UMLをブートできない。

# umount /mnt

UMLの起動

以下のコマンドでUMLを起動する。uml-root-hardy.cow1にuml-root-hardyのファイルサイズと同じ4GBを消費するので注意。

% cd ~/uml-ubuntu
% ~/linux-2.6/linux ubd0=uml-root-hardy.cow1,uml-root-hardy umid=uml1

いくつかエラーメッセージが出ているが、今回はUMLが起動することを目標としたので、ひとまず無視。
Virtual Console #1(uml1)にログイン画面が表示された。
rootでログインできた。

UMLをシャットダウン。

# shutdown -h now

ひとまず、やりたかったことはできた。

ブートメッセージでHello, World!

詳解LINUXカーネル第3版(オライリー)によると、カーネルのブート時にstart_kernel()が実行されるらしい。
start_kernel()を眺めてみると、ブート時に表示されるカーネルのバナーのコードがあった。
(引用したコードの最後のprintk。)

% cat init/main.c
[...]
asmlinkage void __init start_kernel(void)
{
        char * command_line;
        extern const struct kernel_param __start___param[], __stop___param[];

        smp_setup_processor_id();

        /*
         * Need to run as early as possible, to initialize the
         * lockdep hash:
         */
        lockdep_init();
        debug_objects_early_init();

        /*
         * Set up the the initial canary ASAP:
         */
        boot_init_stack_canary();

        cgroup_init_early();

        local_irq_disable();
        early_boot_irqs_off();
        early_init_irq_lock_class();

/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
        tick_init();
        boot_cpu_init();
        page_address_init();
        printk(KERN_NOTICE "%s", linux_banner);
[...]

バナーの後に、「Hello, World!」を表示するようにしよう。diffは以下の通り。
今気づいたけど、git diffはsvn diffと違って、どの関数でのdiffなのかわかるようになっている。これは良い。
(@@ -566,6 +566,7 @@ asmlinkage void __init start_kernel(void)の所。)

% git diff
diff --git a/init/main.c b/init/main.c
index 94ab488..bf13e5e 100644
--- a/init/main.c
+++ b/init/main.c
@@ -566,6 +566,7 @@ asmlinkage void __init start_kernel(void)
        boot_cpu_init();
        page_address_init();
        printk(KERN_NOTICE "%s", linux_banner);
+       printk(KERN_NOTICE "Hello, World!");
        setup_arch(&command_line);
        mm_init_owner(&init_mm, &init_task);
        setup_command_line(command_line);

Hello, World!を仕込んだカーネルをビルドする。クリーンしてビルドしていないので、1分くらいでビルドできた。

% cd ~/linux-2.6
% make ARCH=um
$ ls -l linux
-rwxr-xr-x 2 xxx xxx 25516656 2010-08-29 08:36 linux

UMLを起動する。バナーの後に、無事、「Hello, World!」が表示された。

% ~/linux-2.6/linux ubd0=uml-root-hardy.cow1,uml-root-hardy umid=uml1
Locating the bottom of the address space ... 0x10000
Locating the top of the address space ... 0xc0000000
Core dump limits :
	soft - 0
	hard - NONE
Checking that ptrace can change system call numbers...OK
Checking syscall emulation patch for ptrace...OK
Checking advanced syscall emulation patch for ptrace...OK
Checking for tmpfs mount on /dev/shm...OK
Checking PROT_EXEC mmap in /dev/shm/...OK
Checking for the skas3 patch in the host:
  - /proc/mm...not found: No such file or directory
  - PTRACE_FAULTINFO...not found
  - PTRACE_LDT...not found
UML running in SKAS0 mode
Linux version 2.6.36-rc2-00237-gd4348c6-dirty (xxx@ubuntu-vm) (gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu3)) #4 Sun Aug 29 08:36:51 JST 2010
Hello, World!

おわりに

UMLを利用することで、Linuxカーネルをハックするという目的を達成できた。
問題は、これから、いかに継続してハックに情熱を注げるか。
継続しないことには、結局は意味が無い。

今回の一番の収穫は、UMLを使えば、Linuxカーネルを試行錯誤していじれる環境が
簡単に作れるということがわかったこと。

UMLLinuxカーネル開発の敷居を低くしていることに気づいた人が、UMLな環境で、
継続してカーネルをいじり続ければ、面白い人材が育つ気がする。

これまでLinuxカーネルに興味があったけれども、中々踏み出せなかった人にとっては、UMLは絶好のチャンスだと思う。