Constellation Scorpius

Technical notes

Reading the Paper, "ELI: Bare-Metal Performance for I/O Virtualization"

P.S.

あったほうがわかりやすいかもということで, 当初抜いていた簡易 Overview 再掲.

Note: This article is written in Japanese. In this article, we introduces the paper by A. Belay et al., “ELI: Bare-Metal Performance for I/O Virtualization” [Abel Gordon et al. ASPLOS ‘12].

この記事は, システム系論文紹介 Advent Calendar 2015 12/22 の記事として書かれました.

Reference

  • Abel Gordon, Nadav Amit, Nadav Har’El, Muli Ben-Yehuda, Alex Landau, Assaf Schuster, Dan Tsafrir. ELI: Bare-Metal Performance for I/O Virtualization, In Proc. of 17th international conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS ‘12), 2012, pp 411-422.

Overview

VT-x, そして後の EPT 拡張によって, CPU, memory の virtualization は efficient に達成された. じゃああとは device ということで, VT-d によって, multiplexing はできないけれども, device によらず, isolation を壊さずに VM に device を efficient に assign することができた (pass-through).

とおもいきや, device が遅い場合がある. 調べたところ, interrupt が大量に入るような device ではなんと native 性能の 60% しかでてなかった. これは, VT-d の interrupt remapping は, 別に hypervisor を bypass して interrupt が入るわけではなく, interrupt は結局 VM exit して host に戻るという仕組みに存在した……

interrupt は実際は VM の管理 domain / host ではなくほとんど guest 向けのものになる. のならば, すべての interrupt を guest に直接渡してしまって, で, host に来るべきものだけ, host に戻せばいいのではないだろうか? しかし, guest の interrupt descriptor table (IDT) をそのまま使ってしまっては, host の受け取るべき interrupt を malicious な guest が乗っ取るかもしれない. この構図は, shadow page table の時の構図とほぼ同じだ.

ELI は IDT を shadowing し, shadow IDT を提供する. もちろんこの shadow IDT は guest からは直接触れない. host は guest IDT の変更を write protect で検知し(good old shadowing!), shadow IDT に反映する. VM exit を起こさず, guest の interrupt はすべて guest 内で処理される, Exit-Less-Interrupt (ELI) を提案する.

評価の結果, unmodified かつ untrusted guest が 97 - 100% の performance (compared to bare-metal) を達成した.

Background & Motivation

I/O は disk controller や NIC など, 仮想化環境において支配的な処理であり, direct device assignment (pass-through) の motivation となっている. I/O-intensive workloads においては, direct device assignment がなければ unacceptable な performance になるのだが, direct device assignment を行ったとしても bare-metal 環境と比較し, 60 - 65% 程度の performance しか出すことができない. direct device assignment はその I/O path において host の処理をほぼ全て取り除いているにも関わらずである (DMA remapping による host の介在しない DMA の発行, EPT による BAR の map による MMIO の direct access).

performance degradation は interrupt の処理で起きている.

interrupt handling

上の図の一番下が bare-metal. 対して一番上が現状の direct device assignment の時の interrupt の handle のされ方だ. 仮想化環境において, guest mode である場合,

  1. interrupt が起こるとまず VM exit が起きて host に戻る.
  2. そして host は hardware に interrupt completion を発行する.
  3. VM entry の際の virtual interrupt injection によって guest に interrupt を inject し resume.
  4. guest は物理マシンだと思っているので, 自分も interrupt completion を同様に発行する.
  5. guest の interrupt completion はまた VM exit を起こし host にもどる.
  6. host は interrupt completion の emulation をおこなって guest に resume.

ということが行われる. VM exit / entry の cost は non-I/O-intensive な workload にとってはそれほど問題がないが, I/O-intensive な workload + 近年の high performance storage / NIC にとっては, frequent な interrupt の生成により, これらは大きな問題となった.

これに対して, interrupt を減らすという手法をとる (例えば, polling や interrupt coalescing) こともできる.

  1. polling は OS handler の呼ばれる回数もコントロールできるが (batching すれば), batching する場合は latency が上がる. また, event がないときには CPU cycle を無駄にし, idle state になれないために power consuming でもある.
  2. interrupt と polling を組み合わせる hybrid な手法もある (interrupt 入ったらしばらく polling して… というやつ, NAPI では by default) が, dynamic に切り替える手法は実際には常にうまく行くというわけではない. その理由の一つは, 将来どれくらい interrupt が来るのか予測しなければならないという難しさだ (予測して interrupt / polling どちらのほうが cost が低いかを判断して切り替えるのだから).
  3. interrupt coalescing は, device に一定時間中の interrupt を coalescing して1回送らせる, もしくは一定回数の interrupt を coalescing して1回送らせるよう設定するものだ. これはもちろん latency が上がるという欠点があるが, それ以外に, 例えば TCP traffic の burst を引き起こしたりもする. coalescing の適切な parameter を求めるのは困難で, workload に依存する.

SR-IOV による asssignment ではいずれも高い CPU 利用率を bare-metal に対して示している. これについて, interrupt が大きなオーバーヘッドであることが demonstrate されている. 仮想化環境での interrupt の additional なオーバーヘッドを下げる研究についてもいくつか存在するが, hardware extension を要求する, Network device には適用できない (PV block device の virtual interrupt の coalescing を in flight な command 数に応じて行っている), polling を行うため前に述べた欠点がある, などの問題がある.

Design: ELI

cost となる 2つの exit (interrupt delivery と interrupt completion) を取り除くべく, この論文では ELI (Exit-Less-Interrupt) を提案する. 重要な点は, ある CPU core に来るほとんどすべての physical interrupt は, その CPU core で動いている guest に向けたもの だということだ. その理由は, high-performance を志向する guest は pinned な CPU core を専有しているであろうということ, そして I/O-intensive workload のために device assignment を SR-IOV を用いて行っていること, そして interrupt rate はふつうは実行時間に比例するということである.

Exitless Interrupt Delivery

仮想化の key は, ほとんどすべての時間を guest に使わせること. host の介在はなければないほどよい. ほとんどすべて guest への interrupt なのであれば, guest に interrupt を送ればよい.

ELI ではすべての physical interrupts を guest に送る (VT-x の制約上 all or nothing なので all しか選べない). そして, guest に関係のない, host のための interrupt は exit を起こして host に戻させる. この時, ELI は guest が trusted であったり, para-virtualized であったりといったことを求めない. ELI は unmodified かつ untrusted guest に対してこれを実現する.

ELI

guest が unmodified かつ untrusted であっても安全に host の interrupt を exit できるようにするために, ELI は Interrupt Descriptor Table (IDT)1 を shadowing し, shadow IDT を作成する. shadow IDT はおなじみ shadow page table に非常に近い. guest は自身の guest IDT が設定されていると思っているが, 実際には shadow IDT が設定されている. shadow IDT は guest IDT と in sync である.

上の Figure は ELI の interrupt delivery flow を示している. physical interrupt はすべて guest に exit なく伝えられ, shadow IDT によって assigned device については exit なく guest によって処理が行われる. 一方本来 host に伝えられるべきであった interrupt は, exit を起こすことによって guest を抜け, host にて処理がなされる.

shadow IDT は guest が通常触らない(普通のメモリとして使わない)が IDT として指定可能な空間である device PCI BAR に余分にページを増やし, そこに設置される2. shadow IDT は host に割り当てられた interrupt については non-present 状態にしておく. すると, interrupt が送られると NP exception が起こり, VM exit が発生する.

guest IDT は write-protect な状態に置かれ, 変更を検知し, shadow IDT へ反映する. これは shadow paging の手法とコンセプトは同じ. shadow IDT も guest による modify を避けるため write-protect 状態に置かれる3.

Exitless Interrupt Completion

もうひとつの exit, interrupt completion も取り除きたい. interrupt completion は EOI LAPIC register に書き込むことによって行われる. old interface では LAPIC area の一部に存在し, ほぼすべての LAPI access は exit を起こすが, 新しい x2APIC では MSR (Machine Specific Register) に書き込むことができる. MSR はそれぞれの MSR ごとに細粒度で exit するかどうかを control することができる.

そこで ELI では EOI x2APIC MSR を exit を起こさないで書き込めるように設定し guest の direct access を許可する. これによって interrupt completion についても exit を起こさずにすむ4.

Evaluations

筆者らは KVM 上に ELI を実装し評価を行っている. また性能向上のために host では huge pages を有効にしている. 確認したいのはそもそもの発端, I/O-intensive な workload における bare-metal に対する throughput や latency だ.

Throughput

Bare-metal に対する Throughput を示している. それぞれ Netperf TCP stream の write, Apache に向かって ApacheBench を用いて評価したもの, Memcached に向かって libmemcached の Memslap bench を (get 90%, set 10%) で実行したものだ.

それぞれの ELI の機能の contribution によって throughput は改善し, full ELI ではそれぞれ 98%, 97%, 100% の throughput (compared to bare-metal) を示している. interrupt delivery のほうが interrupt completion よりも寄与が大きいのは, host が interrupt を handling する処理のほうが複雑で, それが取り除かれたためであるとのこと.

Latency

次に, Netperf UDP の ping-pong を行い, その latency を評価する. Baseline (ELI なし) が bare-metal の 129% の高い latency を示しているのに対して, full ELI では 102% にまで改善している.

Conclusion

x86 仮想化が direct device assignment において interrupt の処理に host の介在が必要であることが high-performance storage and NIC にとって overhead となることを示し, ELI (shadow IDT, x2APIC MSR) の techniques を用いることによって, assigned device の interrupt の処理における VM exit を除去し, bare-metal throughput & latency を達成した. また, それを unmodified かつ untrusted guest に対して可能にした5.

Thoughts

問題が鮮やかで, 解法も従来の仮想化の手法を応用し, かつ特に何も犠牲にしておらず, 非常に堅実なものに思える. まあ動くからいいでしょ… としておいたものが, 環境が変わって (high-performance storange / NIC の登場など) 問題となるケース, かっこいいですね.

  1. IDT は interrupt の番号とそれぞれの handler をひも付ける

  2. IDT は kernel address space に map されている必要がある. map され, map され続け, かつ通常アクセスしない場所として device の PCI BAR に余分なページを増やしてそこに設置する. Pass-through の際, 例えば Xen では仮想 device を作る. 仮想化された PCI configuration space に元の device と同じ設定を置いておき, EPT を用いて host に見えている BAR の host physical address と, guest における guest physical address をひも付けて MMIO の direct access を許可する. この時, typical には (Linux / Windows) pci resource の大きさを query しそのサイズ全域を kernel address space に map する. ので, ここで例えば, 本来 1MB のものを 2MB だと宣言しておき, 後ろ 1MB を device の BAR ではない普通の memory に EPT でひも付けておけば, そこを shadow IDT として利用可能だ.

  3. EPT の該当エントリを read only に設定しておけばいけるか.

  4. じゃあなんで今までからしなかったのと言われれば, もちろん問題があったからだ. guest には例えば virtual な keyboard によって生成され inject された virtual interrupt と assigned device による physical interrupt の区別がつかない. しかし, virtual interrupt の場合は, EOI は host の emulation に向かって行われるべきで, physical な EOI x2APIC MSR に書き込まれるのは問題である. そこで, ELI では virtual interrupt を inject するときには injection mode という mode に fallback する. host が virtual interrupt を入れるとき, ELI は guest VM を injection mode にしてから inject する. injection mode は guest の IDT を shadow IDT から guest IDT に戻し, 代わりに interrupt の exit や EOI MSR 書き込みの exit を有効にする. 早い話が, ELI なしの普通の状態に一時的に戻す. すると virtual interrupt は普通に今までの方法で処理される. そして virtual interrupt の EOI を exit によって無事受け取ったらまた guest VM を ELI の mode に戻す.

  5. security についてはもう少しあったが補足で. 例えば guest が interrupt を disable にしたままにしてしまったら interrupt で exit しない以上 timer 割り込みも exit を起こさず, guest が走ったままになってしまう. これについては x86 virtualization の preemption timer を用いれば, unconditional exit を起こすことができる.

Comments