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 群を入れます

```bash
$ sudo apt-get install qemu
$ sudo apt-get install libncurses5 libncurses5-dev
```

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

```bash
$ 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|シリアルポートを有効にします。カーネルの出力を見られるようにするために必要です|

* ここでコンフィグの行数をカウントしてみます。

```bash
$ egrep -v "(^#|^$)" .config | wc -l
311
```

* make allnoconfig が 234　だったので、思ったより増えてますね。

# カーネルのビルド

* ```-j``` オプションはマルチコアの場合に並列ビルドをします。任意で指定してくださいませ。

```
$ make -j4 bzImage
```

* ビルドを待ちます。この時にビルド対象のコード一覧を見てみるのも楽しいかもしれません
* bzImage ができていることを確認します。今回は 64 bit kernel なので　 ./arch/x86/boot/bzImage を使います

```bash
$ find ./ -name bzImage
./arch/i386/boot/bzImage
./arch/x86/boot/bzImage
```

* カーネルができあがったので、次に initramfs を作ります。

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

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

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

* おもむろに vim を立ち上げて init.c ファイルを作ります

```bash
$ vim init.c
```

* 以下のようなコードを書きます

```c
#include <stdio.h>

int main(void) {
  printf("-----------HELLO------------\n");
  return 0;
}
```

* libc をプログラム本体に含めるために static ビルドをします

```bash
$ gcc -static -o init init.c
```

## プログラムを gzip 圧縮の initramfs 形式にする
* cpio コマンドがパイプでプログラム名を受け取れるため、以下のようなコマンドを実行して、initramfs を作ります

```bash
$ echo init | cpio -o -H newc | gzip > initramfs.cpio.gz
```

# カーネルの起動

* これですべての準備が整いました。以下のコマンドを実行してカーネルを立ち上げてみてください

```bash
$ 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
```