タイマ周りを追いかける.
実は受託案件が絡んでいる話の予備調査なので,組込み応用にするために具体的に何をすべきかは,ここでは書かない.
ソースコードを斜め読みしただけで理解できることに疑いないところだけ書く.
デバッガで追いかけたわけではないので,読み違えの可能性もある.記述内容を,後日,こっそり修正する可能性もある.「嘘を嘘と見抜ける奴でないと…(ry」ということで宜しく.
ちなみに,本エントリに出てくるソースコードは,xen-3.1.0.tgz にあったものである.
3.2.0もダウンロード可能になっているようで,
とある通り,タイマ周りに手が入ったらしい.仮にこのエントリの内容が,執筆時点での真実だったとしても,あっという間に陳腐化する可能性も高い.
Configurable timer modes for HVM guests, depending on how the guest OS manages time-keeping
組込み用途でXenを使おうとしたときに
組込み用途でXenを使おうとしたときに,気になるのはtime tickの分解能.
I/Oのスループットも気になるが,仮想化が進めば,PCIパススルーなども普通にできるようになるだろう.
I/Oのいくつかを手放した後も,タイマをXenが握り続けるのは間違いなく,その実装は気になる.
デフォルトの値と言われている10msでは,使える分野が限られてしまう.まあサーバ用のハイパバイザに,組込み用途云々言うのはお門違いという気もするけれども.
プリミティブなタイマハンドリング
3.1系でのタイマは*2,init_timer(), set_timer(), stop_timer() といった関数群が担っている.コールバック関数とタイムアウト時間を設定するという,オーソドックスなものだ.
Xenの中身をhackする気があるのなら,上記の関数を使って,好きなハンドラ関数を登録可能である.
しかし,通常の使用でXen自身をhackする奴は多くないだろう.もうちょっと上位層に視点を移す.
VCPUタイマ
VCPUは,periodic, singleshot, poll と3種類のタイマを提供している.これらは先述のタイマハンドラに基づいている.
periodicタイマは,ゲストOSにtickを与えるために強く望まれるのは判る.singuleshotやpollなタイマを,ゲストOSが何に使うのかはよく判らない.しかし,何か需要があってのことなのだろう.RTOSとして見れば,複数のタイマハンドラがあるのは不思議なことではない*3.
これらは,schedule.c の sched_init_vcpu() 辺りから追いかけていくと判りやすい.
/* Initialise the per-vcpu timers. */
init_timer(&v->periodic_timer, vcpu_periodic_timer_fn,
v, v->processor);
init_timer(&v->singleshot_timer, vcpu_singleshot_timer_fn,
v, v->processor);
init_timer(&v->poll_timer, poll_timer_fn,
v, v->processor);
sched_init_vcpu()から遡ってdomain.c のalloc_vcpu()を見て,その流れでvcpu_initialise()へ視点を移す.これはarch配下で,x86の場合は,下記のようにperiodicタイマのtickを10msに設定している.(arch/x86/domain.c)
/* PV guests by default have a 100Hz ticker. */ v->periodic_period = MILLISECS(10);
periodicタイマは,特にハードウェアの力を借りず,ハンドラ内で次の発生時刻を決めて再設定している.
/* Per-VCPU periodic timer function: sends a virtual timer interrupt. */ static void vcpu_periodic_timer_fn(void *data) { struct vcpu *v = data; vcpu_periodic_timer_work(v); } /* (snip) */ static void vcpu_periodic_timer_work(struct vcpu *v) { s_time_t now = NOW(); uint64_t periodic_next_event; ASSERT(!active_timer(&v->periodic_timer)); if ( v->periodic_period == 0 ) return; periodic_next_event = v->periodic_last_event + v->periodic_period; if ( now > periodic_next_event ) { send_timer_event(v); v->periodic_last_event = now; periodic_next_event = now + v->periodic_period; } v->periodic_timer.cpu = smp_processor_id(); set_timer(&v->periodic_timer, periodic_next_event); }
send_timer_event() は,arch以下のターゲット依存部にある.ia64やx86では,ゲストに対してVIRQ_TIMERを投げている.powerpcでは,VIRQ_TIMERを投げていなさそうな気もするのだけれども,私の読みが浅いだけなのだろう.
高負荷時に縮退する可能性があるはずなのだが,その辺のケアはしていない.時刻情報は与えるから後はゲストOSでなんとかしろ,というのがXenの設計思想だと耳にしていたが,即ち,そういうことなのだろう.
VCPUタイマは,ハイパーバイザコールで操作可能
docsにある文書類には,きちんとした情報が無いようなのだが,periodicとsingleshotのタイマに関しては,ハイパーバイザコールで周期設定や停止が行えるようになっている.
下記の通り,domain.c のコードで一目瞭然.
case VCPUOP_set_periodic_timer: { struct vcpu_set_periodic_timer set; if ( copy_from_guest(&set, arg, 1) ) return -EFAULT; if ( set.period_ns < MILLISECS(1) ) return -EINVAL; v->periodic_period = set.period_ns; vcpu_force_reschedule(v); break; } case VCPUOP_stop_periodic_timer:
1ms以下にすると,EINVALが返るらしい.まあ,1msで設定できるのであれば,大多数のRTOSは,別段のトリックも無く動くはず*4.縮退するかもという心配は,クロック上げれば概ね解決するだろう.Xen自身はRTOS並みに薄い層なので,理不尽な長さの割込み禁止が起こる可能性は低い(はず),Xen使うようなシステムでは,クロックを上げられないなんて悩みを持つなんて考えづらい.
しかし,HVMでは.
最近のx86用Xenは,HVMと呼ぶ完全仮想化のサポートがある.
オープンソースであるRTOSは少数派であるため*5,HVM上でも1ms単位のタイマが使えるかどうかは,組込みで使う上でも気になるところである.
さて,HVM周り.比較的新しいせいなのか,それとも私が思い及ばない理由があるのか,HVMのゲストと非HVMのゲストとで,タイマ周りの扱いが微妙に異なっている.
arch/x86/domain.cのvcpu_initialise()にもう一度立ち返ってみる.
int vcpu_initialise(struct vcpu *v) { struct domain *d = v->domain; int rc; v->arch.flags = TF_kernel_mode; pae_l3_cache_init(&v->arch.pae_l3_cache); paging_vcpu_init(v); if ( is_hvm_domain(d) ) { if ( (rc = hvm_vcpu_initialise(v)) != 0 ) return rc; } else { /* PV guests by default have a 100Hz ticker. */ v->periodic_period = MILLISECS(10);
is_hvm_domain() という,判りやすいネーミングの関数が前に控えている.
つまり,HVMドメインのとき,VCPUのperiodicタイマの周期は設定されない.
さらに,vcpu_periodic_timer_work()に立ち返ると自明だが,設定されないとき,periodicタイマは実行されない*6.
hvm_vcpu_initialise() *7
話はHVM固有の世界に飛んで行く.タイマに関しては,Xenが提供するプリミティブな関数群のことを頭に置く必要はあるものの,別の世界だと考えてしまっても構わない.
起点はarch/x86/hvm/hvm.cにあるhvm_vcpu_initialise().
int hvm_vcpu_initialise(struct vcpu *v) { int rc; if ( (rc = vlapic_init(v)) != 0 ) return rc; if ( (rc = hvm_funcs.vcpu_initialise(v)) != 0 ) { vlapic_destroy(v); return rc; }
hvm_funcs.vcpu_initialise() は,AMD SVMとIntel VMXの差異を吸収している.タイマ周りには関与していない.
タイマが直接関与するのは,arch/x86/hvm にある vlapic_init().vlapic == Virtual Local PIC らしい.
init_timer(&vlapic->pt.timer, pt_timer_fn, &vlapic->pt, v->processor); return 0; }
ハンドラである pt_timer_fn() は,xen/arch/vpt.c にある.
/* Hook function for the platform periodic time */ void pt_timer_fn(void *data) { struct periodic_time *pt = data; pt->pending_intr_nr++; pt->scheduled += pt->period; missed_ticks(pt); if ( !pt->one_shot ) set_timer(&pt->timer, pt->scheduled); vcpu_kick(pt->vcpu); }
そろそろ疲れてきたので詳細は省略するが,missed_tick()が,タイマ呼び出しの縮退についてケアしている処理になる.HVMでは,縮退の対策をゲストOSに行わせるわけにはいかない.
どんな感じでケアしているかは,periodic_time構造体のpending_intr_nrメンバを処理している箇所を重点的に見れば解るはず.
ちなみに,処理性能が低いCPUで動くHVMドメインのタイマをあまり細かくすると,ちょいとまずいことが起こるかもしれない.missed_ticks()のコードを見ると,TODO とだけしてあって,具体的な処理がない.
static __inline__ void missed_ticks(struct periodic_time *pt) { s_time_t missed_ticks; missed_ticks = NOW() - pt->scheduled; if ( missed_ticks > 0 ) { missed_ticks = missed_ticks / (s_time_t) pt->period + 1; if ( missed_ticks > 1000 ) { /* TODO: Adjust guest time together */ pt->pending_intr_nr++; }