Plan 9(9front)のMP化周りを調べてみる その1

MP化に関係ありそうなファイル(archmp.c, mp.c, apic.c)が転がってるけど、いまいちほんとに対応しきれているのか謎なので調べてみる。とりあえず初期化のコールグラフまわりのまとめ。具体的な役割はまた今度。


実線は関数呼び出し、破線は参照。なんとなく、呼び出し順に左から右へ、上から下へと流れていくふぃーりんぐで

うん、でかい....

【カーネル/VM Advent Calendar 2012 5日目】virtio-net on Plan 9

さて、カーネル/VM Advent Calendar 2012 5日目でございます。

初めに
新ネタ...? ヒィ、そんなものないです><

というわけで夏に出したネタを引きづり出してこねくり回してお茶を濁すことにしましょう。
題しまして「virtio-net on Plan 9」でございます。virtio-netのPlan 9実装*1について、ざらりとお話したいと思います。

夏にうちの薄い本持っていった人は読み飛ばすといいよ!
えっ、pdf後悔公開? 永遠に改訂中さ!


virtio & virtio-netについての参考資料

文章系

他OSのドライバコード

とはいえ、実装のために挙動を知るにはコードを読むのが一番。各種OSにコードが転がっているので適宜参照します。

基本的にはどのOSでも、virtioの主役である共有メモリ上のリングバッファ部分とそれを使うネットワークドライバ部分という2本の柱で成り立っています。

qemu-kvmのコード

以上に加えて、ホストOS側で動いているqemu-kvmのコードも参考になります。ドライバ開発中に、下手なリングバッファの使い方をすることがまれによくあります(注:個人差があります)。するとよくわからないメッセージとともにVMが殺されます。その際に何がどうおかしかったのか、どこでおかしいと判断されたのかを調べる際に重宝します。自前でqemu-kvmコンパイルしつつデバッグメッセージなどを埋め込む感じで進めます。

"sdvirtio.c"

今回のメインのパクリ参照元
そしてPlan 9のvirtioドライバ開発に光明をもたらした素敵なドライバ。
これはPlan 9 向けの virtio-blk、いわゆる仮想化ブロックデバイス用のドライバです*2

Plan 9の本家であるPlan 9 from Bell Labsには入っていませんが、9front*3および9legacy*4にはそれぞれ2012/04あたりに導入されています。

Plan 9向けにvirtioのメインの柱である共有リングバッファ部分が書かれており、さらにとてもシンプルなので流用・参考にするのに便利です(最低限、な感じがあふれていますが...)。ただし上記に挙げたLinuxやら*BSDのように、virtio共通な共有リングバッファ部分とvirtio-blkに固有のコードが若干混ざってしまっており、今後virtioなドライバを増やしていくにあたっては改良が必要そうです。


virtioの仕組み in Plan 9 World
virtioでは、ゲスト向けのエミュレーション処理を省くためにゲストホスト間で一つ以上の共有リングバッファを設け、これを通してデータのやり取りを行います。ここではざっくりとsdvirtio、そして今回実装したvirtio-netドライバでの共有リングバッファの使い方について概説します。

共有リングバッファの構造

viritoではホストゲスト間でメモリ上に共有のリングバッファを設けます。そしてこれに対してそれぞれ要素をpush/popすることで、データのやり取りをします。このリングバッファはゲストの物理メモリ上に置かれ、こんな感じのマップになります*5




各要素は以下のような意味を持ちます。

デスクリプタテーブル

ゲストからホストまたはその逆方向へのデータの転送(たとえばTX側のパケットの送出や、RX側の受取)を管理するためにデスクリプタなるものが定義されています(2.3.2節)。それぞれのデスクリプタにはデータの(guest-physicalな)アドレスや長さ、その転送のタイプなどが入ります。
AvailableリングとUsedリングからこのデスクリプタのいずれかを参照することでゲストはホストに対してOutput/Inputのデータ処理を行うことができるようになります。
なお、双方のリングはこのデスクリプタテーブルを先頭からのインデックス値にて参照します。このため、メモリ上で連続領域に指定個数分*6確保する必要があります。

Availableリング

Availableリングは、ゲスト→ホストの方向にデータを送りたい(たとえば、パケットを出したい、ブロックを書き込みたいe.t.c.)時に使います。この部分がリングバッファ その1 になります。
このリングは以下の要素からなります

  • ヘッダ : このAvailableリングのフリー領域の先頭インデックス含む
  • availたち : 転送するデスクリプタのインデックス配列(実態はu_int16_tの配列)
  • availevent : plan 9では未使用

ゲストがデータを送りたい場合、以下の手順を踏むことでこれを行います。

  1. デスクリプタテーブルから空いてる物を探し、インデックス値をゲットする
  2. 送りたいデータとか長さを適切な設定
  3. そのインデックスをavailたちからなるリングバッファの先頭に突っ込む
  4. (上記を何度か繰り返す)
  5. ヘッダ中のインデックスを更新し、「ここまで書き込んだからなんとかしろ!」とホストをキックする

以上により、これを受けたホスト側はヘッダ中のインデックスの部分までは何かしら有効なデスクリプタを参照するためのインデックス値が入っている物としてよしなに処理を行ってくれます。

Usedリング

Usedリングは、Availableリングとは対称的にホスト→ゲストの方向への転送を司るリングバッファ その2になります。
だいたいの構造はAvailableリングと同じで、usedたちのそれぞれに転送対象のデスクリプタのインデックス値が入っています*7。このリングの出番は基本的には割り込みが入ってきたら、になります。例えばパケットの受信割り込みや、さきほどAvailableリングを用いて行った送信処理の後始末などで登場します。
例として受信処理を挙げると、ゲストはこれを以下の手順で行うことができます。

  1. 割り込みが入る
  2. ヘッダ中のインデックスに設定されているホスト側の「used配列のここまで何か書き込んだよ!」という値を見る
  3. 過去のインデックスから、現在のインデックスまでのused配列間になにかが入っていると判断
  4. 一個ずつ読み進めてなにかしらの処理をする
  5. (現在のインデックスになるまで繰り返す)
  6. すべて処理し終わったら、ヘッダ中のインデックスを更新して「ここまでは終わったよ!」と明記する

データのやり取り

ここでは一連の流れとして、ゲストからホストへデータを送信しその後処理がおわるまでを取り上げてデスクリプタと2つのリングバッファの使い方を並べると以下のようになります。

  • ここではデスクリプタたちを内部的にチェーンさせ、空き状態を管理しています。
    • 基本後方から取っていきます
  • Available リングのヘッダのインデックスをavailidxとしています
  • Used Ringのヘッダのインデックスをusedidxとしています
    • また、「過去のインデックス」を保存するためにlastusedを定義しています。
ゲスト→ホスト



ホスト内での処理



ホスト→ゲスト



virtio-netの仕組み in Plan 9 World
前節でのリングバッファを使うことで、ゲストホスト間でのデータのやり取りを行うことができるようになりました。ここではこれを使ったvirtio-netドライバの構成について述べます。specではAppendix C : Network Deviceに示されているあたりの挙動についてその一部、最低限の部分について解説します。

リングバッファの構成

virtioを用いたデバイス(ホスト)とドライバ(ゲスト)間では1つ以上の共有リングバッファを使います。これは基本的にそれぞれのリングバッファが単方向でしか使えないためです。このため、virtio-netでは以下の三種類のリングバッファを使います。

virtio-net ヘッダ

リングバッファはあくまで「ゲスト-ホスト間」でのデータパスを提供するだけなので、実際になんらかのデバイスとして叩くためにはさらにメタ情報が必要になる場合があります。virito-netの送信側ではこの為にvirtio-netヘッダなる情報を送ろうとするL2フレームの先頭に付加してやり取りをする必要があります。

virtio-netヘッダは以下のような要素からなります

  • フラグ : チェックサム計算のオフローディングのenable/disable
  • gso_type : 有効にするTSO(TCP Segmentation Offload), UFO(UDP Segmentation Offload)などの種類
  • フレーム長
  • この転送で対象となるデータのサイズ(virtio-netヘッダ長 + フレーム長)
  • データとして格納可能な最大サイズ(MSS)
  • チェックサムの挿入位置
  • チェックサム
  • チェーン長

ゲストは送信時にはこのヘッダを適切に埋めて付加し、受信時にはこのヘッダを取り除いてIPスタック他に上げる必要があります。

送受信処理

virtio-netにおける送信処理では、これまでの述べたvirtioの共有リングバッファのオペレーションに従って適宜フレーム+virtio-netヘッダをホストへ渡してやることでこれを行うことができます。
受信処理に関しては若干の考慮が必要です。というのも、受信(とその割り込み)はいつ入るかわからないことに加えて、virtioでホストが自由に扱えるバッファはAvailableリングにある物だけだからです。このために、ゲスト側では事前にAvailableリングにありったけのディスクリプタを詰めておく必要があります。これらのディスクリプタには受信用のバッファを確保した上でその(guest-physicalな)アドレスを設定しておきます。
こうすることで、ホスト側でフレームを受信した場合、Availableリングのまだ使われていないデスクリプタを取得し扱うことができるようになります。ホストは受け取ったデータのアドレスや長さなどを詰めて、Usedリングにこれを置き、割り込みを注入することで受信完了を伝えます。




送受信ともに、割り込み時にはホストが処理したフレームのディスクリプタがUsedリングに詰まれることになります。送信側では送信完了処理としてこれを解放する、つまりデスクリプタテーブルに、フリーなデスクリプタとして戻してやれば完了です。
受信側では、フレームを取り出して上位層に上げることが必要なのはもちろんですが、さらに一手間必要です。次なる受信に備えるため空いたデスクリプタはAvailableリングに書き戻してやる必要があります。これにより、受信バッファがたりない!といったことを極力起こさず受信処理を継続させることができます。

終わりに

virito-net on Plan 9について、virtioの動作概要(ただし一部)からvirtio-netでの動き(ただし一部。大事なことなので(ry)までさらりと概説してみました。実装自体がほんっっっとーに最低限しかない&まだ完璧に動いていないため、不十分なところや誤謬があるかと思いますが、何かしらの参考になれば幸いです。

また、ここがおかしいよ!といったご指摘を頂けると実装が進みますので大変ありがたいですm(_ _)m

*1:実際には9frontですが

*2:Plan 9では"Storage Device"つまりストレージに関連するデバイスへのインタフェースを担うコードには"sdほげほげ.c"と名付けるようになっています。

*3:Plan 9 from Bell Labsのfork

*4:Plan 9本家への機能追加パッチ集。forkではないらしい...

*5:specとは微妙に命名が違うので注意

*6:バイスによる

*7:実際にはusedはインデックスとデスクリプタチェーンの長さからなる構造体になっています。ここではデスクリプタチェーンのお話は省いています。詳しく知りたい方はspecの2.3.5節 "Used Ring"を参照してください

linux virtio-9p host side call graph (途中)

前回のBHyVe Hackathon #1にて実装を断念したVirtFS (virtio-9p)のホスト側、qemu-kvm内での処理を追っかけてみる。virtio-netみたいに、virtqueueのディスクリプタ用に特殊なヘッダがあるのかなぁとあたりをつけていたが、どうやら9Pのフォーマットのパケットをそのままべたで突っ込めばいいらしい...

C82 Glenda9 @ 土西す35b "Plan 9 × Virtio"

作業中のVirtio-net on Plan 9ネタでなんか書きます書いてます。

とりあえず最低限NIC的なところまでたどりついたので、そのあたりまでの技術的な内容についてちらほら。
比較的誰得。いや俺得。

Virtio-netについての資料は@hasegawさんの比較的わかりやすいのがあったりするので、資料価値としても疑義を呈される可能性大。