Linux を(わりと)シンプルな構成でビルドして Qemu で起動する


はじめに

  • 唐突に小さいカーネルを何となく作りたくなりました(。>﹏<。)
  • そこで(わりと)シンプルな kernel config で Linux カーネルを作れるか試してみました
  • まったり進行です

前提

  • ビルド環境は Ubuntu 14.04 64 bit を使っています
    • 64 bit 環境で 32 bit のカーネルをビルドする、とか需要あるのかしら。
    • あれば書こうかなぁ。
  • init は自作の Hello, world で代用します
  • ディスクイメージ(qcow2とか)に焼く方法は書いてません。qemu の -kernel オプションと -initrd オプションを使ってビルドしたカーネルを起動します
    • そのうち書くかも

ざっくりとした流れ

  1. qemu をインストールして
  2. カーネルを設定して、ビルドして
  3. initramfs を作って
  4. qemu で起動(∩´∀`)∩

下準備

使うツールのインストール

  • qemu と カーネルの設定をする際に使う make menuconfig で利用される libcurses 群を入れます
$ sudo apt-get install qemu
$ sudo apt-get install libncurses5 libncurses5-dev

カーネルのダウンロードと解凍

$ wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.4.4.tar.xz
$ tar Jxvf linux-4.4.4.tar.xz
$ cd linux-4.4.4

カーネルの設定

カーネルに必要最低限なコンフィグのみ残す

$ make allnoconfig
  • どれくらい行数があるか気になったのでカウントしてみました
$ egrep -v "(^#|^$)" .config | wc -l
234
  • 結構ありますね(* ´﹃` *)

qemu で起動するために必要なコンフィグを有効にする

  • make menuconfig を使います。
$ make menuconfig
  • 下の項目を有効にしていきます。
[*] 64-bit kernel

-> General setup
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
-> General setup
  -> Configure standard kernel features
[*] Enable support for printk

-> Executable file formats / Emulations
[*] Kernel support for ELF binaries

-> Device Drivers
  -> Character devices
[*] Enable TTY

-> Device Drivers
  -> Character devices
    -> Serial drivers
[*] 8250/16550 and compatible serial support
[*]   Console on 8250/16550 and compatible serial port
設定項目 何に使うか
64-bit kernel 今回は 64 bit kernel をビルドするので必要です
Initial RAM filesystem and RAM disk (initramfs/initrd) support initramfs/initrd を有効にします。qemu から -initrd オプションで起動させるためです。これを有効にしないでやるケースってどんなときなんでしょうね。
Enable support for printk printk 出力を有効にします。カーネルの起動ログをコンソールに出すようにするためです。
Kernel support for ELF binaries ELF を読み込めるようにします。initrd に指定するプログラムが ELF binary なためです。
Enable TTY TTY を有効にします。ターミナルに文字を出力させるためです。
Console on 8250/16550 and compatible serial port シリアルポートを有効にします。カーネルの出力を見られるようにするために必要です
  • ここでコンフィグの行数をカウントしてみます。
$ egrep -v "(^#|^$)" .config | wc -l
311
  • make allnoconfig が 234 だったので、思ったより増えてますね。

カーネルのビルド

  • -j オプションはマルチコアの場合に並列ビルドをします。任意で指定してくださいませ。
$ make -j4 bzImage
  • ビルドを待ちます。この時にビルド対象のコード一覧を見てみるのも楽しいかもしれません
  • bzImage ができていることを確認します。今回は 64 bit kernel なので  ./arch/x86/boot/bzImage を使います
$ find ./ -name bzImage
./arch/i386/boot/bzImage
./arch/x86/boot/bzImage
  • カーネルができあがったので、次に initramfs を作ります。

initramfs の作成

下準備(作業ディレクトリの作成とか)

$ mkdir ~/work
$ cd ~/work
$ cp ~/linux-4.4.4/arch/x86/boot/bzImage bzImage

init で実行するプログラムの作成

  • おもむろに vim を立ち上げて init.c ファイルを作ります
$ vim init.c
  • 以下のようなコードを書きます
#include <stdio.h>

int main(void) {
  printf("-----------HELLO------------\n");
  return 0;
}
  • libc をプログラム本体に含めるために static ビルドをします
$ gcc -static -o init init.c

プログラムを gzip 圧縮の initramfs 形式にする

  • cpio コマンドがパイプでプログラム名を受け取れるため、以下のようなコマンドを実行して、initramfs を作ります
$ echo init | cpio -o -H newc | gzip > initramfs.cpio.gz

カーネルの起動

  • これですべての準備が整いました。以下のコマンドを実行してカーネルを立ち上げてみてください
$ qemu-system-x86_64 -kernel ./bzImage -initrd ./initramfs.cpio.gz -append "console=ttyS0" -nographic
  • 以下のような感じでカーネルの起動ログが出て、作成したプログラムの HELLO という文字が出力されていると思います
    • panic してますが、とりあえずここまで。
(省略)
mousedev: PS/2 mouse device common for all mice
input: AT Translated Set 2 keyboard as /devices/platform/i8042/serio0/input/input0
Freeing unused kernel memory: 440K (ffffffff81175000 - ffffffff811e3000)
-----------HELLO------------
Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000

Kernel Offset: disabled
---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000