ちょっと詳しいEPT
はじめに
最近hypervisor関連の論文を読み漁っているのですが、EPTに関して知識不足だなと感じたので一度ちゃんとインプットをしようと思いSDMを読み返したりEPTに関連する論文をいくつか読んでみました。アドレス変換、パフォーマンス、キャッシュあたりを中心に調べたので、簡単にまとめてみます。随時更新するつもりです。
0. 準備
まずは準備として簡単に省略形や用語の整理をしておきたいと思います。
省略形の整理
基本的には省略形があるものはそれを使って説明するので事前に整理しておきます。
- EPT(Extended Page Table)
- GVA(Guest Virtual Address)
- GPA(Guest Physical Address)
- HVA(Host Virtual Address)
- HPA(Host Physical Address)
- EPTP(EPT Pointer)
- VMCS(Virtual Machine Control Structure)
- TLB(Translation Lookaside Buffer)
用語の説明
SDMで使用されている用語について、予め解説しておきます。
paging-structure
PML4, PDPT, PD, PTの各エントリのこと。
page frame
物理ページのこと。アドレス変換では、リニアアドレスのpage numberからこのpage frameの物理アドレスを計算する。
page number
リニアアドレスに含まれている, page frameを見つけるためのindexのこと。4KBページを採用している場合は47:12までの36ビットであり、2MBの場合は47:21までの27ビットのこと。
EPTとは?
EPT(Extended Page Table)はゲストOSの物理アドレスをホストOSの物理アドレスに変換するためのページテーブルです.
1. EPTのアドレス変換
EPTにおけるアドレス変換の流れはとてもシンプルで、通常の4-level pagingとほとんど変わりません。EPTが有効になっている場合でも、スタートは普通のアドレス変換と同様に(ゲスト)CR3レジスタを参照し、PML4, PDPT, PD, PTのベースアドレスを求めていきます。普通のアドレス変換と違うのは、PML4~PTのベースアドレスがGPAであり、HPAに変換する必要があるということです(以下の図を参照)。
2. EPTのパフォーマンス
EPTのアドレス変換の仕組みを理解すると、単純に 「通常のアドレス変換よりもかなりオーバーヘッドが大きいのでは?」 という疑問が湧きます。キャッシュなどを考慮しないとすれば、これはYESと言って問題ないと思います[1]。
通常のメモリアクセスでは、リニアアドレスから物理アドレスへの変換に最悪4回のメモリアクセスを必要とします。(PML4, PDPT, PD, PTそれぞれのpaging structureへのアクセス) EPTではPML4, PDPT, PDT, PTの各ベースアドレス(GPA)をHPAに変換する必要があり、それぞれの変換で5回のメモリアクセスが発生します。(正確にはPTに関しては4回になります)最終的に、EPTによるアドレス変換では最悪の場合24回のメモリアクセスが発生します。
Intel SGX Explainedより引用
2MBページについて
page-walkによるメモリアクセスの回数を減らすための一般的な手法として、比較的大きなページサイズを扱う手法があります。例えば、2MBページを有効にすると、PDが指すのが次のpaging-structureではなく最終的なpage frameの物理アドレスになります。これによって、TLBミス時のメモリアクセスの回数が1回減ります。「Performance Implications of ExtendedPage Tables on Virtualized x86 Processors」という論文ではゲストとホストがそれぞれ2MBページ or 4KBページを使用した場合のTLBミス時のCPUサイクル数などを計測しているので、興味があれば読んでみると面白いと思います。
3. EPTとキャッシュ
2で説明したように、EPTのアドレス変換は比較的オーバーヘッドの大きい仕組みです。とはいえ、実際のメモリアクセスではGVA-GPA, GPA-HPA, GVA-HPA間のそれぞれでキャッシュが用意されています。そのため、必ずしも多くのメモリアクセスが発生する訳ではないです。
3.1 TLBとpaging-structure cacheの復習
EPT環境におけるキャッシュを説明する前に、通常のアドレス変換におけるキャッシュについて復習しておきます。アドレス変換におけるキャッシュには2種類あります。それが、TLB
とpaging-structure cache
です。ざっくりと言えば、TLBはpage number
とpage frame
の対応 を記録したキャッシュで、paging-structure cacheはpage numberの一部と対応するpaging-structureを記録したキャッシュです。 具体的なアドレスがあると分かりやすいので、今回は 0x0b900c0ae0c2016
というリニアアドレス[2]があった時に、どのようにキャッシュが働くのかを見ていきます。
TLBの場合
TLBの場合はpage numberとpage frameのマッピングなので単純です。実際には、アドレス変換に使用された全てのpaging-structuresの read/write
, user/supervisor
, dirty
, execute-disable
ビットなどの情報も含まれています。以下の図のようなイメージです。(890というのが対応するpafe frameの物理アドレスで、これに016をoffsetとして加算した値が最終的な物理アドレスになります)
paging-structure cacheの場合
paging-structure cacheはpage numberの一部と対応するpaging-structureのmappingをキャッシュします。Intelではアドレス変換における各レベル(L4~L2)のキャッシュを区別しており、それぞれPML4 cache
,PDP cache
, PDE cache
と呼ばれています。図の右側にあるのは、各indexに対応するpaging-structureの物理アドレスです。
よくあるページングの図に合わせて見てみると、以下のようなイメージになります。CPUはとにかくアドレス変換に必要なメモリアクセスの回数を減らしたいので、まずはTLBを参照します。TLBヒットすれば最終的なpage frameが一発で分かるので、アドレス変換に必要なメモリアクセスの回数は0回になります。TLBミスした場合は、page-walkする必要があります。page frameに近いPDE cahceから探していき、PML4 cacheまで探してもキャッシュが見つからなければCR3にアクセスして地道にpage-walkしていきます。PDE cacheにヒットした場合は1回, PDP cacheの場合は2回, PML4 cacheの場合は3回のメモリアクセスが必要になります。TLBと同様に、実際にはread/write
, user/supervisor
, dirty
, execute-disable
ビットなどの情報も含まれています。
3.2 EPTにおけるTLBとpaging-structure cache
EPT環境で使用されるアドレス変換キャッシュにはGuest-physical mapping
とCombined mapping
の2つがあります。まずはそれぞれについて簡単に説明します。(仕様書ではTLBのエントリのことをtranslation
と表記しているので、ここではそれに従います。意味は変わらずpage numberとpage frameのmappingです)
-
Guest physical mapping
- GPAとHPAのmapping
- translationとpaging-structure cacheの2種類がある。translationの場合は、GPAのpage numberと(host)page frameのmapping。paging-structure cacheの場合は、GPAのpage numberの一部と(host)page frameのmapping。
-
Combined mapping
- GVAとHPAのmapping
- translationとpaging-structure cacheの2種類がある。translationの場合は、GVAのpage numberと(host)page frameのmapping。paging-structure cacheの場合は、GVAのpage numberの一部と(host)page frameのmapping。
3.3 EPTとVPID
EPT環境ではアドレス変換以外にも性能面での問題がありました。その一つに、VMX transition時のTLBフラッシュによる性能劣化が挙げられます。初期の仮想化では、VMX transition (VMExit, VMEnter や ゲストVMの切り替え) のたびにTLB(とpaging-structure cahceの)フラッシュが発生し、これがパフォーマンス上の大きなボトルネックになっていました。
この問題を解決するために導入されたのが、Virtual Processor Identifier (VPID) という仕組みです。VPIDは各vCPUにユニークなIDを振ります。これによって物理プロセッサはvCPUをIDによって識別することが可能になりました。そして、このVPIDをTLBのエントリにも取り入れることでVM transition時のTLBフラッシュが大幅に軽減されました。
PCIDについて
VPIDについて見ていく前に、PCID(Process-context identifier) に関して復習しておきます。PCIDはCPUが複数のリニアアドレス空間の情報をキャッシュするための機能で、プロセスごとにユニークなIDを振ります。PCIDが有効になっている場合、CPUはTLBやpaging-structure cacheのエントリを作成する時に、それらのエントリとcurrent PCID
を紐付けます。
例えば、システムコールの呼び出しなどでuser-mode
からkernel-mode
へのスイッチが発生し、それに伴いページテーブルの切り替えが発生したとします(実際にKPTIが有効になっているLinuxバージョンでは切り替えが発生します)。PCIDが無効になっている場合、CPUはTLBやpaging-structure cacheのエントリをフラッシュする必要があります。一方で、PCIDが有効になっている場合、CPUはcurrent PCID
を切り替えるだけでTLBやpaging-structure cache全体をフラッシュする必要はありません。(ページテーブルの切り替え時に発生するCR3の変更などのコストは発生すると思います)
EP4TAについて
EP4TAはEPTPの51:12までの40ビットのことです。現在のCPUではEPTP switching
などが実装されている場合もあり、こういったケースでは1つのvCPUが複数のEPTを使い分けるような使い方が想定されるため、vCPUに加えて現在使用しているEPTについての情報も必要になります。
VPIDの使用例
PCIDとEP4TAについて復習したのは、EPT環境においてTLB, paging-structure cacheエントリはEP4TA, VPID, PCID の3つの識別子の組み合わせによってタグ付けされるからです[3]。VPIDはPCIDのVMバージョンみたいなもので、VM(より正確にはvCPU)ごとにユニークなIDを振り、TLBやpaging-structure cacheのエントリ作成時にcurrent VPID
を紐付けます。
TLBはアドレス変換の時にcurrent PCID, VPID, EP4TAに一致しないキャッシュは使用しないので、VMX transition時にこれらのタグ付けがされたキャッシュエントリは残しておいてもアドレス変換に支障はありません。キャッシュが残るので、VMExitした後にVMEnterした場合残ったキャッシュを使ってゲストを再開できます。
4. EPTのいろいろな用途
ここまででEPTの基本的な内容について解説したので、以降ではEPTのいろいろな用途について見ていきます。
メモリ保護
EPTを使うと、ゲストOSからのメモリアクセスを検知したり、ブロックしたり出来ます。具体例として、カーネルの特定のコードやデータが格納されているアドレスへの書き込みをブロックすることができます。このような、マルウェアやrootkitからゲストOSを守る用途でもEPTはよく使われます。
dirtyビットによる書き込み検知
EPTで使われるpaging structureは基本的には通常のpaging structureと変わりませんが、いくつかの点で差異があります。その中でも特徴的なのが、dirtyビットです。ゲストOSから特定のメモリに書き込みがあった場合、書き込まれたpage frameと、参照に使われたpaging structureのdirtyビットが立ちます。この処理はハードウェアで行われるため、書き込みに際してのオーバーヘッドはほとんどありません。代表的な用途として、ライブマイグレーションにおけるページへの書き込み検知などが挙げられます。
おまけ:ライブマイグレーションにおけるcopy方式
詳細は割愛しますが、ライブマイグレーションは動作中のVMを別のホストに移動させる機能です。CPUのレジスタやメモリ、NICなどの周辺デバイスの状態を保存して転送先ホストへ送るのですが、代表的な転送方式にpre-copy方式とpost-copy方式があります。pre-copy方式は事前にメモリをある程度転送しておき、その後ゲストを一定時間停止させて残りのメモリを転送する方式です。当然ながらこのダウンタイムは小さければ小さいほど良いです。すべてのメモリを転送後、移行先ホストでVMの状態を復元して動作を再開します。post-copy方式は最初にCPUやデバイスなどの比較的軽いデータを送ってしまい、いきなり移行先でVMを再開してしまいます。メモリに関してはアクセスされるたびにオンデマンドで移行元ホストから取得します。事前のメモリ転送がない分、移行先への切り替えは速いですが、パフォーマンス劣化などのデメリットがあります。
dirtyビットが使われるのは、pre-copy方式のときです。pre-copy方式では動作中のVMのメモリを転送するため、転送途中でメモリに書き込みが発生した場合はそのメモリを再転送する必要があります。基本的に、pre-copy方式ではダウンタイム(stop-and-copy)発生前に一定の閾値までメモリを転送します。1回目はすべてのメモリを転送するので、2回目以降は書き込みが発生したメモリを再転送するだけです。ここで問題なのが、「転送済みメモリが書き込まれたことをどのように検知するか」です。既に説明したように、EPTにはdirtyビットという機能があり、ゲストOSが特定のメモリに書き込んだ際にそれを検知することができます。VMMはEPTEを走査して、dirtyビットが立っているpage frameを特定します。
QEMUにおける実装(WIP)
おそらくここら辺だよなーというのは見つけたのですが、いまいち理解できていないのでちゃんと分かり次第追記しようと思います。今のところ分かっているのは、ram_find_and_save_block
という関数が、
finds a dirty page and sends it to f
と書かれているのでそれっぽくて、この関数から辿っていくと
ram_find_and_save_block
-> find_dirty_block
-> migration_bitmap_find_dirty
-> find_next_bit
という流れで呼び出されています。find_next_bitはmigrationはユーティリティ関数のようなので、dirty bit以外でも使えるっぽいです。
まとめ
今回はEPTの中でも特に、アドレス変換の仕組み、パフォーマンス、キャッシュについて調べた限りのことをまとめました。今回詳しくは触れられなかった内容の中でも、EPTに関連する面白い話題はたくさんあります。この記事を書くにあたって、いくつか面白い論文も見つけました。記事内の説明で参考にした資料や文献についてはリンクを貼っていますが、それ以外の論文について知りたい/記事を書いてほしいという方がいましたら コメント or @oshiboriまでご連絡ください。
参考
- Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 3A, 3B, 3C, and 3D: System Programming Guide
- TLBs, Paging-Structure Caches, and
Their Invalidation - Performance Implications of Extended Page Tables on Virtualized x86 Processors
- Translation Caching: Skip, Don’t Walk (the Page Table)
- Intel SGX Explained
- EPT と TLB でしくじった話
-
参考に載せているPerformance Implications of ExtendedPage Tables on Virtualized x86 Processorsという論文ではEPTにおけるキャッシュの働きやパフォーマンスへの影響について詳しく解説されています ↩︎
-
このアドレスは参考にも載せているTranslation Caching: Skip, Don’t Walk (the Page Table)という論文で説明に使われているアドレスをそのまま使っています ↩︎
-
この説明は実は正確ではありません。EPT環境では、Guest-physical mappingとCombined-mappingという2種類のキャッシュがあり、それぞれにTLB(translation)とpaging-structure cacheがありました。結論から書くと、Combined mappingにはEP4TA, VPID, PCIDの3つの情報が紐付くのですが、Guest-physical mappingに関してはEP4TAだけが紐付きます。 ↩︎
Discussion