x86-64/Linuxに独自メモリ空間を勝手増設3. エミュレータを高速化するに
は?
• ゲストとホストでCPUが異なる計算機を想
定
– ARM、MIPS、SPARC、Alphaをx86-64上で実行
• ゲストの仮想メモリがネック
– ソフトウェアでエミュレーションすると絶対
的に遅い
– ホストのMMUを使ってゲストのメモリ空間を
エミュレーションしましょう。
3
4. ホストMMUをどうやって使う
か?
• どうすればいい?
1. OSを自前で作る
2. OSから一部のCPUを切り離して独自処理
3. 既存のOSを改造
このLTではx86-64/Linux上で32ビットCPUを
エミュレーションする勝手改造を紹介しま
す
4
5. オレオレVM
• Linux仮想メモリ(VM)中に自分専用のVMを!
– https://github.com/nminoru/oleolevm/
x86風の2段のページテーブルを持つCPUを仮定
• 使い方
– /proc/oleolevm を mmap
– CPU 命令のインタプリータがロード・ストア
– ゲストのTLB ミス・保護違反は SIGSEGV シグナルで通知
Memory
Load/Store
mmap
CPU Interpreter
/proc/oleolevm oleolevm
SIGSEGV
ioctol or syscall 5
6. どのように改造するか?
• Hugetlb を参考に (`・ω・´)
• vm_area_structのvm_flagsを勝手に追加
– /include/linux/mm.h
#define VM_HUGETLB 0x00400000
#define VM_OLEOLETLB 0x100000000UL
• VM_HUGETLBとis_vm_hugetlb_pageを手掛
かりに追加
if (is_vm_hugetlb_page(vma))
return copy_hugetlb_page_range(dst_mm, src_mm, vma);
if (is_vm_oleoletlb_page(vma))
return -ENOMEM; 6
7. Shadow Page Table(SPT)
• ゲストの仮想メモリ空間をホストのページテーブルに
組み込む。
CR3
oleolevm 0x3,000,000
Guest Virt Space PGD
Page 512GB
0x2,000,000 PUD
空き4GB 1GB
写像 0x1,000,000 PMD
Guest Phy Space
2MB
Page PTE
gCR3 ST PT 4KB
0x0,000,000
Page
7
9. ゲストCR3を切り替え対応
• ゲストのコンテキストスイッチに応じてSPTの書き換
えが必要
• 毎回破棄するとコスト高なので過去のSPTも保存
– PUDをゲスト仮想空間1つに割り当てると効率がイイ!!
CR3
PGD PMD PMD PMD PMD
CR3が変更され
たら入れ替え
PUD PTE PTE PTE PTE
1GB
PMD PMD PMD PMD
PMD PMD PMD PMD
PTE PTE PTE PTE
PTE PTE PTE PTE
9
10. ゲストモード対応
• ゲストにもuser modeとsupervisor modeがあり
アクセス禁止領域が違う
• User mode用とsupervisor mode用の空間を分
けてしまえば解決
Guest Virt Space
(Supervisor)
空き CPU Interpreter
Guest Virt Space
(User)
空き
Guest Phy Space
10
11. JITコンパイラ対応
• 高速化のためのJIT Compilation
– Dynamic Binary Translationとも言う
• 分岐回数テーブル
– 普通はハッシュテーブル
Branch Counter Table
– オレオレVMならMMUで用意
• 翻訳コードへのlookup table Page カウン
タ
– これもMMUで用意できる
• 翻訳元コードの書込み保護 空き
– 書込み保護違反時にカーネル側 Guest Virt Space
で翻訳コードの破棄が可能
Page
命令
11
12. ゲストデバッグ機能対応
• Watchpoint Debug
– 指定したメモリ範囲にアクセスがあったことを検
出
• 問題
– アクセスの補足方法
• x86のdebug registerはエミュは使い辛い
• ゲストのwatchpoint範囲をカバーするようにSPTのペー
ジにプロテクションを付ける
– アドレスの判定
– Watchpointに引っかかったx86命令の再実行方法
12
13. アドレス判定
• #PF例外はCR2に例外を起こしたメモリアドレ
スを返すが不正確
– movq %rax, [0x0FFE] で #PF がでてもCR2は0x1000に
なるかも。
• x86命令をデコードしてアドレスを判定
– struct pt_regsに#PFが起きた時点のレジスタが格納
されている
– %ripから16バイトを読み込んで命令をデコード
– でもオペコードレベルで400命令以上のデコード
が必要 (´;ω;`)
13
14. 再実行
• ゲストのLOAD/STOREは#PF例外補足後に再実
行できないとダメ
– でもSPTにプロテクションをかけているよ
• x86を1命令エミュレーション
– #PFを起こしたx86命令をカーネルランドで1命令
エミュし、pt_regsに結果を書き戻す。%ripも1命
令分続ける。
• x86のSingle Step実行を使う
1. #PFハンドラでSPTのプロテクションを解除し、
EFLAGSにTFを立ててユーザランドに戻る
2. 1命令を実行したら#DB例外が発生
3. SPTのプロテクションを元に戻す
14
16. 参考文献
• Jim Smith, Ravi Nair, Virtual Machines:
Versatile Platforms for Systems and Processes
16
20. SPTの一貫性(1/2)
• ゲストのページテーブルとSPTの一貫性の維
持するには?
1. ゲストのページテーブル領域に書込み保護をか
ける
2. 毎回SPTを破棄
3. 毎回SPTをゲストのページテーブルとチェック
• x86-64の予約領域を使ってSPTのチェックを
遅延させる方法があるよ
– 特許4897578「仮想計算機の制御プログラムおよ
び仮想計算機システム」服部 直也ほか
20
21. SPTの一貫性(2/2)
• 予約領域へビットを打った場合「SPTを
チェックしろ」という意味
• RSVD=1の#PF例外をハンドルして検査
• 最初はPGDのreserved bitを打つ。
• #PF例外があがるとPGDのreserved bitを解除し
て直下のPUDの512エントリにresreved bitを打
つ。
• 中位のエントリのreserved bitを下位のテーブ
ルに写してゆく。
• 最後のPTEのreserved bitを解除する時にゲス
トPTEとチェックを行う
21
22. 改造なしで独自メモリ管理
• vm_area_structにvm_opsを設定
static int foo_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
vmf->page = page; // ここにロジックを入れる
}
static struct vm_operations_struct foo_vm_ops = {
.fault = foo_vm_fault,
};
static int foo_mmap(struct file *filp, struct vm_area_struct *vma)
{
vma->vm_ops = &foo_vm_ops;
}
static struct file_operations foo_fops = {
.mmap = foo_mmap,
};
22
23. X86-64の仮想メモリは将来拡張さ
れるか?
• U.S. Patent 6,671,791 Processor including a translation
unit for selectively translating virtual addresses of
different sizes using a plurality of paging tables and
mapping mechanisms
23