自作VMM u-rootベースのinitrd

はじめに gokvm開発 1 2 3 4 5 6 の続き。 前回までに紹介したとおり virtio-blk と virtio-net に対応したことで、仮想マシンが外部とIOを通してやり取りができるようになった。 今回は initrd を busybox ベースから u-root ベースへと変更したので、それについて述べていく。 0d89a47f u-rootベースの initrd の導入 Go言語で作られたVMMには、同じくGo言語で書かれた initrd が相応しいのではないかということで、Pull Requestをもらった。 1コマンドで成果物を生成でき、busyboxと比べると手順が少なく簡単な印象を受けた。 cb504d85 u-rootベースのinitrdをデフォルトとする u-rootによるinitrdをしばらく触ってみると自分のやりたいことはこなせるだろうという感触を持ったので、デフォルトとした。ただ、busyboxでは特に意識せずできていたことが u-root ではできないことがあった。例えば以下のもの。 ctrl-lやctrl-eでシェル内カーソル移動を行うために、clearやticコマンドに加えて terminfo ファイルが必要だった。 ゲストの起動時に、NIC・ファイルシステムの初期化やHTTPサーバの起動のために、それを記載したスクリプトファイルを /bin/uinit に配置したが、デーモンが途中でkillされるような挙動になってしまった。init関連の挙動に対する自分の理解が甘いのだと思う。しょうがないのでワークアラウンドとして .bashrc に記載した。 終わりに この他にもいくつかリファクタリングを実施した。 今回はVMMらしい変更はなかった。今後はマイグレーションをやっていきたい。 KVMを使ったVMMを自作してLinuxを起動するまでの記録 ↩︎ KVMを使ったVMMを自作してLinuxを起動するまでの記録2 ↩︎ KVMを使った自作VMMのSMP対応 ↩︎ 自作VMMの PCI デバイス対応 ↩︎ 自作VMM の virtio-net 対応 ↩︎ 自作VMM の virtio-blk 対応 ↩︎...

June 13, 2022

自作VMM の virtio-blk 対応

はじめに gokvm開発 1 2 3 4 5 の続き。 前回の virtio-net 対応に引き続いて、virtio-blk に対応した。 virt queueのデータ構造や挙動はそのまま流用できる。 この辺り Virtio はうまく設計されているなと感動する。 7389ff59 カーネルコンパイルオプションの調整 ゲストカーネルからファイルシステムを経由してブロックIOを実現するにあたって、以下のオプションを有効にした。 CONFIG_VIRTIO_BLK=y CONFIG_XFS_FS=y CONFIG_EXT3_FS=y CONFIG_EXT4_FS=y 4f4bbb78 virtio-blkの実装 さて、それでは本題である virtio-blk の実装に移っていく。 virtio-blk の挙動は virtio-net のものとほとんど同じなので、もし前回のブログを読んでいなければ、そちらを先に読むことをお勧めする。 差分はキュー数とdescripterテーブルのエントリが指す先のデータ構造だけである。 virtio-net では送受信のため2つのキューを必要としたが、virtio-blk の場合には 1つのキューで読み書きを実現する。 これはディスクへの読み書きはどちらもOS側からの発行となるため、外部割り込みを受ける必要がないためである。 descripterテーブルエントリが指すデータ構造は、以下のように3つのエントリがLinked Listの要領で繋がっている 6 。 1つ目のエントリが指すデータ構造は blkReq であり、typeフィールドが1なら書き込み、0なら読み込みを意味する。 sectorフィールドがディスクの先頭からのオフセットを意味する。 1セクタは512バイトなので、仮想ディスク用ファイルの sector x 512 バイト目から読み書きすることを意味する。 type blkReq struct { typ uint32 _ uint32 sector uint64 } 2つ目のエントリが実データを指す。ここに実際に読み書きしたいデータをバイナリで格納する。 3つ目のエントリがステータスである。エラーが発生した場合には0以外の数値を書き込む。...

April 12, 2022

自作VMM の virtio-net 対応

はじめに gokvm開発 1 2 3 4 の続き。 最近の一連の開発によって、gokvm 上のVMに virtio-net によって仮想NICを提供することができた。 ネットワーキングのサポートは当初の目標の一つだったので、達成感がある。 この対応によって gokvm 上のVMはホスト(あるいはソフトウェアスイッチを経由して外部)との間で通信できるようになった。 WEBサーバを提供したり、SSHでログインできたり、と出来ることの幅が広がる大きな変更だと思う。 例によって、重要なコミットを抜き出して振り返りたい。 c5217550 Virt Queue データ構造の追加 そもそも Virt Queue とは何なのか。 Virt Queue は ゲスト・ホスト間におけるデータのやり取りに使うリング構造のキューを意味する。 例えば送受信でそれぞれ1つのキューを使うナイーブな virtio-net の場合には、 送受信それぞれ1つの Virt Queue (全体で合わせて2つのVirt Queue)が必要になる。 もちろん、マルチキューをサポートする場合やコントロールキューをサポートする場合には、さらに Virt Queue が必要になる。 1つの Virt Queue は Descripter Table、Avail Ring、Used Ring から構成される。 取り扱いたいデータのアドレスと長さを1つのディスクリプタとしてまとめ、それをテーブル状に並べたものが Descripter Table である。 Avail Ring と Used Ring は似ていて、どちらもディスクリプタのIDをゲスト・ホスト間で 伝え合うために利用される。 方向も決まっていて、Avail Ring がゲストからホスト宛、Used Ring がホストからゲスト宛 となる。 ちなみに virtio 仕様 5 の中では、ゲストをdriver、ホストをdevice と表現している。...

March 18, 2022

自作VMMの PCI デバイス対応

はじめに gokvm開発 1 2 3 の続き。 gokvm 上のVMからPCIデバイスを取り扱えるよう開発を進めてきた。 道のりは長いだろうが、最終的には virtio-net を経由して、VMと外部の間でIP疎通を取りたい。 現時点では virtio-net デバイスをゲストカーネルのネットワークインターフェイスとして認識させることができたので、 ひとまずそこまでのログを残しておく。 やったことを大きく分けると、(1) ゲストのLinuxカーネルに対してvirtio-netデバイスをPCIデバイスとして認識させ、 (2) virtio-netデバイス初期化を完了させることでネットワークインターフェイスとして登録させることの2点。 virt queue上の操作やパケットのやり取りについては、この記事には含まれない。 例によって、コミット単位で実装の経過を残しておく。 fc02176d lspciコマンドの追加 busyboxにはlspciコマンドが同梱されているが、pci.ids ファイル 4 が存在しない。 pci.ids はベンダIDやデバイスIDなどの数値と、それに対応する文字列が組になっているようなファイルである。 このファイルがあれば、人間に読みやすいフォーマットで出力できる。 後々のデバッグをスムーズに進めたいので、対応させておいた。 e126392e PCI Config空間に対するIOエミュレーション カーネルがPCIデバイスを認識するための重要なフェーズ。 PCI Config 空間を読む方法はいくつかあるようだが、ここではタイプ1 5 と呼ばれる方法でアクセスした。 ここで使われるIOポートのアドレスは以下の通り。 0xcf8:アドレスレジスタに対応する。バス番号、デバイス番号、Function番号、PCI Config 空間内のオフセットに対応する。 0xcfc ~ 0xcff:データに対応する。 アドレスレジスタは32bit幅で以下のように解釈される。 位置 内容 Bit 31 Enable Bit Bit 30-24 Reserved Bit 23-16 Bus Number Bit 15-11 Device Number Bit 10-8 Function Number Bit 7-0 Register Offset ざっくり PCI Config 空間のあるオフセットにあるデータを読みたいときは次のような手続きになる。...

January 24, 2022

KVMを使った自作VMMのSMP対応

はじめに gokvm開発 1 2 の近況報告。 これまでは1つの仮想CPUにしか対応していなかった。 マルチCPUのためSMP(Symmetric Multiprocessing)対応させたいと思い立ってから2~3週間くらい試行錯誤し、無事実装することができた。 自分の知る限り KVM でVMMを作ってみたという取り組みを探す中で、 具体的にSMP対応とはどのような実装なのか解説されている資料がなかなか見つからなかった。 稚拙な記事ではあるけれど、今後自作VMMに挑戦する方にこの記事が役に立てば嬉しい。 例によってコミット単位に開発の経過を紹介していく。もちろん実際にはもっともっと泥臭い実装から始めていて、 何度もgit rebaseを繰り返しながら、最終的に解説できるよう粒度を調整したので、コミットのタイムスタンプはあてに出来ない。 #34 vCPUスレッドを複数生成 プルリクエストをいただいた。まずはioctl(fd,KVM_CREATE_VCPU,...)でvCPUを複数生成できるよう変更する。 その後vCPUごとに個別のスレッドを生成して各vCPUごとに独立してioctl(fd,KVM_RUN,...)を発行する。 vCPUはライフタイム全体を通して、同一のスレッドからioctlを発行する必要がある。 Go言語の場合にはスレッドの代わりにgoroutineを使うことが多いので、 runtime.LockOSThread()を呼び出してgoroutineとスレッドを静的に関連づけた。 ce22a91 struct mpf_intel の実装 vCPUがカーネルに認識されるためにはIntel MultiProcessor Specification 3 に従ったデータ構造を認識させる必要がある。 このデータ構造はLinuxカーネルの中で struct mpf_intel のPhysPtrが指す先 struct mpc_table に対応する。 コード 4 を読むと、チェックサム・バーション・マジックナンバーを読み取ることができたので、 仕様書は斜め読みしかしていない。 このデータ構造はどこに配置すれば良いのか。仕様書を読むとExtended BIOS Data Area (EBDA)の最初の1KB以内とあるのでそこに配置することにした。 EBDAは典型的に 0x0009FC00 に置かれる 5 ようなので、それに倣った。 a. In the first kilobyte of Extended BIOS Data Area (EBDA), or b. Within the last kilobyte of system base memory (e....

November 25, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録 2

2021/2/24 WSL2 サポート 4f6b785 WSL2(Windows Subsystem for Linux 2)のUbuntu 20.04で gokvm を実行すると、 IOポート 0x64 への出力が無限に繰り返され、Initプロセスの起動まで到達しなかった。 どうやら PS/2 キーボード周りの挙動が原因のようだ。 kvmtool では in (0x61) に対して 0x20 を返している 1 のでそれを踏襲する形で対応した。 IOポート 0x61は NMI (Non-Maskable Interrupt)のステータスとコントロールレジスタとして使われているようだ 2。 このステータスレジスタの内容を調べると、bit 5はmirrors timer 2 output condition を意味するが、 これ以上は解釈できず。 理解できていない部分はあるが、結果として WSL2 での ゲストVMの起動もできるようになった。 0061 r KB controller port B control register (ISA, EISA) system control port for compatibility with 8255 bit 7 parity check occurred bit 6 channel check occurred bit 5 mirrors timer 2 output condition bit 4 toggles with each refresh request bit 3 channel check status bit 2 parity check status bit 1 speaker data status bit 0 timer 2 gate to speaker status 出典:XT, AT and PS/2 I/O port addresses...

March 3, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録

はじめに KVMを利用したナイーブで実験的なVMMを作ってみた。 ioctl で /dev/kvm を叩いて仮想マシンを作成し、その上でLinux Kernelとユーザプロセスを起動できる。 Kernelのデバイスドライバから認識できる程度の非常に簡素なシリアルコンソールのエミュレーションも実装したので、 ログインシェルから操作ができる。 一方で、ネットワーキングやディスクについては現時点ではまだサポートしていない。 最近はKVMを従来のような仮想マシンとしての使い方だけでなく、 マルチテナントなクラウド環境において分離レベルを強化するために、 Google gVisor 1 や Kata Containers 2、 Amazon Firecracker 3 をはじめとした コンテナやマイクロVMでの使い方が登場してきた。 今回作ったgokvmは標準ライブラリのみを使いGo言語で実装したもので、 全体で1,500行程度(ブログ記事作成時点)なので、 自分と同じようにKVMやLinuxのブートプロセスに興味のある方にはとっかかりとして役立つかなと思う。 コミットログを見ながら、何をどう実装したのかについて振り返ってみる。 2021/1/30 プロジェクト始動 632c6e0 最初のコミット。README.md、.gitignore、LICENSEファイルを配置しただけで、 特に特筆することはない。似たようなプロジェクト 4 5 や LWN.net の記事 6 を調べていた。 ミニマムな実装で Linux ユーザランドまでブートさせるようなものは見当たらなかった。 ざっと調べただけなので調査漏れがあるかも。 もともとはkvmtool 4 がその立ち位置だったのかもしれないが、 ちょっとコードが巨大に感じた。kvm-host.c 5 は250行程度のCのコードでkernelのブートができるが、 ユーザランドまでは到達できていないようだ。 2021/2/4 bzImage・initrdのビルドとKVMのラッパー実装 69e3ebb 動作確認用のbzImageとinitrdを make コマンドから生成できるようにした。 bzImageはLinux Kernel本体、initrd はメモリ上の一時的なファイルシステムに対応する。 Linux Kernel バージョンはプロジェクト開始時点で最新の 5.10 を使った。 make tinyconfig を実施したのち、make menuconfig を使って追加で必要なconfigを有効にした。 initrdは、Busyboxをベースとした。 Linux KernelとBusyboxの ....

February 18, 2021