Idea: When sending a call-function IPI-many to vCPUs, yield(by hypercall) if any of the IPI target vCPU was preempted, yield to(让步于) the first preempted target vCPU which we found.
如果IPI的source vCPU不yield to the preempted target vCPU的话,source vCPU在Non-root mode下依然会busy waiting(参考smp_call_function_many_cond函数),直到preempted target vCPU被调度到Non-root mode后才结束;还不如直接yiled source vCPU,yield to the preempted target vCPU。
When the guest send call func IPI, the current vcpu will call native_send_call_func_ipi to send IPI to the target vcpu. If the target vCPU is preempted, it will issue a hypercall KVM_HC_SCHED_YIELD.
We just select the first preempted target vCPU which we found since the state of target vCPUs can change underneath and to avoid race conditions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
staticvoidkvm_smp_send_call_func_ipi(conststruct cpumask *mask) { int cpu;
native_send_call_func_ipi(mask);
/* Make sure other vCPUs get a chance to run if they need to. */ for_each_cpu(cpu, mask) { if (!idle_cpu(cpu) && vcpu_is_preempted(cpu)) { kvm_hypercall1(KVM_HC_SCHED_YIELD, per_cpu(x86_cpu_to_apicid, cpu)); break; } } }
kvm side
kvm needs to implement the hypercall handler to process the yield hypercall.
1 2 3 4 5 6 7 8 9 10 11 12
intkvm_emulate_hypercall(struct kvm_vcpu *vcpu) { ... case KVM_HC_SCHED_YIELD: if (!guest_pv_has(vcpu, KVM_FEATURE_PV_SCHED_YIELD)) break;
if (!target || !READ_ONCE(target->ready)) goto no_yield;
/* Ignore requests to yield to self */ if (vcpu == target) goto no_yield;
if (kvm_vcpu_yield_to(target) <= 0) goto no_yield;
vcpu->stat.directed_yield_successful++;
no_yield: return; }
上述代码的第26行kvm_vcpu_yield_to(target),target就是目标vCPU,当前代码的执行上下文是IPI的source vCPU thread,执行完kvm_vcpu_yield_to后,即可yield to 目标vCPU。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
intkvm_vcpu_yield_to(struct kvm_vcpu *target) { structpid *pid; structtask_struct *task =NULL; int ret = 0;
rcu_read_lock(); pid = rcu_dereference(target->pid); if (pid) task = get_pid_task(pid, PIDTYPE_PID); rcu_read_unlock(); if (!task) return ret; ret = yield_to(task, 1); put_task_struct(task);
return ret; }
再看下yield_to函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
/** * Return: * true (>0) if we indeed boosted the target task. * false (0) if we failed to boost the target. * -ESRCH if there's no task to yield to. */ int __sched yield_to(struct task_struct *p, bool preempt) { ... // yield_to_task主动放弃CPU并执行指定的task_struct yielded = curr->sched_class->yield_to_task(rq, p, preempt); ... }