kvm-poll-control 是 KVM 中的一种半虚拟化 (paravirtualization) 特性,简称为 PV Feature。该 feature 在 2019 年引入 linux, patchset 可参考:[patch 0/5] cpuidle haltpoll driver and governor (v6) - Marcelo Tosatti (kernel. org)
针对虚拟机 CPU 在 idle 与 runnable 之间反复切换导致 VM-exit, 从而产生的性能损失,它提供了一种机制,可以关闭 KVM halt poll, 启用自己引入的 guest halt poll。因为 guest halt poll 是从 guest 端对 HLT 的优化,因此该 feature 相当于是将 HLT 之前的 polling (轮询) 环节从 host 转移到了 guest,这在某些场景下可以减少一部分 HLT cased VM-exit。
接下来结合 linux 6.6-rc7 的代码, 从 HLT 到 poll-control 梳理该 feature 引入的背景和工作机制。
1. HLT 对虚拟化的影响
根据 Intel 软件开发手册中的描述,HLT 指令的作用是停止 CPU,收到唤醒信号再继续执行。
SDM vol 3, 2.8.5 Controlling the Processor
The HLT (halt processor) instruction stops the processor until an enabled interrupt (such as NMI or SMI, which are normally enabled), a debug exception, the BINIT# signal, the INIT# signal, or the RESET# signal is received.
从操作系统的角度,CPU 在空闲 (idle) 时会使用 HLT 指令使 CPU 停止运行,直到收到新的中断/异常等信号再继续运行。
对于虚拟机,idle 状态中停止的是虚拟 CPU (Virtual CPU, vCPU),其本质是一个在物理 CPU (Physical CPU, pCPU) 上运行的线程。为了让占据的 pCPU 被充分利用,HLT 一般会触发 VM-exit, 然后 vCPU 将占用的 pCPU 交还给调度器,调度其他任务在 pCPU 上执行,vCPU 变为 blocked。当需要 vCPU 继续运行时,调度器再重新调度回 vCPU,通过 VM-entry 让 vCPU 重新变为执行状态。
vCPU 从空闲状态到执行状态,主要涉及两方面的开销:
- VM-exit 和 VM-entry 导致的 root mode 和 non-root mode 之间的上下文切换;
- vCPU 退出到 host 之后,线程由 block 到 unblock 的开销。
因为状态切换带来的性能开销是很大的, 所谓 polling 就是在状态切换前轮询一段时间,如果有唤醒条件满足则取消切换。guest halt poll 和 kvm halt poll 分别是针对前面两种状态切换的轮询。kvm-poll-control 是控制 kvm halt poll 是否开启。
2. KVM halt poll
The KVM halt polling system — The Linux Kernel documentation
Under some circumstances, the overhead of context switch from idle --> running or running --> idle is high, especially the halt instruction. The host halt poll is that when the vcpu execute halt instruction and cause VM-exit, in the ‘kvm_vcpu_block’ function, it will poll for conditions before giving the cpu to scheduler.
KVM halt poll 是 vCPU 退出到 host,即将被调度器 sched_out
前,KVM 先轮询一段时间,如果这期间有对 vCPU 的唤醒要求,则让 vCPU 继续执行。这种策略可以在一些场合,避免在 host 中 vCPU 线程 idle 和 running 状态的切换。
此过程的源码实现如下。
2.1 HLT caused VM-exit
在 guest 中,当 vCPU 进入 idle 状态,执行 HLT 会触发 VM-exit。退出到 root mode 后,KVM 调用 __vmx_handle_exit
检查发生 VM-exit 的原因,然后调用 HLT 对应的处理函数 kvm_emulate_halt
。
vcpu_enter_guest(vcpu)
// 发生了 VM-exit
static_call(kvm_x86_handle_exit)
vmx_handle_exit
__vmx_handle_exit
=> {
// ...
else if (exit_reason.basic == EXIT_REASON_HLT)
return kvm_emulate_halt(vcpu);
// ...
return kvm_vmx_exit_handlers[exit_handler_index](vcpu);
}
在模拟 guest 端的 HLT 指令时,KVM 先会检查 x2apic 的 Local APIC 采用的是哪种模拟方式。
- 如果 LAPIC 在 KVM 中模拟,那就将修改 vCPU 的状态标记为 HLTED,即
vcpu->arch.mp_state = KVM_MP_STATE_HALTED
,并返回 1。 - 如果 LAPIC 在 userspace 中模拟,直接返回 0。后续在大循环
vcpu_run
中,KVM 根据返回值决定是否退出到 userspace VMM (如 QEMU) 中模拟 HLT 指令。
(目前 x86 的虚拟化主要采用 lapic in kernel 的方式,因为硬件上有很多对中断虚拟化的支持,性能更好)
// VM-exit 之后,将 mp_state 设为 HLT,即 KVM_MP_STATE_HALTED
kvm_emulate_halt
kvm_emulate_halt_noskip
// 当执行了 HLT 后,VM-exit
// 如果是 local APIC is in-kernel,
// 设置 vCPU 状态为 HALTED,
// 然后在 run loop (vcpu_run)中会检查到 non-runnable, 然后停止 vCPU
// 如果 local APIC is managed by userspace,
// 返回值设为 0,退出到 userspace,userspace VMM 会负责处理 wake events
__kvm_emulate_halt(vcpu, KVM_MP_STATE_HALTED, KVM_EXIT_HLT) => {
if (lapic_in_kernel(vcpu)) {
vcpu->arch.mp_state = state;
return 1;
} else {
vcpu->run->exit_reason = reason;
return 0;
}
}
kvm_emulate_halt
返回值最终会作为 vcpu_enter_guest
的输出。
- 对于返回值 0,会导致程序跳出大循环,进入 userspace VMM 的模拟阶段 (此处暂不考虑这种方式)。
- 对于 lapic in kernel, 即返回值 1, 循环不会退出。在下一轮循环时时,由于
vcpu->arch.mp_state = KVM_MP_STATE_HALTED
,会进入vcpu_block
,接着在kvm_vcpu_halt
中由模拟 HLT。
// run loop
vcpu_run
for (;;) {
// kvm_vcpu_running() 检查 vCPU是否处于可运行状态:
// vcpu->arch.mp_state == KVM_MP_STATE_RUNNABLE &&
// !vcpu->arch.apf.halted
if (kvm_vcpu_running(vcpu)) {
r = vcpu_enter_guest(vcpu);
} else {
r = vcpu_block(vcpu);
=> kvm_vcpu_halt
}
// local APIC 由 userspace 管理,跳出循环,进入 userspace VMM 处理
if (r <= 0)
break;
}
2.2 KVM 模拟 vCPU halt
函数 kvm_vcpu_halt
是 KVM 对 HLT 指令进行模拟的主函数。
首先会检查是否可以进行 KVM halt polling,轮询需要满足两个条件:
!kvm_arch_no_poll(vcpu)
: KVM 支持轮询。默认为 ture,feature kvm-poll-control 可改变该设置。见 4 小节。vcpu->halt_poll_ns
:轮询持续时间不为 0。
如果满足轮询条件,则在时长为 vcpu->halt_poll_ns
的时间内,通过函数 kvm_vcpu_check_block
反复检查是否有唤醒任务到达。
- 若不满足唤醒条件,则通过函数
kvm_vcpu_block
让出 CPU 资源,允许调度器调度其他任务执行。直到需要继续执行 vCPU 时,返回此处继续执行。并调整轮询的持续时间。 - 若满足唤醒条件,则直接进入调整轮询持续时间阶段。
void kvm_vcpu_halt(struct kvm_vcpu *vcpu)
{
// `kvm_arch_no_poll()`, true 表示不支持 kvm halt poll
bool halt_poll_allowed = !kvm_arch_no_poll(vcpu);
ktime_t start, cur, poll_end;
bool do_halt_poll;
// 执行 halt poll 需要 (1)KVM 支持 host halt poll; (2) poll 的时长不为0
do_halt_poll = halt_poll_allowed && vcpu->halt_poll_ns;
start = cur = poll_end = ktime_get();
// 如果可以进行 host halt poll
if (do_halt_poll) {
// 计算出 poll 的结束时间 stop
ktime_t stop = ktime_add_ns(start, vcpu->halt_poll_ns);
// 进行 poll, 如果:
// (1) stop设定的时间还没到,and,
// (2) 运行队列上没有其他任务是可运行的,and,
// (3) 没有其他唤醒源到达
// 则 vCPU 一直处于忙等待
do {
if (kvm_vcpu_check_block(vcpu) < 0)
goto out;
cpu_relax();
poll_end = cur = ktime_get();
} while (kvm_vcpu_can_poll(cur, stop));
}
// 如果直到 stop 仍然在 polling,则 block vCPU,程序会停止在此处。
// 结束 block 后回到此处继续运行。
waited = kvm_vcpu_block(vcpu);
// ...
out:
// 调整poll的持续时间 vcpu->halt_poll_ns
shrink_halt_poll_ns(vcpu);
grow_halt_poll_ns(vcpu);
// 跟踪点。若启用了 kvm halt poll,可以使用 perf 查看到log
trace_kvm_vcpu_wakeup(halt_ns, waited, vcpu_valid_wakeup(vcpu));
}
完成 HLT 模拟后, 在 vcpu_enter_guest
中发生 VM-entry, vCPU 继续执行。
3. Guest halt poll
Guest halt poll 是和 kvm-poll-control 一同引入的 feature, 可以将轮询阶段从 host 转移到 guest 中,避免一部分 HLT 导致的 vm-exit。
如其名字所示,guest halt poll 是一种在 guest 里完成的轮询机制。其基本原理是在 vCPU 进入 idle 状态时,HLT 指令执行之前,先轮询一段时间,检查是否有唤醒 vCPU 的条件满足。如果有,则退出 idle,同时不会有 HLT 和随后的 VM-exit。
为实现这一个功能,guest kernel 首先调用函数 haltpoll_init
添加一个 cpuidle driver:haltpoll_driver
,并初始化。当 vCPU 进入 idle 时,就会调用 driver 的处理逻辑。
3.1 haltpoll_driver 的定义
haltpoll_driver
有两个状态,该 driver 只会在 guest 中注册,调用时会依次进入 states[0]
和 states[1]
, 执行 .enter
对应的回调函数。haltpoll_driver
结构体中状态 1 的回调函数默认为 default_enter_idle
。
// haltpoll_driver, 默认设置了 states[1]
static struct cpuidle_driver haltpoll_driver = {
.name = "haltpoll",
.governor = "haltpoll",
.states = {
{ /* entry 0 is for polling */ },
{
.enter = default_enter_idle,
.exit_latency = 1,
.target_residency = 1,
.power_usage = -1,
.name = "haltpoll idle",
.desc = "default architecture idle",
},
},
.safe_state_index = 0,
.state_count = 2,
};
至于状态 0,它就是 guest 轮询环节,在初始化 cpuidle 的轮询状态时,即 cpuidle_poll_state_init
设置回调函数。
3.2 初始化 haltpoll
初始化 haltpoll 函数 haltpoll_init
会初始化和注册 halt poll driver。不过首先会检查两个条件:
kvm_para_available()
: 能够检查到 KVM 提供的半虚拟化参数,即当前必须为 guest。haltpoll_want()
: 配置了KVM_HINTS_REALTIME
或者 module parameter:force
// drivers/cpuidle/cpuidle-haltpoll.c
static int __init haltpoll_init(void)
{
struct cpuidle_driver *drv = &haltpoll_driver;
/* Do not load haltpoll if idle= is passed */
if (boot_option_idle_override != IDLE_NO_OVERRIDE)
return -ENODEV;
// 使用 haltpoll 需要满足:
// (1)必须为 guest
// (2) 配置了 KVM_HINTS_REALTIME 或者 module parameter: force
// 即,/sys/module/cpuidle_haltpoll/parameters/force
if (!kvm_para_available() || !haltpoll_want()) // [*]
return -ENODEV;
// ...
}
static bool haltpoll_want(void)
{
return kvm_para_has_hint(KVM_HINTS_REALTIME) || force;
}
满足了以上条件,即 guest 想要 halt 轮询, driver 注册流程才能继续进行。 接下来做的事情主要包括:
-
- 使用
cpuidle_poll_state_init
为haltpoll_driver.states[0]
设置轮询环节的回调函数poll_idle
。
- 使用
-
- 使用
cpuidle_register_driver
注册haltpoll_driver
,并设置 idle driver 管理器。
- 使用
-
- 设置状态 "cpuidle/haltpoll: online" 的处理函数:
arch_haltpoll_enable
/arch_haltpoll_disable
。kvm-poll-control 就发生在此阶段。
- 设置状态 "cpuidle/haltpoll: online" 的处理函数:
// drivers/cpuidle/cpuidle-haltpoll.c
static int __init haltpoll_init(void)
{
struct cpuidle_driver *drv = &haltpoll_driver;
// 检查 guest 是否想要 halt poll
// 1. state[0] 入口回调函数设置
cpuidle_poll_state_init(drv);
=> {
struct cpuidle_state *state = &drv->states[0];
snprintf(state->name, CPUIDLE_NAME_LEN, "POLL");
state->enter = poll_idle;
state->flags = CPUIDLE_FLAG_POLLING;
}
// 2.
ret = cpuidle_register_driver(drv);
=> {
ret = __cpuidle_register_driver(drv);
// ...
gov = cpuidle_find_governor(drv->governor);
cpuidle_switch_governor(gov)
}
// 3.
cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cpuidle/haltpoll:online",
haltpoll_cpu_online, haltpoll_cpu_offline);
=> arch_haltpoll_enable / arch_haltpoll_disable
}
3.3 haltpoll_driver.states[0]
Guest halt polling 发生在 haltpoll_driver.states[0]
的入口函数中, poll_idle()
。与 KVM halt poll 类似,同样是在一个循环中反复检查是否需要被重新调度为 running,当轮询时间结束或有唤醒触发,则结束循环。
// states[0]
static int __cpuidle poll_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
// `current_set_polling_and_test` 为 vCPU 线程置上 polling 标志 `TIF_POLLING_NRFLAG`
// 成功则返回 0
if (!current_set_polling_and_test()) {
// 检查是否有唤醒标志 TIF_NEED_RESCHED,若没有则继续循环
while (!need_resched()) {
=> return test_bit(TIF_NEED_RESCHED, (unsigned long *)(¤t_thread_info()->flags));
u64 limit;
// polling 上限
limit = cpuidle_poll_time(drv, dev);
cpu_relax();
if (loop_count++ < POLL_IDLE_RELAX_COUNT)
continue;
loop_count = 0;
if (local_clock_noinstr() - time_start > limit) {
dev->poll_time_limit = true;
break;
}
}
}
}
// 将当前 task 设置上 poll 标志 TIF_POLLING_NRFLAG
current_set_polling_and_test
__current_set_polling => {
arch_set_bit(TIF_POLLING_NRFLAG,
(unsigned long *)(¤t_thread_info()->flags));
}
3.4 haltpoll_driver.states[1]
根据状态 1 的代码调用路径,可知其就是 HLT 指令出现的环节。
// state[1] 执行逻辑
static int default_enter_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
if (current_clr_polling_and_test()) {
=> __current_clr_polling // 清除 TIF_POLLING_NRFLAG, 因为 polling 已经结束
local_irq_enable();
return index;
}
arch_cpu_idle();
return index;
}
arch_cpu_idle
static_call(x86_idle)() => default_idle
raw_safe_halt
native_safe_halt
asm volatile("sti; hlt": : :"memory");
3.5 CPU idle 完整流程
在 idle 中的 vCPU 处于一个大的 while 循环中,若检测到 TIF_NEED_RESCHED
标志已设置,即需要重新调度任务,则退出 idle 循环。
循环中的 vCPU 会依次进入 halt poll 的两个状态,依次执行 poll_idle
和 default_enter_idle
。
cpu_startup_entry
do_idle
=> {
while (!need_resched()) {
cpuidle_idle_call
=> {
// 由于 haltpoll_driver 有 states[0], states[1]
// 此处 call_cpuidle 会调用两次。
// 第二次调用完会打破 while 循环条件,并清除标志 `TIF_POLLING_NRFLAG`
next_state = cpuidle_select(drv, dev, &stop_tick);
cpuidle_curr_governor->select(drv, dev, stop_tick);
entered_state = call_cpuidle(drv, dev, next_state);
cpuidle_enter
cpuidle_enter_state
target_state->enter(dev, drv, index)
=> poll_idle / default_enter_idle // states[0], states[1]
cpuidle_reflect(dev, entered_state);
cpuidle_curr_governor->reflect(dev, index);
haltpoll_reflect
adjust_poll_limit
guest_halt_poll_ns
// 为 current_thread_info()->flags 设置 TIF_POLLING_NRFLAG
__current_set_polling();
}
}
__current_clr_polling();
}
两个状态的切换由 haltpoll_governor
管理。cpuidle 的当前管理者记录在 cpuidle_curr_governor
。
// haltpoll_governor
static struct cpuidle_governor haltpoll_governor = {
.name = "haltpoll",
.rating = 9,
.enable = haltpoll_enable_device, // 查看并配置 CPU 的状态
.select = haltpoll_select, // 选择下一个进入的 idle state
.reflect = haltpoll_reflect, // 更新和调整轮询持续的时间
=> adjust_poll_limit
=> guest_halt_poll_ns
};
// 为 cpuidle 的处理注册一个管理者
int cpuidle_register_governor(struct cpuidle_governor *gov)
{
// 先检查指定名字的管理者是否已经注册
if (cpuidle_find_governor(gov->name) == NULL) {
// 配置当前管理者时,先进行一些检查
// ...
cpuidle_switch_governor(gov);
=> {
cpuidle_curr_governor = gov;
cpuidle_install_idle_handler();
pr_info("cpuidle: using governor %s\n", gov->name);
}
}
// ...
}
3.6 向 CPU 发送 IPI
当其他 CPU 准备发送 IPI 到目标 CPU 前,会检查是否有必要发送 IPI。
- 如果目标 CPU 正在执行轮询,即 POLL flag
_TIF_POLLING_NRFLAG
是置上的,这时允许远程 CPU 不发送 IPI 唤醒目标 CPU,以避免处理 IPI 的相关开销。只需置上标志_TIF_NEED_RESCHED
, CPU 会退出轮询,idle task 不久将通过sched_ttwu_pending()
重新调度。 - 如果没有轮询标记,则使用 IPI。
static __always_inline void
send_call_function_single_ipi(int cpu)
{
if (call_function_single_prep_ipi(cpu)) { // [*]
=> set_nr_if_polling // [**]
trace_ipi_send_cpu(cpu, _RET_IP_,
generic_smp_call_function_single_interrupt);
arch_send_call_function_single_ipi(cpu);
}
}
// [*]
// 检查是否立即发送 IPI
// 返回值:
// false 表示目标 CPU 处于 polling,没必要对其发送 IPI
// true 表示可对目标 CPU 发送 IPI
bool call_function_single_prep_ipi(int cpu)
{
// set_nr_if_polling 返回 true, 不会立即发送 IPI
if (set_nr_if_polling(cpu_rq(cpu)->idle)) {
trace_sched_wake_idle_without_ipi(cpu);
return false;
}
// set_nr_if_polling 返回 false
return true;
}
#define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED)
#define _TIF_POLLING_NRFLAG (1 << TIF_POLLING_NRFLAG)
// [**]
// 如果 task polling, 设置 need resched 标志 `_TIF_NEED_RESCHED`
// 返回值:
// true 表示 idle task 承诺会通过 `sched_ttwu_pending` 重新调度。
// 返回时的 flag 为 `_TIF_NEED_RESCHED`
static bool set_nr_if_polling(struct task_struct *p)
{
struct thread_info *ti = task_thread_info(p);
typeof(ti->flags) val = READ_ONCE(ti->flags);
for (;;) {
if (!(val & _TIF_POLLING_NRFLAG))
return false;
if (val & _TIF_NEED_RESCHED)
return true;
if (try_cmpxchg(&ti->flags, &val, val | _TIF_NEED_RESCHED))
break;
}
return true;
}
4. KVM poll control
Guest 通过一个用户定义的 MSR 0x4b564d05
(MSR_KVM_POLL_CONTROL) 控制 host 端关闭/开启 KVM halt poll。当使用 guest halt poll 时,会自动禁用 host 端的 halt polling。
4.1 Guest 端读写虚拟寄存器
haltpoll 初始化函数 haltpoll_init
中设置了回调函数 arch_haltpoll_enable
/ arch_haltpoll_disable
, 其核心就是改变用户定义的寄存器的值为:
- 0 (禁用 kvm poll),或,
- 1 (启用 kvm poll)。
禁用/启用 host poll
void arch_haltpoll_disable(unsigned int cpu)
{
if (!kvm_para_has_feature(KVM_FEATURE_POLL_CONTROL))
return;
/* Disable guest halt poll enables host halt poll */
smp_call_function_single(cpu, kvm_enable_host_haltpoll, NULL, 1);
wrmsrl(MSR_KVM_POLL_CONTROL, 0);
}
void arch_haltpoll_enable(unsigned int cpu)
{
if (!kvm_para_has_feature(KVM_FEATURE_POLL_CONTROL)) {
pr_err_once("host does not support poll control\n");
pr_err_once("host upgrade recommended\n");
return;
}
/* Enable guest halt poll disables host halt poll */
smp_call_function_single(cpu, kvm_disable_host_haltpoll, NULL, 1);
wrmsrl(MSR_KVM_POLL_CONTROL, 1);
}
两个函数分别在 CPU 上线和下线时调用,所以一般只会在 OS 启动和关闭时配置该虚拟寄存器。
smpboot_thread_fn
cpuhp_thread_fun
cpuhp_invoke_callback
haltpoll_cpu_online
arch_haltpoll_enable
arch_haltpoll_enable
kvm_disable_host_haltpoll
wrmsrl(MSR_KVM_POLL_CONTROL, 0);
4.2 Host 端关闭 KVM halt polling
Host 端,KVM 使用 kvm_vcpu_arch->msr_kvm_poll_control
模拟 MSR_KVM_POLL_CONTROL
,geust 读写寄存器实际是读写该变量。
struct kvm_vcpu_arch {
u64 msr_kvm_poll_control; // 仅使用bit0, true表示启用 kvm poll; false 禁用 kvm poll
}
在 KVM halt poll 之前,通过调用 kvm_arch_no_poll
检查是否支持 kvm halt polling。如果不支持,则不会在 KVM 中 polling。如 2.2 所示 kvm_vcpu_halt
将直接退出。
bool kvm_arch_no_poll(struct kvm_vcpu *vcpu)
{
return (vcpu->arch.msr_kvm_poll_control & 1) == 0;
}
EXPORT_SYMBOL_GPL(kvm_arch_no_poll);
4.3 保留 guest BSP 的配置
patch: [PATCH 5.17 115/140] x86/kvm: Preserve BSP MSR_KVM_POLL_CONTROL across suspend/resume
当 guest 暂停/恢复后,
- 非 BSP 的一类 guest CPU 是可热插拔的,因此它能够被 haltpoll driver 保存/恢复
MSR_KVM_POLL_CONTROL
的值,因此 poll control 的也能正常配置。 - 但是对于 guest BSP 属于不可热插拔的,guest 恢复执行后,polling 的配置仍然是 kvm halt poll,从而导致 guest 每次进入 idle 都会有额外的 VM-exit 开销。
因此需要在
kvm_suspend
/kvm_resume
中保存/恢复MSR_KVM_POLL_CONTROL
的设置。
kvm_guest_init
// 把需要的系统核心操作 (system core operation) 注册到链表上 syscore_ops_list
register_syscore_ops(&kvm_syscore_ops);
static struct syscore_ops kvm_syscore_ops = {
.suspend = kvm_suspend,
.resume = kvm_resume,
};
static int kvm_suspend(void)
{
kvm_guest_cpu_offline(false);
#ifdef CONFIG_ARCH_CPUIDLE_HALTPOLL
if (kvm_para_has_feature(KVM_FEATURE_POLL_CONTROL))
rdmsrl(MSR_KVM_POLL_CONTROL, val);
has_guest_poll = !(val & 1);
#endif
}
static void kvm_resume(void)
{
kvm_cpu_online(raw_smp_processor_id());
#ifdef CONFIG_ARCH_CPUIDLE_HALTPOLL
if (kvm_para_has_feature(KVM_FEATURE_POLL_CONTROL) && has_guest_poll)
wrmsrl(MSR_KVM_POLL_CONTROL, 0);
#endif
}
5. How to use
使用 guest halt polling 需要:
- enable PV feature
KVM_FEATURE_POLL_CONTROL
。同时需要禁用KVM_FEATURE_PV_UNHALT
,因为两者冲突(会自动 disable)。 - enable PV feature hint
KVM_HINTS_REALTIME
,表示 guest vCPU 永远不会被抢占。KVM_HINTS_REALTIME
: What does KVM_HINTS_REALTIME do? — Linux KVM
在 qemu command line 添加: +kvm-poll-control,+kvm-hint-dedicated
BTW, PV feautres, tlb-flush
and sched-yield
can be enabled only when KVM_HINTS_REALTIME
disabled.
测试 1:使用 perf 检查关键函数是否被调用,可采集以下跟踪点。
# 用于 host trace,跟踪 kvm halt poll
kvm:kvm_vcpu_wakeup
kvm:kvm_halt_poll_ns
# 用于 guest trace,跟踪 guest halt poll
power:guest_halt_poll_ns
测试 2: dmesg in guest
kvm-poll-control 启用的正确的 log:
[ 0.449065] cpuidle: using governor haltpoll
错误的 log:
[ 0.883940] cpuidle: using governor haltpoll
[ 0.885186] kvm-guest: host does not support poll control
[ 0.886612] kvm-guest: host upgrade recommended
Related
本文作者:文七安
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!