livaの雑記帳

livaの雑記帳

OSとか作ってみたい

Unikernelのすゝめ

 以下の記事を読んだ。

Single address spaces: design flaw or feature?

 

UnikernelというとMirageOSの論文で最初に出てきた概念で、Rump KernelにもUnikernelで動かすモードがあるのだけど、今回はRump Kernelの事は忘れて、本来のUnikernelの話。(このリポジトリにはMirageOSに関する話もあるので、完全にそっち系の人が書いている様子)

 

まとめると、

 

・ユーザー空間とカーネル空間の切り替えコストは大きい

 ・キャッシュ汚染するため

 ・システムコールの仕組みを変えて、切り替えコストを削減するだけで効果的という研究有り

システムコールの切り替えコストが大きい事は、性能上の制約となる

 ・10Gのパケットの処理の度にシステムコールを発行すると、追いつかない

 ・DPDK(ry

・unikernelなら、アプリケーションとカーネルが共通のメモリ空間(同じユーザー空間上のメモリ)に置かれるので、システムコール発行コストが無くなって、早い

 ・言語が提供する型安全、メモリ保護を用いて、カーネル空間が破壊されない事を保証する

 

なるほど、という感じ。

Rump Kernelsだとkernelとアプリケーションって別々のメモリ空間になるのかな?どうなんだろう。要調査

Rump Kernelを実機で動かす

Unikernelをまだ動かしてなかったので、動かす。

↓これ通りにやれば良い。

Tutorial: Building Rumprun Unikernels · rumpkernel/wiki Wiki · GitHub

 

$ git clone http://repo.rumpkernel.org/rumprun
$ cd rumprun
$ git submodule update --init
$ ./build-rr.sh hw

 

helloer.cを作る。

#include <stdio.h>

#include <unistd.h>

 

int main() {

  printf("test1");

  sleep(2);

  printf("test2");

  return 0;

}

 

$ ./rumprun/bin/x86_64-rumprun-netbsd-gcc -o helloer-rumprun helloer.c

 

$./rumprun/bin/rumprun-bake hw_generic helloer-rumprun.bin helloer-rumprun

 

これでアプリケーションとかFSとかが全部1つになった、elfバイナリができるので、後はこれをmultibootで起動するだけ。

 

multibootが起動できれば何でも良いけど、GRUB2以外の選択肢は無いだろうなぁ。

ともかく、普通にディスクイメージ作って、GRUB2インストールして、ディスクイメージをマウントして、/boot以下とかにhelloer-rumprun.binを置けば良い。

たぶんgrub.cfgの最小構成はこんな感じ。

menuentry "unikernel" {

  multiboot /boot/helloer-rumprun.bin

}

 

f:id:liva_h:20170718215534j:plain

 

とりあえずtest1test2って出てるから、動いてるっぽいですね。

とても簡単に動いた。

 

7/21追記:

qemuで動かしたければ、

./rumprun/bin/rumprun qemu -i -g '-curses' helloer-rumprun.bin

とかやればよろし。GUI環境なら、 -g '-curses'も不要。

Rump Kernelの起動コードを読む

Rump Kernelのソースコードを読んでいく。

unikernel版(?)で、platformはhw(ベアメタル上で動作する版)

 

ベースとなるコードはここ。

GitHub - rumpkernel/rumprun: The Rumprun unikernel and toolchain for various platforms

 

僕はアーキテクチャspecificな部分から攻めていくのが好きなので、とりあえずx86依存なブート処理を読んでいく。

 

リンカスクリプトはこれ

rumprun/kern.ldscript at master · rumpkernel/rumprun · GitHub

 

ブートはmultiboot経由で行われる。

multibootのエントリポイントがここ

rumprun/locore.S at master · rumpkernel/rumprun · GitHub

 

やってる事は64bitへの遷移。

cr3レジスタはcpu_pml4tというシンボルを代入している。

ページ構造体は以下にstaticに作られている。

rumprun/pagetable.S at master · rumpkernel/rumprun · GitHub

凄いコードだ。。。

あんまりOSのコードを読んだことが無いから分からないのだけど、staticに作るのって一般的なのだろうか?動的に生成するのが普通かと思っていたけども。。。

 

64bit遷移後はx86_bootへジャンプする。

rumprun/boot.c at master · rumpkernel/rumprun · GitHub

ここでやっている事は、諸々の初期化。

cons_initはコンソールの初期化。putcした時に、vgaコンソール使う場合はvgacons_putcを呼ぶようにするし、シリアルを使う場合はserialcons_putcを呼ぶようにする。

 

cpu_initはTSS、IDT、PICの設定をした上で、タイマを初期化する。

実機だったらtscを用いる。(tscがあれば)これは8254でtscのfrequencyを測定しているコード。

rumprun/clock.c at master · rumpkernel/rumprun · GitHub

 

ところで、8254を使い終わった後も、とりあえず適当なハンドラを登録して、割り込みが来ると握りつぶすようにしてるようなんだけど、これはなぜ?

rumprun/clock.c at master · rumpkernel/rumprun · GitHub

 

multibootではコマンドラインオプションを取得(moduleで指定されている場合はmoduleを読む) し、parsememで使用可能なメモリを登録する。で、なぜかここでintr_initを読んでいる。intr_initはdoisrをスケジュールしている。別にmultibootから呼ばなくて良くないか?と思うが、何か深い理由があるのだろうか。

 

doisrはイマイチよく分からない。

thread context we use to deliver interrupts to the rump kernel

 だそうで。

rumprun/intr.c at master · rumpkernel/rumprun · GitHub

とりあえずリンクだけ貼っておく。

 

multibootを抜けたら、割り込みを許可して、bmk_sched_startmainを開始する。

 

 

雑感。

一通り読んで分かったけど、マルチコアの初期化は一切やってない。そりゃそうだよね。まあユニカーネルなんだし複数コアとかいらないのかな。

今回は出てこなかったが、rump kernelを動かすにはスケジューラとかが必要という話があって、既存のOS上で動かす場合はホストOSが面倒を見るのだけど、ベアメタルで動かす場合は自分で実装しなければならない。そういった部分はlib/libbmk_core内にあるっぽい。アーキテクチャ非依存だからね。

 

Rump Kernelを触った備忘録

いろいろ思う所があって、Rump Kernelを触っている。

Rump KernelはNetBSDベースのlibrary OS(詳しくはおるみんさんの記事を参照)で、2つの使い方がある。

 

・basic Rump Kernel (何か公式の呼び名があるのだろうか?)

NetBSDカーネルをserverとして立ち上げて、clientを繋ぎ、NetBSDのアプリを実行する方式

 

・Unikernel

NetBSDアプリケーションをNetBSDとハードリンクして、1つのバイナリにした上で実行する方式

 

前者はLinuxの上とか*BSD上で動かしている例が多く、後者はベアメタルや、qemu上で実行される例が多い。逆はできるのかな?(よく分かっていない)

 

前者のチュートリアルがこれ

Tutorial: Getting Started · rumpkernel/wiki Wiki · GitHub

buildrump.shでrump_server(カーネル)作って、rumpctrlから繋ぎますよ、っていうお話。これ通りやればとりあえず何か動いた。

 

後者のチュートリアルはこっち。rumprun使ってごにょごにょする。

 Tutorial: Building Rumprun Unikernels · rumpkernel/wiki Wiki · GitHub

これはUnikernelアプリを自分で作って動かしてみましょうというもの。やってはないけど、たぶん動くのだろう。(追記:↓やりました)

raphine.hatenablog.com

 

Tutorial: Serve a static website as a Unikernel · rumpkernel/wiki Wiki · GitHub

これはUnikernelでnginx動かして、HTTPサーバー立てるよ、というもの。

 

最初はrumprunとかrumpctrlとかbuildrump.shとかいろいろあって良くわからなかったけど、チュートリアル見つけてようやく理解した。

 

 

で、ここで疑問なのだけど、以下で提供されている以外のアプリケーションを動かしたかったらどうすれば良いのだろう?

GitHub - rumpkernel/rumprun-packages: Ready-made packages of software for running on the Rumprun unikernel

 

うーん、もう少し触り続けるか否か悩むなぁ。

LinuxのCPU hotplugについて調べてみる。

諸事情によりLinuxのCPU hotplugについて調べてみたくなったので。

 

とりあえず試してみたいので、やり方を調べた。

NAKAMURA Minoru's Diary (2006年3月)

 

要するに、rootで以下のようにすれば良いとの事。

# echo 0 > /sys/devices/system/cpu/cpu1/online

/proc/cpuinfoからも見えなくなる。凄い。

そして1を突っ込むとまた復活する。凄い。

どういう仕組が知りたいので、調べてみる。

 

dmesgを読むと、

smpboot: CPU 1 is now offline

というのが出てるので、それっぽい関数にアタリを付ける。

arch/x86/kernel/smpboot.c内のnative_cpu_die()っぽい。

 

どういう経路でこいつが呼ばれてるか知りたいので、stacktraceを取得する。

kernhack.hatenablog.com

 

Linuxカーネルってこんな事できるのか。。。すごすぎでは。

 

#!/usr/bin/env stap

 

probe begin {

}

 

probe kernel.function("native_cpu_die") {

        print_backtrace()

        exit()

}

 

probe end {

printf("Done\n")

}

こんなスクリプトを書いて実行すると、以下のようなスタックトレースが取れる。

0xffffffff81040ee0 : native_cpu_die+0x0/0x80 [kernel]

0xffffffff816fa6be : _cpu_down+0x17e/0x2c0 [kernel]

0xffffffff816fa834 : cpu_down+0x34/0x50 [kernel]

0xffffffff81488bf4 : cpu_subsys_offline+0x14/0x20 [kernel]

0xffffffff81483ec5 : device_offline+0x95/0xc0 [kernel]

0xffffffff81483fc0 : online_store+0x40/0x90 [kernel]

0xffffffff814814b8 : dev_attr_store+0x18/0x30 [kernel]

0xffffffff8122f978 : sysfs_write_file+0x128/0x1c0 [kernel]

0xffffffff811b8c44 : vfs_write+0xb4/0x1f0 [kernel]

0xffffffff811b9679 : sys_write+0x49/0xa0 [kernel]

0xffffffff81719c6d : system_call_fastpath+0x1a/0x1f [kernel]

drivers/base/cpu.c内のcpu_subsys_offline()が呼ばれて、kernel/cpu.cのcpu_down()が呼ばれ、__cpu_die()経由でnative_cpu_die()が呼ばれる、と。

 

逆に起こす時は、cpu_subsys_online()からcpu_up()が呼ばれ、native_cpu_up()が呼ばれるという流れ。

native_cpu_up()はdo_boot_cpu(), wakeup_cpu_via_init_nmi()を経由して、wakeup_secondary_cpu_via_init()を呼び、MPの初期化が行われる。初期化されたコアのエントリポイントはstart_eipで指定される。このstart_eipはdo_boot_cpu()内で

real_mode_header->trampoline_startが指定されている。

 

trampoline_startは、arch/x86/realmode/rm/header.Sで定義されている通り、pa_trampoline_startである。

multibootカーネルにおける画面描画

multiboot specification(このブログでは全てv2を指す)に則ってカーネルを作るとブートローダ周りの諸々を手抜きできるのだけど、今回は特に画面描画辺りのHowToを軽くまとめておく。

 

最初にmultiboot specificationのおさらいをしておくと、

 

  • elfでカーネルを書ける
  • 仕様書に規定されている様々なハードウェア情報を勝手にブートローダが集めてくれる
  • 最初からモジュールをメモリに読み込ませたりできる(ディスクドライバがない状態でもモジュールがロードできる)

 

まあ最近はUEFIのお陰でブートローダは凄く書きやすくなったそうなのだけど、それでもmultiboot specificationは良くできているので、余程の事がないかぎりはこれで十分かなーというのが個人的な印象。

multiboot specficationをサポートしたgrubという素晴らしいブートローダがあるので、下周りは全てこいつに任せる事ができる。grubを使えば、BIOSUEFIファームウェアの差を吸収してくれるので、どのような環境でも等しく動くOSが作れる。特にBIOS環境下ではとても便利。

 

さて、multiboot specificationにはframebuffer info(仕様書3.6.12 Framebuffer info)というのが規定されており、画面描画のためのフレームバッファの情報を取得できる。取得できるのは、アドレスと解像度で、下の描画インターフェースがVBEだろうとGOPだろうとUGAだろうと同じように扱える。(描画インターフェースのサポートはgrub側の仕事なのだけど、どれもきちんと動く)

 

f:id:liva_h:20170506165924p:plain

ちゃんと公式のサンプルコードもあるよ。

kernel.c\doc - grub.git - GNU GRUB

 

で、問題はここから。

フレームバッファの情報が取れるのは良いのだけど、ブートローダ側がフレームバッファを使う準備をしない事にはフレームバッファに関する情報が得られない。その辺の設定が抜けていると時間が溶ける。

 

ブートローダはもうgrub2固定で説明する。

まずはgrub.cfgの設定。

 

insmod efi_gop

insmod efi_uga

insmod vbe

insmod vga

 

描画インターフェースのモジュール(つまりデバドラ)をgrubに読み込ませる必要がある。上2つはUEFI環境用、下2つはBIOS環境用。

 

次に、multiboot headerにframebuffer tagを置く。(参考:仕様書の3.1.10 The framebuffer tag of Multiboot2 header)multiboot headerというのは、elfカーネル内に埋め込む、カーネルからブートローダに情報を渡すための仕組み。シグネチャの直後とかに書けばよろし。

f:id:liva_h:20170506171100p:plain

 

これも公式サンプルにある。

boot.S\doc - grub.git - GNU GRUB

解像度は1024x768x32がたぶん無難。もっとデッカイ解像度が良い場合は数字を変えれば良いのだけど、必ずこの数値が使われるわけではないので、注意。(ブートローダの気まぐれとか、インターフェースのサポート状況とかによって低解像度に格下げされる)

この設定を埋め込まないと、ブートローダが勝手にデフォルトの解像度に設定してしまう。UEFIだと一応フレームバッファ(ただし解像度800x600x32)になるのだけど、BIOSだと80x25のテキストコンソールになると思う。

ただまあぶっちゃけテキストコンソールの方が便利だとは思う。UEFIではこれが削除されてしまって、自力でテキスト描画しなければならなくてちょっと不便。

 

ちなみに、自分でフォントを画面描画するなら、grubのpf2フォントを使うのが便利。普通にインストールすると /boot/grub/fonts/unicode.pf2 というのがあるはずなので、こいつをmoduleとして読み込んで、解析すればよろし。

raphine.hatenablog.com

 

こんな感じで描画される。

 

 

最後に、参考までに僕の自作OSのgrub.cfgを載せておく。

github.com

 

UEFI対応(その2)

これの続き

pf2フォントの解析ができたので、UEFIからブートしようと頑張る。

 

ACPICAの初期化でクラッシュするので原因究明

ここまでの問題のオチはこれ↑

 

 

RSDPが見つからない

たぶん返してくれてる、と思う。

これにてRSDPは解決。

 

HPETがない。

存在しなかったオチ。qemuUEFIでのデバッグは諦めた。

 

オマケ。

BIOS起動時もUEFI起動時と同様にフレームバッファベースで動かそうとして嵌った。