VirtualMachine本: Chapter 3 Process Virtual Machines
Virtual Machines: Versatile Platforms For Systems And Processes
VM本第三章やっと読み終わりました。ProcessVMについてです。
いろいろなVMがあるけど、ここではProcess or Program VMについて語られます。「仮想ABI」です。たとえばIA-32/WindowsのアプリケーションをItaniumのWindows上で動作させるものです。
3.1 Virtual Machine Implementation
Process VMのよくある構成について、主なブロック。
- loader
- guestのコードをロードする。
- initialization
- ロードされたあとに、初期化をするモジュール。code cacheとか、trapの登録とか、いろいろな前準備をする
- emulation engine
- インタープリットまたは・およびバイナリトランスレーションをする。プレデコードとかトランスレート結果はcode cacheに保持される。
- code cache manager
- コードキャッシュはたいてい限られているので、その管理をする。
- profile database
- ダイナミックに収集されたコードのプロファイル情報。最適化に使われる。
- OS Call Emulator
- OSとのインタラクションをエミュレートする。
- Exception Emulator
- Exception(trapや割り込み)をエミュレートする。
- Side tables
- トランスレートの信仰とともに生成されるデータ。用途はいろいろ。
3.2 Compatibility
ネイティブなプラットフォームとVM環境の互換性について。
3.2.1 Levels of Compatibility
"intrinsic compatibility"と"extrinsic compatibility"がある。
intrinsic compatibilityは"complete transparency"ともいわれる。多くの場合は完全な透過性はオーバースペック。
extrinsic compatibilityは、何らかの条件がつく。たとえば、特定のアプリケーションは動作するよ、とcertifyするなど。
3.2.2 A Comptibility Framework
intrinsicだろうがextrinsicだろうが、互換性があることを証明するのは大変むつかしい。その話をするためのフレームワークとして、まずはVMを構成要素に分解する。そして、各構成要素について(1)native環境とのステートマップ(2)ステート間遷移のマップをおこなう。このステートは、user-managedとOS-managedに分けることができる。
State Mapping
user-managedなステートマップはそんなに難しくない。guestのレジスタがhostのメモリにマップされるなどの場合があるが。OS-managed stateは、考え方が同じだがもうちょっと複雑。
Operations
user codeからOSへ制御がうつるポイントがキー。このマップがつくれれば、そのポイントでの状態が同等かどうかに集中できる。
Sufficient Compatibility Conditions
制御ポイントが明確になると、次のことがいえる:
- user code → OSのポイントで、ゲストの状態がホストの状態とマップ上同じである。ここからいえる重要なこと: 命令の粒度ではなく、OSへコントロールが渡る単位で状態の同等性をメンテナンスすればよい。
- OS → user codeのポイントでも、状態が同等。このとき、グラフィックとかネットワークとかみたいに、状態が同等なだけじゃなくてオペレーションの順番が同等である必要があるものもある。
Discussion
ここまでをふまえた議論。
3.2.3 Implementation Dependences
ISAの実装が、機能の差として顕れるケースがある。
たとえば、コードセグメントへの書き込みが、コードキャッシュにすぐ反映されない。
このような実装差が問題になるのは、たとえば、CPUの種別判定に使われる時。
逆に、仕様上は「コードキャッシュに反映されない」となっていても、実在の実装がすべてコードキャッシュに反映させているケースもある。そうすると、仕様に従って実装されたVMでは、現実のコード(自己変更含むもの)がうごかないかもしれない。「仕様に従っている」はいいわけにならない。
3.3 State Mapping
guest → targetの状態マップ。状態とはレジスタとメモリのことで、要はリソースなので、リソースマップと呼ぶこともある。
ここでのメモリは、論理メモリで実メモリスペースの話じゃない。
3.3.1 Register Mapping
レジスタマッピングはstraightforward(ってなんて訳すのがよいかな)。target側に充分なレジスタ数があれば、全部マップすればよいし、ダメならメモリにマップしたり、ダイナミックにマップを変更したりする。
3.3.2 Memory Address Space Mapping
メモリ空間のエミュレーションは、いろんなやり方がある。ソフトウェアの役割が大きいやりかたほど遅くなり、ハードウェアに任せる部分が多いほどパフォーマンスがよくなる。
Runtime Software-Supported Translation Tables
一番フレキシブル。ランタイムソフトウェアがメモリ管理。
でもバイナリトランレート環境ではかなり遅い。最後の手段。
Direct Translation Methods
単純なマッピング。同一アドレスか、単にオフセットを与えるかだけ。
Compatibility Issues
メモリマッピングとアドレス変換に何を使うか、はパフォーマンス・互換性の要求と深い関係がある。
メモリサイズや、ランタイムがアドレス空間を共用するか、など。
extrinsic compatibilityでよい場合も多い。
3.4 Memory Architecture Emulation
メモリアーキテクチャのエミュレーションで、考慮しなきゃいけないのは:
- アドレス空間の構成: セグメントがあるのか、連続なのか。
- アクセス権: R/W/Eがあるのか。RWのみか。
- 保護・アロケーションの粒度:メモリブロックのサイズ
3.4.1 Memory Protection
メモリプロテクションは、もし変換テーブルが用意されていれば簡単。しかし直接またはオフセットアドレッシングだとそうはいかない。ホストのメモリプロテクション機能に頼ることもできるが、ページサイズがホスト・ゲストで異なるとか、実行中にメモリの権限が変更されることもある。
3.4.2 Self-Referencing and Self-Modifying Code
自己参照はオリジナルの参照をしているからOK。自己書き換えについて、基本的なやりかたはRead onlyにしておくこと。例外のハンドリングで自己書き換えを扱うことができる。
psude-self-modifing code
psude-self-modifingという概念がある。code pageのなかに、実際にデータが混じっているもの。デバイスドライバや組み込みのコードに見られる。psude-self-modifing codeについては、「頻繁にwrite exceptionがおきるとき、それがpsude-self-modifingかどうかチェックして、その場合はwrite protectionをはずす」という手がある。
Fine-Grain Write Protection
ページ単位ではなく、もっと細かいwrite protectionをソースのコードに用意する。ページ毎にbit mask
True Self-modifing Code
ほんとうにSelf-modifing codeがある場合(code regionにデータがあるのではなく、実際にcodeがかきかわる場合)は、idiom recognitionでself modifyじゃないコードに書き換える。
Protecting Runtime Memory
Omniware VMという例では、2の累乗サイズのページごとにアクセス権を設定。それにより、アドレスのシフト演算で権限チェックが簡単にできる。
ランタイム実行中と、エミュレーション中で権限を切り替えるやりかたがある(エミュレーション中はランタイムのアドレスにはアクセスできない、そしてCode Cacheは実行可能。ランタイム実行中は、Code Cacheは読み書き可能、など)。
レジスタがメモリ上にマップされているときの問題もある。
3.5 Instruction Emulation
(続く)