BitVisor で IPI を使って別コアで処理を実行する


(~~Preemption timer のことを書くといったな，あれは嘘だ~~)
(本当は BitVisor Advent Calendar 24 日目として書いていたんですが，気が付いたら24日目は既に埋めていただいていたので25日目の記事にしました)
(たぶん Preemption timer の記事よりこっちの方が需要あると思う)


# はじめに
BitVisor でマルチコアなマシンでいろいろやろうとすると，あるコアから別のコアへ処理の実行を依頼したいことがあります．
例としては TLB Shootdown なんかがあると思います．
しかし，BitVisor にはコア間で処理を実行させる仕組みがありません．

一般的に，このようなコア間での処理の依頼は Inter-processor interrupt (IPI) を用いて実現しています．
そこで，今回は BitVisor で IPI を用いて別コアに処理の実行を依頼する方法について書きたいと思います．

# IPI の簡単な説明
IPI とはマルチコアシステムにおいて，あるコアから別のコアに対して割り込みを送る機能です．
いまどきの x86 マシンでは，IOAPIC がこの機能を提供しています．
ちなみに，IPI には Startup IPI (コア起動時に送る特殊な IPI), NMI などいくつか種類があります．
詳しくは Intel SDM 3A の APIC の章を見ていただけるといいと思います．

# BitVisor での IPI の用途
BitVisor でも一部 IPI を使っている部分があります．
それは起動時とパニック時です．
起動時には BSP のコアが AP のコアを起動するために Startup IPI を送信しています．
パニック時には，あるコアがパニックした際に，全てのコアを停止を要求するために IPI を送信します．

前者の startup IPI は起動時にのみ用いる IPI であり今回の目的とは少し違っています．
後者の IPI の使い方は，今回やりたい用途に似ているため，まずはこちらについて見てみましょう．

## パニック時の IPI について
BitVisor は VMExit の度に，他のコアが panic していないかをチェックするようになっており (`panic_test ()`) ，他コアが panic していれば，自コアでも `panic ()` を実行するようになっています．
しかし，BitVisor は設定によっては稀にしか VMExit しない場合があります．
そこで，確実に全コアで VMExit させるために IPI を用います．

BitVisor は割り込みパススルーな設定で動作することもできるようになっています．
このため，普通の割り込みを送っても BitVisor は動作しない可能性があります．
これでは困るので，BitVisor は NMI をフックするようにし，パニック時には NMI の IPI を送信しています．

全体の流れとしてはあるコアがパニックしたら全コアに IPI (NMI) を送信，全てのコアが VMExit (Exception or NMI) --> panic_test () -->`panic ()`  という流れで停止します．


NMI の送信処理は `panic_wakeup_all ()` という関数で行っています．
ソースコードの詳細は省きますが，IOAPIC の　Interrupt Command Register (ICR) というレジスタを叩くことで IPI を起こしています．


また，パニック時以外に別の要因で発生した NMI については，BitVisor がエミュレートしてゲスト OS に送っています．

## 別コアで処理を実行するために IPI を使ってみる
別コアで処理を実行しようとすると，上記のパニックの仕組みだけでは不十分です．
具体的には 

1. 特定のコアに対してのみ IPI を転送する機能がない．
1. BitVisor は NMI をゲストに転送する以外のハンドリングをしない．
    - BitVisor が生成した NMI か，別の要因で生成した NMI かを区別する必要がある．
        - 前者は BitVisor で処理した後に隠蔽，後者はこれまで同様ゲスト OS に転送が必要．
    - NMI 隠蔽処理がない．具体的には，VMEntry
 前に CPU が NMI の処理が完了した状態にするために，BitVisor で iret 命令を発行しなければならない．

1 は特に IOAPIC 側に制約があるわけでもないので，そういうコードを書くとよいです． https://bitbucket.org/snippets/ftakaaki/77a8n/using-nmi-to-request-other-core-in#file-nmi.patch のパッチに中に `core/ap.c` と `core/ap.h` の変更で，特定のコアにのみ IPI を送るための関数を作れると思います．
(その他の部分は，BitVisor 本体の変更により使えなくなっているので，下記のパッチを参照してください)

2 に対応するためには，IPI (NMI) を送信する前に，これから届く NMI は BitVisor によって生成されたものだということを示すフラグを作り，そのフラグが立っている時に NMI を受け取ったら
 BitVisor で `iret` 命令を実行し，NMI のエミュレーション処理をスキップします．

というわけで，なんとなく TLB shootdown っぽいことをするためには以下のようなコードを書くといいんじゃないかと思います．
(以前書いて，試しに少し動かしたものを整理してパッチにしたものなので，**ちゃんと動くかわかりませんが**...)
なお，TLB shootdown は全コアで同じ処理を行って欲しいので，IPI (NMI) の送信は `wakeup_panic_all ()` を使いまわしています．

```diff
diff -r 34fa14de2421 core/main.c
--- a/core/main.c	Mon Dec 04 19:24:33 2017 +0900
+++ b/core/main.c	Sun Dec 24 23:12:17 2017 +0900
@@ -401,6 +401,7 @@
 		current->vmctl.init_signal ();
 	}
 	current->vmctl.enable_resume ();
+	current->has_handled_nmi_from_host = true; //Initialize a flag
 	current->initialized = true;
 	sync_all_processors ();
 	if (bsp)
diff -r 34fa14de2421 core/vcpu.h
--- a/core/vcpu.h	Mon Dec 04 19:24:33 2017 +0900
+++ b/core/vcpu.h	Sun Dec 24 23:12:17 2017 +0900
@@ -87,6 +87,7 @@
 	struct localapic_data localapic;
 	struct sx_init_func sx_init;
 	struct cache_data cache;
+	bool has_handled_nmi_from_host;
 };
 
 void vcpu_list_foreach (bool (*func) (struct vcpu *p, void *q), void *q);
diff -r 34fa14de2421 core/vt_main.c
--- a/core/vt_main.c	Mon Dec 04 19:24:33 2017 +0900
+++ b/core/vt_main.c	Sun Dec 24 23:12:17 2017 +0900
@@ -187,12 +187,48 @@
 	vid->vmcs_instruction_len = 0;
 }
 
+static void
+seize_nmi (void)
+{
+	/* FIXME: Support only 64bit */
+	asm volatile (
+		"mov %%rsp, %%rbx   \n"
+		"xor %%rax, %%rax   \n"
+		"mov %%ss, %%ax     \n"
+		"pushq %%rax        \n"
+		"pushq %%rbx        \n"
+		"pushfq             \n"
+		"mov %%cs, %%ax     \n"
+		"pushq %%rax        \n"
+		"mov $next, %%rax   \n"
+		"pushq %%rax        \n"
+		"iretq              \n"
+		"next:              \n"
+		"nop                \n"
+		:
+		:
+		: "rax", "rbx", "memory"
+		);
+}
+
+static void
+handle_nmi_from_host () {
+	printf ("Handle NMI by %d\n", currentcpu->cpunum);
+	vt_paging_flush_guest_tlb();
+}
+
 /* NMI handler.  FIXME: This is currently pass-through only. */
 static void
 vt_nmi_has_come (void)
 {
 	ulong is, proc_based_vmexec_ctl;
 
+	if (!current->has_handled_nmi_from_host) {
+		current->has_handled_nmi_from_host = true;
+		handle_nmi_from_host ();
+		return;
+	}
+
 	/* If blocking by NMI bit is set, the NMI will not be
 	 * generated since an NMI handler in the guest operating
 	 * system is running. */
@@ -309,6 +345,7 @@
 			current->u.vt.intr.vmcs_instruction_len = len;
 			break;
 		case INTR_INFO_TYPE_NMI:
+			seize_nmi();
 			vt_nmi_has_come ();
 			break;
 		case INTR_INFO_TYPE_EXTERNAL:
diff -r 34fa14de2421 core/vt_paging.c
--- a/core/vt_paging.c	Mon Dec 04 19:24:33 2017 +0900
+++ b/core/vt_paging.c	Sun Dec 24 23:12:17 2017 +0900
@@ -27,6 +27,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "ap.h"
 #include "convert.h"
 #include "cpu_mmu_spt.h"
 #include "current.h"
@@ -37,6 +38,51 @@
 #include "vt_paging.h"
 #include "vt_regs.h"
 
+static bool
+clear_nmi_flag (struct vcpu *p, void *dummy)
+{
+	p->has_handled_nmi_from_host = false;
+	return false;
+}
+
+static bool
+check_nmi_flag (struct vcpu *p, void *r)
+{
+	bool *ret = r;
+	*ret = p->has_handled_nmi_from_host;
+	if (*ret) {
+		/* Continue the foreach loop */
+		return false;
+	}
+	/* Break the foreach loop */
+	return true;
+}
+
+static bool
+check_nmi_flag_all ()
+{
+	bool done = false;
+	vcpu_list_foreach (check_nmi_flag, &done);
+	return done;
+}
+bool
+vt_tlb_shootdown ()
+{
+	bool ret;
+	int i;
+	vcpu_list_foreach (clear_nmi_flag, NULL);
+	panic_wakeup_all();
+	for ( i = 0; i < 100; i++ ){
+		if (check_nmi_flag_all()) {
+			ret = true;
+			break;
+		} else {
+			usleep (100);
+		}
+	}
+	return ret;
+}
+
 bool
 vt_paging_extern_flush_tlb_entry (struct vcpu *p, phys_t s, phys_t e)
 {

```

# おわりに
BitVisor で IPI を使って別コアで処理を実行する方法を書きました．
コードは昔書いたものを基にここに載せるように編集したものなので，動かかなったらご一報ください．

# (おまけ) Preemption timer について
せっかくなので，Preemption timer についても少し書きます．

BitVisor では，設定によっては驚くほど VMExit が起きません．
これは性能面では非常にありがたいのですが，BitVisor で定期的に行いたい処理ができなくて困ることがあります.
そんな人のために(なんだと思うんですが) Intel VT-x では，preemption timer という機能を提供しています．
これは，まさにタイマデバイスのごとく一定時間毎に VMExit を起こすことができる機能です．
なのでBitVisor を弄っている人たちは使うこともあるかもしれません．
そこで，一言言いたいんですが
「VT-x の Preemption timer は **コアが C2 ステートより深く眠ると止まる** ので気を付けて使おうね．」

以下 SDM より引用

> The timer is not decremented in C-states deeper than C2.



## BitVisor Advent Calendar 最終日
とうとうクリスマスですね!
皆さん，BitVisor を使って楽しいクリスマスを過ごしていることかと思います．
今年も hdk_2 さんをはじめ，参加者の皆さんのおかげで BitVisor Advent Calendar 完走できそうでよかったです．

ちなみに，Qiita に投稿されている BitVisor タグが付いた記事はこれで 74 個目だそうです．
今回の Advent Calendar は 3 回目なわけです 25 日 * 3 回 とほぼ記事数なところを見ると，どうやらほとんどの記事は Advent Calendar の記事として書かれているみたいですね．