OSb: Network Booting OSv

【注意】こちら OSv/BitVisor 軍事境界線(BitVisor側)の記事です.

こちら OSv Advent Calender 2014 11 日目の記事です.

OSv はハイパーバイザ上で動作する事を前提として設計された軽量 OS です.そこで,BitVisor という軽量ハイパーバイザ上で,OSv を動かす試みをしています.(前回の記事参照

結論から言うと,OSv はデバイスのサポートを氣にしなければ,コードを約 1 箇所コメントアウトすることで BitVisor (= デバイスを仮想化しないハイパーバイザ = 物理マシン) 上でブートします.しかし,コンパイルする度に物理ディスクに OSv をインストールのは大変面倒だったので,BitVisor を使って OSv をネットワークブートさせることにしました.

しかし,BitVisor 自体は KVM などと違ってゲストをネットワークブートさせる機能はありません(デバイスを仮想化しませんので).そこで今回は OSv を BitVisor 上でネットワークブートさせる実装について説明します.必要なコードを全部公開したいところですが,政治的理由のため今回は控えて,コードは限定的に触れていきます.

※ いずれ全部公開できると思います.

前回の記事 の通り,OSv のブートプロセスをザックリ読んだところ,BIOS コールでのディスクアクセス(INT13/42) だけで OSv がブートしている感じです.そこで,INT13/42 を BitVisor で捕まえてディスクアクセスをサーバへ転送すれば,OSv がネットワークブートしそうです.ということで,挑戦してみます.

※ OSv はブートした後,ファイルシステム ZFS をマウントするために,Virtio Block Device を使いにきます.BitVisor 側で Virtio Block Device を仮想化してあげないと ZFS マウントできないことになりますが,これは今後の課題として,今回はひとまず OSv カーネル (& bootfs) をブートさせるところまでを目的とします.SATA もいけるようなオプションドライバ を見かけましたが,どうせ Virtio にしたいので試していません.

BIOS コールは OS 起動初期の Real Mode 環境下で,INT 命令を叩いてソフトウェア割り込みを発生させると,BIOS がそれをハンドリングして色々リッチな処理をしてくれるという仕組みです.ゆえに BIOS コールは,BIOS が用意した 割込みベクタテーブル で割込みとしてハンドリングされています.

割込みベクタテーブルは 4 バイト幅のポインタのテーブルになっており,各ポインタは割込みハンドラのアドレスを示しています.それらポインタを差し替えることで,BIOS コールにお好みの処理を挿入できます.今回は,OSv がディスクアクセスに使っている INT13 のポインタを BitVisor で差し替えて,その中でディスクアクセスをサーバの仮想ディスクに対して行なうことで,ネットワークブートを目指します.

今回 BitVisor で差し替えた割込みハンドラ内では,VMCALL を発行するようにして,BitVisor に遷移します.そこで BIOS コールの引数を解釈して,INT13/42 だった場合,要求されたセクタをサーバから読み込んでくる処理をします.それ以外(INT13/XX, XX は 42 以外)だったらもともと BIOS が用意していた割込みハンドラにジャンプします.

Unrestricted Guest 前提で考えています.

INT13 の割込みハンドラを以下のコードに差し替えます.まず,INT13 で本来 BIOS が用意した割り込みハンドラジャンプするところを,こちらの guest_int13h_guest へジャンプさせます.

それぞれの処理について詳しく見ていきます.

まず,レジスタを退避します.BIOS コールの引数になっている AX, BX レジスタはBitVisor 内で VMCALL ハンドラを区別する際に使われてしまいますので,使われていない ECX, EDX の上位 16 ビットを活用して退避します.

そして,さっそく VMCALL を発行します.BitVisor には, BitVisor 内であらかじめ登録されたハンドラを,ゲストから VMCALL を使って呼び出す仕組みが既に実装されています.ゲストから BitVisor 内のハンドラを呼び出すには以下の手順で VMCALL を 2 回発行します.

  1. EAX には 0,EBX にはハンドラ名を表す文字列を示すポインタを格納してから,VMCALL を実行します.VMCALL 実行し終えると,EAX にはハンドラ番号(0 以外)が返って来ます.
  2. EAX にハンドラ番号を格納して VMCALL をもう一度実行します.すると,BitVisor に遷移(VMExit)して該当のハンドラが呼び出されます.ハンドラの処理が終わると VMCALL の次の命令からゲストの処理が再開(VMEntry)されます.

BitVisor ではあらかじめ “int13h” という名前でハンドラを登録しておきます.上記の手順に則って,”int13h” を EBX に指定して 1 回目の VMCALL を発行します.その直後に 2 回目の VMCALL を発行します.1 回目の VMCALL で EAX にハンドラ番号が入っているので,これで目的のハンドラが実行できます.

※ EBX に “int13h” のポインタを入れる際ですが,上記の割り込みハンドラのコードを配置するメモリ領域を動的に確保しているので,”int13h” のアドレスが実行時まで分かりません.そこで実際のコードの位置が決まってから “mov $0, %ebx” の “$0″ の部分を書き換えて,正しいアドレスが EBX に入るように命令を書き換えています.”.set guest_int13h_vmmcall_offset, . – 4″ して,guest_int13h_vmmcall_offset に 4 バイトのアドレスの値を入れます.

そして,2回目の VMCALL 内では INT13/42 かどうかを,ゲストのレジスタの値を覗き込んで判断します.もし,INT13/42 でなかった場合は ZF を立ててゲストへ帰ってきます.もし,INT13/42 で転送処理をした結果失敗したら CF を立てて帰ってきます.ZF が立っていたらもともとの BIOS コールへジャンプします.CF が立っていたらエラー状態をエミュレートしてゲストへ帰ります.いずれでもない場合は正常完了をエミュレートしてゲストへ帰ります.

では続いて,BitVisor 内の VMCALL ハンドラで何が行なわれるかについて説明します.2 回目の VMCALL 時に,BitVisor では以下の int13hook_vmmcall() が実行されます.

int13hook_vmmcall() の中では,まず,先のアセンブラの方で ECX, EDX に退避した AX, BX を取り戻します.

続いて,AH, DL をチェックして,INT13/42 か判断して,そうであればディスクアクセスの転送処理に移っていきます.

AH が 0x42,DL が 0x80 の場合,INT13/42 でローカルディスク(ブートディスク)を読み込んでいるので,転送処理を行ないます(それ以外は基本スルーで,もともとの BIOS コールを呼ぶようにします).INT13/42 では,ディスクアクセスに関する情報(LBA, セクタ数など)は DS:SI へ格納されるので,DS:SI のアドレス値を取得し,引数として渡しながら int13hook_readlba() を呼び出します.

INT13/42 では DS:SI で指定された下記のような構造体の情報に基づいて読み込みを行ないます.サーバの仮想ディスクから blocks で指定されたセクタ数だけ,lba で指定された位置からデータを読み込んで,segment:offset で指定された位置にロードすればよいということになります.

int13hook_readlba() 内で,実際にサーバディスクから読み込みに当たる部分は, nd_read() 付近になります.

cont フラグを false にしてから nd_read () を呼びます.nd_read () ではネットワーク経由でサーバの仮想ディスクを読みます.実際に,自前プロトコルでサーバへリクエストを飛ばします.(ここで Ethernet でフレームが 1 個飛びます.)続いて nd_poll(), nd_check() を繰り返して,受信された Ethernet フレームをポーリングします.そして,サーバからレスポンスがあれば受信処理をした後, nd_read() で指定した関数ポインタ int13hook_cb() が呼び出されます.

int13hook_cb() では,cont フラグを true にするだけの処理が入っていて,nd_poll(), nd_check() のポーリングを終了します.この時点で,既に nd_read() に指定したバッファ p (= 構造体で指定された読み込み先メモリアドレス)にサーバから読み込んだデータが入っています.

以上でサーバからセクタ読み込みが完了したので,あとはただ呼び出し元に戻るだけです.VMCALL で呼ばれたこのハンドラから抜けると,VMEntry して先のアセンブラのコードに戻ります.そして最後はゲストへ帰ります.

以上のように実装していくと,INT13/42 が BitVisor によって全部サーバへ転送されて,OSv がネットワークブートします.BitVisor 自体も PXE/iPXE 経由でネットワークブート可能なので,ローカルディスクに何も入れない状態で,OSv + BitVisor (= OSb) が降ってくることになります.

※ 自前プロトコル含めコードはいずれ公開してみようと思います.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">