本文由做者朱益軍受權網易雲社區發佈。linux
簡介
在實際業務中,guest執行HLT指令是致使虛擬化overhead的一個重要緣由。如[1].api
KVM halt polling特性就是爲了解決這一個問題被引入的,它在Linux 4.3-rc1被合入主幹內核,其基本原理是當guest idle發生vm-exit時,host 繼續polling一段時間,用於減小guest的業務時延。進一步講,在vcpu進入idle以後,guest內核默認處理是執行HLT指令,就會發生vm-exit,host kernel並不立刻讓出物理核給調度器,而是poll一段時間,若guest在這段時間內被喚醒,即可以立刻調度回該vcpu線程繼續運行。安全
polling機制帶來時延上的下降,至少是一個線程調度週期,一般是幾微妙,但最終的性能提高是跟guest內業務模型相關的。若是在host kernel polling期間,沒有喚醒事件發生或是運行隊列裏面其餘任務變成runnable狀態,那麼調度器就會被喚醒去幹其餘任務的事。所以,halt polling機制對於那些在很短期間隔就會被喚醒一次的業務特別有效。服務器
代碼流程
guest執行HLT指令發生vm-exit後,kvm處理該異常,在kvm_emulate_halt處理最後調用kvm_vcpu_halt(vcpu)。架構
int kvm_vcpu_halt(struct kvm_vcpu *vcpu){ide
++vcpu->stat.halt_exits; if (lapic_in_kernel(vcpu)) { vcpu->arch.mp_state = KVM_MP_STATE_HALTED; return 1; } else { vcpu->run->exit_reason = KVM_EXIT_HLT; return 0; }
}
將mp_state的值置爲KVM_MP_STATE_HALTED,並返回1。函數
static int vcpu_run(struct kvm_vcpu vcpu){ int r; struct kvm kvm = vcpu->kvm;性能
vcpu->srcu_idx = srcu_read_lock(&kvm->srcu); for (;;) { if (kvm_vcpu_running(vcpu)) { r = vcpu_enter_guest(vcpu); } else { r = vcpu_block(kvm, vcpu); } if (r <= 0) break; //省略 }
}
因爲kvm處理完halt異常後返回1,故主循環不退出,但在下一個循環時kvm_vcpu_running(vcpu)返回false,因此進入vcpu_block()分支,隨機調用kvm_vcpu_block()。.net
通用的halt polling代碼在virt/kvm/kvm_main.c文件中的額kvm_vcpu_block()函數中實現。線程
ktime_t stop = ktime_add_ns(ktime_get(), vcpu->halt_poll_ns);do { /*
* This sets KVM_REQ_UNHALT if an interrupt * arrives. */ if (kvm_vcpu_check_block(vcpu) < 0) { ++vcpu->stat.halt_successful_poll; if (!vcpu_valid_wakeup(vcpu)) ++vcpu->stat.halt_poll_invalid; goto out; } cur = ktime_get();
} while (single_task_running() && ktime_before(cur, stop));
狀況一:若是當前物理核上沒有其餘task處於running狀態,並且在polling時間間隔內,那麼就一直等着,直到kvm_vcpu_check_block(vcpu) < 0,即vcpu等待的中斷到達,便跳出循環。
out:
block_ns = ktime_to_ns(cur) - ktime_to_ns(start); if (!vcpu_valid_wakeup(vcpu)) shrink_halt_poll_ns(vcpu); else if (halt_poll_ns) { if (block_ns <= vcpu->halt_poll_ns) ; /* we had a long block, shrink polling */ else if (vcpu->halt_poll_ns && block_ns > halt_poll_ns) shrink_halt_poll_ns(vcpu); /* we had a short halt and our poll time is too small */ else if (vcpu->halt_poll_ns < halt_poll_ns && block_ns < halt_poll_ns) grow_halt_poll_ns(vcpu); } else vcpu->halt_poll_ns = 0; trace_kvm_vcpu_wakeup(block_ns, waited, vcpu_valid_wakeup(vcpu)); kvm_arch_vcpu_block_finish(vcpu);
這段代碼主要用於調整下一次polling的等待時間。若block_ns大於halt_poll_ns,即vcpu halt時間很短就被喚醒了,則把下一次的halt_poll_ns調長;不然,減短。
狀況二:若是當前物理核上其餘task變成running態,或polling時間到期,則喚醒調度器,調度其餘任務,以下代碼。
for (;;) {
prepare_to_swait(&vcpu->wq, &wait, TASK_INTERRUPTIBLE); if (kvm_vcpu_check_block(vcpu) < 0) break; waited = true; schedule(); }
模塊參數說明
Module Parameter Description default Value
halt_poll_ns The global max polling interval which defines the ceiling value which defines the ceiling value which defines the ceiling value of the polling interval for each vcpu. KVM_HALT_POLL_NS_DEFAULT (per arch value)
halt_poll_ns_grow The value by which the halt polling interval is multiplied polling interval is multiplied polling interval is multiplied in the grow_halt_poll_ns() function. 2
halt_poll_ns_shrink The value by which the halt polling interval is divided in the shrink_halt_poll_ns() function. 0
kvm用這3個參數來動態調整最大halt polling時長。debugfs下/sys/module/kvm/parameters/存放着這3個模塊參數的默認值。X86架構下,KVM_HALT_POLL_NS_DEFAULT默認值爲400000。 grow一次,值乘以halt_poll_ns_grow:
static void grow_halt_poll_ns(struct kvm_vcpu *vcpu){
unsigned int old, val, grow; old = val = vcpu->halt_poll_ns; grow = READ_ONCE(halt_poll_ns_grow); /* 10us base */ if (val == 0 && grow) val = 10000; else val *= grow; if (val > halt_poll_ns) val = halt_poll_ns; vcpu->halt_poll_ns = val; trace_kvm_halt_poll_ns_grow(vcpu->vcpu_id, val, old);
}
shrink一次,值除以halt_poll_ns_shrink,當前系統該參數爲0,說明在設定的polling時長下虛擬機未被喚醒,那麼下一次polling時長降到基準值10us:
static void shrink_halt_poll_ns(struct kvm_vcpu *vcpu){
unsigned int old, val, shrink; old = val = vcpu->halt_poll_ns; shrink = READ_ONCE(halt_poll_ns_shrink); if (shrink == 0) val = 0; else val /= shrink; vcpu->halt_poll_ns = val; trace_kvm_halt_poll_ns_shrink(vcpu->vcpu_id, val, old);
}
值得注意幾點
該機制有可能致使物理CPU實際空閒的狀況下佔用率表現爲100%。由於若是guest上業務模型是隔一段時間被喚醒一次來處理不多量的流量,而且這個時間間隔比kvm halt_poll_ns短,那麼host將poll整個虛擬機的block時間,cpu佔用率也會衝上100%。
halt polling是電源能耗和業務時延的一個權衡。爲了減小進入guest的時延,idle cpu時間轉換爲host kernel時間。
該機制只有在CPU上沒有其餘running任務的狀況得以應用,否則polling動做被立馬終止,喚醒調度器,調度其餘進程。
延伸閱讀
業界針對虛擬機idle這個課題有比較多的研究,由於它帶來了比較大的overhead。主要能夠歸結爲如下幾種:
idle=poll,即把虛擬機idle時一直polling,空轉,不退出。這樣不利於物理CPU超線程的發揮。
阿里提出guest裏面提供halt polling機制,即在VM退出前先等會兒,這樣能夠減小VM退出次數。 --- 優勢:性能較社區halt polling機制好;缺點:須要修改guest內核;狀態:社區還沒有接收 https://lwn.net/Articles/732236/
AWS及騰訊考慮guest HLT指令不退出。 --- 優勢:性能較阿里好;缺點:只適用於vcpu獨佔物理核場景;狀態:社區討論中,比較大可能被接受。https://patchwork.kernel.org/...
idle等於mwait及mwait不退出。 --- 須要高版本kvm及高版本guest內核支持。
總結
如何高效地處理虛擬機idle是提高虛擬化性能的研究點。針對不一樣的業務模型,採用不一樣的機制最大化業務性能。後續將在考拉及其餘業務上逐個驗證這些方案。
參考文檔
https://www.linux-kvm.org/ima...
http://events17.linuxfoundati...
http://events17.linuxfoundati...
https://www.kernel.org/doc/Do...
免費領取驗證碼、內容安全、短信發送、直播點播體驗包及雲服務器等套餐
更多網易技術、產品、運營經驗分享請訪問網易雲社區。
文章來源: 網易雲社區