Steal Time - GiantVM/KVM-Annotation GitHub Wiki

Steal time

Steal time is the percentage of time a virtual CPU waits for a real CPU while the hypervisor is servicing another virtual processor.

Steal time是在虚拟化环境下,当前VM(vCPU)等待CPU的时间百分比,即VMM把同一个CPU分配给其他VM(vCPU)的时间,比如0.5表示当前VM只能拿到50%的CPU时间。1表示当前VM拿不到一点CPU时间。

steal time带来的直接后果是VM的指令执行速度降低,比如2GHz的CPU在0.4的steal time下vCPU的频率为1.2GHz。

steal time通过top的st进行暴露。意义在于让guest感知自己真正占用CPU的时间比例,并据此调整自己的行为,以免影响业务,如果st值比较高,则说明当前vCPU分到的的CPU比例太小,整个VMM任务比较繁重,有些高计算任务可以跟着自我限制。

在KVM中,steal time通过 kvm_steal_time 结构进行维护:

struct kvm_steal_time {
    __u64 steal;                // 当前vCPU没有运行的时间(ns),在vCPU空闲时不计算
    __u32 version;              // 版本号,奇数表示正在更新
    __u32 flags;                // 目前为0
    __u32 pad[12];
}

当VM初始化了该数据结构后,把地址写入 MSR_KVM_STEAL_TIME(0x4b564d03)中。VMM会定时更新该数据结构的内容。和kvmclock一样,采用半虚拟化,guest需要安装驱动。

The interval between updates of this structure is arbitrary and implementation-dependent

The hypervisor may update this structure at any time it sees fit until anything with bit0 == 0 is written to it

Guest Driver

setup_arch => kvm_guest_init => pv_time_ops.steal_clock = kvm_steal_clock 如果支持 KVM_FEATURE_STEAL_TIME,注册steal函数 => smp_ops.smp_prepare_boot_cpu = kvm_smp_prepare_boot_cpu => smp_prepare_boot_cpu => smp_ops.smp_prepare_boot_cpu(kvm_smp_prepare_boot_cpu) => kvm_guest_cpu_init => kvm_register_steal_time

kvm_steal_clock

不断尝试读per-cpu变量steal_time(类型为kvm_steal_time),直到version为偶数且不再变化为止。

kvm_register_steal_time

通过写MSR告知VMM自己strcut kvm_steal_time的地址。

steal_account_process_time

获取当前的steal time。会被account_process_tick / irqtime_account_process_tick等函数调用,即定时器更新时也更新steal time。

=> paravirt_steal_clock 读取steal_clock结构,获得当前steal time => steal -= this_rq()->prev_steal_time; 减去prev_steal_time,得到steal time的差值 => account_steal_time 将当前steal time的差值累加到kcpustat_this_cpu->cpustat[CPUTIME_STEAL] => this_rq()->prev_steal_time += cputime_to_nsecs(steal_cputime) 加上差值,更新prev_steal_time

注意,kcpustat_this_cpu 是top命令看到的cpu数据的来源,CPUTIME_STEAL项对应的就是st。

update_rq_clock

进程调度中rq计算task运行时间。steal time不会被计算到具体的调度队列的运行时间中。

=> update_rq_clock_task 开启CONFIG_PARAVIRT_TIME_ACCOUNTING后,在计算task运行时间会从delta中减去steal time

VMM

当VM设置了MSR后,触发VM exit,VMM执行以下流程

handle_wrmsr => kvm_set_msr => kvm_x86_ops->set_msr 即 vmx_x86_ops->set_msr (vmx_set_msr) => kvm_set_msr_common => (MSR_KVM_STEAL_TIME)

MSR_KVM_STEAL_TIME

初始化VM的steal_time区域,该区域被映射到vcpu->arch.st.stime

=> kvm_gfn_to_hva_cache_init(vcpu->kvm, &vcpu->arch.st.stime, data & KVM_STEAL_VALID_BITS, sizeof(struct kvm_steal_time)) => kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu) 产生 KVM_REQ_STEAL_UPDATE 请求,在enter guest时会处理

KVM_REQ_STEAL_UPDATE

设置MSR后,每次在 vcpu_enter_guest 时都会去执行 record_steal_time

record_steal_time计算vCPU的steal time,更新VM的 steal_time 区域,即更新vcpu->arch.st.stime

// run_delay是任务(qemu的vcpu线程)没有运行的时间
vcpu->arch.st.steal.steal += current->sched_info.run_delay - vcpu->arch.st.last_steal;  // 得到vCPU当前没有运行的时间
vcpu->arch.st.last_steal = current->sched_info.run_delay;

通过 kvm_write_guest_cached 将vcpu->arch.st.stime中的值更新到VM中对应地址的数据结构中。