MirageOS のインストールから Hello World までを試す

## 概要
本記事では[MirageOS Unikernel](https://mirage.io/) 環境を仮想化支援機構(on x86_64 or aarch64 アーキ)が有効化されたLinuxマシンへインストールし、MirageOS Unikernelの第一歩であるHello Worldアプリ実行までを説明していきます。

Ubuntu18.04LTS(amd64 or aarch64) がインストールされた物理マシンが用意されていることを想定します。(Intel VT-x もしくは AMD-V の Nested が有効化された仮想マシン上のUbuntuでも動作するはずですが、実機で試しておらず...)。

MirageOS Unikernel のアプリケーション([Hello Worldアプリ](https://github.com/mirage/mirage-skeleton/tree/master/device-usage/console))を、Linux KVM(Kernel Virtual Machine) を利用して起動させます。

(参考サイト)
[MirageOS Documentation and guides - Installation](https://mirage.io/wiki/install)
[Building a MirageOS hello world](https://mirage.io/wiki/hello-world)

## 0. CPU仮想化支援機構のチェック
#####Intel社 もしくは AMD社製のx86系CPUを利用している場合
`lscpu`コマンドを用いて仮想化支援機構が有効化されているか確認します。`lscpu`コマンドがない場合には、インストールします。

```:lscpuコマンドのインストール
$ apt-get install util-linux
```
`lscpu`コマンドを実行すると、CPU仮想化支援機構が有効化されている場合には "Virtualization" という項目の行に "VT-x" もしくは "AMD-V" と表示されているはずです。
(もし表示されていない場合には、[こちら](https://docs.fedoraproject.org/ja-JP/Fedora/13/html/Virtualization_Guide/sect-Virtualization-Troubleshooting-Enabling_Intel_VT_and_AMD_V_virtualization_hardware_extensions_in_BIOS.html)などを参考にBIOS設定を確認する必要があります。)

```bash:lscpu実行結果
$ LC_ALL=C lscpu | grep Virtualization
Virtualization:        VT-x    # Intel社製CPU
もしくは
Virtualization:        AMD-V   # AMD社製CPU
```

さらに、Ubuntu が提供しているx86_64用カーネルはLinux KVMがモジュール化されているため、`modprobe`コマンドを実行して読み込んでおきます。

```bash:kvmモジュール読み込み
# for Intel社製CPU
$ modprobe kvm
$ modprobe kvm_intel

# for AMD社製CPU
$ modprobe kvm
$ modprobe kvm_amd
```

##### ARMアーキテクチャCPUの場合
手元にあった Raspberry Pi 3 に Ubuntu18.04LTS(aarch64) をインストールしてみたところ、`lscpu`コマンドでは上手く "Virtualization" が表示されませんでした。代わりに、`dmesg`コマンドの結果からCPU仮想化支援機構が有効化されていることを確認します。
"Hyp mode initialized successfully" と表示されれば有効化されています。

```:dmesg実行結果
$ dmesg | grep kvm
[    4.399887] kvm [1]: 8-bit VMID
[    4.405636] kvm [1]: Invalid trigger for IRQ4, assuming level low
[    4.410233] kvm [1]: Hyp mode initialized successfully
```

## 1. 依存関係のあるパッケージのインストールと環境設定
MirageOS Unikernel は [OCaml](https://ocaml.org/) という関数型言語でアプリケーションレイヤをユーザが記述するため、コンパイラと管理ツールが必要です。コンパイラは"ocaml"、管理ツールは"opam"というパッケージ名です。

特に、Ubuntu18.04LTSのデフォルトで提供されるopamパッケージはバージョンが1.2.2と古いため、外部のリポジトリ [ppa:avsm/ppa](https://launchpad.net/~avsm/+archive/ubuntu/ppa) を利用してバージョン2.0.0以降がインストールされるようにします。

他にも必要なパッケージがあるため、ここでインストールしておきます。

```bash:パッケージインストール
$ sudo add-apt-repository ppa:avsm/ppa   # 外部リポジトリ利用
$ sudo apt-get update
$ sudo apt-get install opam ocaml gcc make bubblewrap m4 pkg-config
```
次に、`opam`コマンドとOcamlコンパイラに関わる設定をします。具体的には、`opam init`コマンドでopam環境を初期化し、.bashrc(もしくは.bash_profile)ファイルに `` eval `opam config env` ``を追加します。

```:opam環境の初期化
$ opam init
```
```:.bashrc(.bash_profile)の中身
$ tail ~/.bashrc
...
...
eval `opam config env`
```

次のステップに進むには、一度ログアウトして上記.bashrcファイルの変更を有効化させるか、または`` eval `opam config env` ``を実行しておきます。

なお、この`opam`コマンドはOCaml環境を簡潔に管理するためのツールであり、異なるOCamlコンパイラのバージョンに切り替えたり、OCaml向けライブラリのインストール/アンインストール(Pythonにおける`pip`コマンドと同じ仕組みです)など色々な機能を持っています。`opam`コマンドについての詳細な説明は[こちら](https://opam.ocaml.org/)。

```:インストール済みOCamlコンパイラの表示
$ opam switch list
#  switch  compiler                    description
→  4.07.0  ocaml-base-compiler.4.07.0  4.07.0
   system  ocaml-system.4.05.0
```
```:インストール済みライブラリの表示
$ opam list
# Packages matching: installed
# Name                    # Installed # Synopsis
astring                   0.8.3       Alternative String module for OCaml
base                      v0.11.1     Full standard library replacement for OCaml
...
...
tcpip                     3.5.1       OCaml TCP/IP networking stack, used in MirageOS
topkg                     1.0.0       The transitory OCaml software packager
uchar                     0.0.2       Compatibility library for OCaml's Uchar module
```

## 2. MirageOS Unikernel のインストール
実のところ、本ステップはMirageOS Unikernel自体のインストールではありません。MirageOS Unikernelはユーザが記述するアプリケーションコードがあって初めてコンパイルできるものなので、ここでのインストールは「ユーザ記述のアプリケーションコードをコンパイルするためのプログラムやモジュール、ライブラリのインストール」のことを指します。

インストール作業はとても簡単です。下記のコマンドを実行して、mirageパッケージをインストールするだけです。1. にて紹介した`opam`コマンドを早速利用します。

```:mirageパッケージインストール
$ opam install mirage
```
`mirage`コマンドを使えるようになりました。

```:mirageコマンド実行
$ mirage
NAME
       mirage - The mirage application builder

SYNOPSIS
       mirage COMMAND ...
...
...
```

## 3. Hello World な Mirage Unikernel アプリケーションのコンパイルと実行
Hello World な Mirage Unikernel アプリケーションのコードは、サンプルがあるので github から clone します。

```:mirage-skeletonのclone
$ git clone https://github.com/mirage/mirage-skeleton.git
```
この "mirage-skeleton" というリポジトリには、Hello World 以外にもDHCPサーバアプリなど幾つかのアプリケーションコードが登録されています。

次に、Hello World アプリケーションコードのあるディレクトリへ移動します。`config.ml` と `unikernel.ml`という2つのファイルがあります。前者は構成ファイル、後者はアプリケーションコードファイルです。

```:helloディレクトリ
$ cd ./mirage-skeleton/tutorial/hello
$ ls
config.ml  unikernel.ml
```
アプリケーションソースコード(unikernel.ml)を見てみると、 "hello"文字列を出力するための `Logs.info()`関数が`loop()`再帰関数によって4回実行されるように記述されています。

```ocaml:unikernel.ml
open Lwt.Infix

module Hello (Time : Mirage_time_lwt.S) = struct

  let start _time =

    let rec loop = function
      | 0 -> Lwt.return_unit
      | n ->
        Logs.info (fun f -> f "hello");
        Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
        loop (n-1)
    in
    loop 4

end
```

コンパイルしてみます。コンパイルまでには3コマンド実行します。

```bash:コンパイルまでの3コマンド(その1)
# ターゲットプラットフォームの設定(ここでは hvt) と コンパイルに必要なファイルの生成
$ mirage configure -t hvt
$ ls
_build  config.ml  key_gen.ml  main.ml  Makefile  mirage-unikernel-hello-hvt.opam  myocamlbuild.ml  unikernel.ml
```
```bash:コンパイルまでの3コマンド(その2)
# 依存関係のあるパッケージのインストール
$ make depend
```
```bash:コンパイルまでの3コマンド(その3)
# MirageOS Unikernel アプリケーションバイナリの生成 と 仮想マシン(hvt)バイナリの生成
$ make
$ ls
_build            config.ml  key_gen.ml  Makefile            mirage-unikernel-hello-hvt.opam  solo5-hvt
_build-solo5-hvt  hello.hvt  main.ml     Makefile.solo5-hvt  myocamlbuild.ml                  unikernel.ml
```
ここで、`hello.hvt` は MirageOS Unikernel アプリケーションバイナリファイルであり、`solo5-hvt` は仮想マシンバイナリファイルです。この2つを用いて、Linux KVMを利用して仮想CPU上でアプリケーションバイナリを実行させます。

このとき、Linux KVM利用のために `/dev/kvm`ファイルをオープンする必要があるため、 `sudo` コマンド経由での実行となります。`sudo` コマンドでの実行が不便な場合は `/dev/kvm` ファイルにreadとwriteのユーザ権限を付与してください(もちろん自己責任の範囲で...)。

```:アプリケーション実行
$ sudo ./solo5-hvt ./hello.hvt
            |      ___|
  __|  _ \  |  _ \ __ \
\__ \ (   | | (   |  ) |
____/\___/ _|\___/____/
Solo5: Memory map: 512 MB addressable:
Solo5:     unused @ (0x0 - 0xfffff)
Solo5:       text @ (0x100000 - 0x20efff)
Solo5:     rodata @ (0x20f000 - 0x236fff)
Solo5:       data @ (0x237000 - 0x303fff)
Solo5:       heap >= 0x304000 < stack < 0x20000000
2019-01-11 16:42:35 -00:00: INF [application] hello
2019-01-11 16:42:36 -00:00: INF [application] hello
2019-01-11 16:42:37 -00:00: INF [application] hello
2019-01-11 16:42:38 -00:00: INF [application] hello
Solo5: solo5_exit(0) called
```
無事に "hello" という文字列が4回出力されました!!
なお、アスキーアートと"Solo5:"で始まる文字列は仮想マシン(hvt)側のログ出力です。

## 4. コードの構成を理解したいときは...
[こちら](https://qiita.com/t-imada/items/5d0149587d36c6051ed3)にネットワークアプリケーションを題材とした記事がありますので、参考にしてみてください。