Skip to content

Commit 3e7f431

Browse files
committed
KVM: Protect vCPU's "last run PID" with rwlock, not RCU
To avoid jitter on KVM_RUN due to synchronize_rcu(), use a rwlock instead of RCU to protect vcpu->pid, a.k.a. the pid of the task last used to a vCPU. When userspace is doing M:N scheduling of tasks to vCPUs, e.g. to run SEV migration helper vCPUs during post-copy, the synchronize_rcu() needed to change the PID associated with the vCPU can stall for hundreds of milliseconds, which is problematic for latency sensitive post-copy operations. In the directed yield path, do not acquire the lock if it's contended, i.e. if the associated PID is changing, as that means the vCPU's task is already running. Reported-by: Steve Rutherford <srutherford@google.com> Reviewed-by: Steve Rutherford <srutherford@google.com> Acked-by: Oliver Upton <oliver.upton@linux.dev> Link: https://lore.kernel.org/r/20240802200136.329973-3-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
1 parent 6cf9ef2 commit 3e7f431

File tree

3 files changed

+28
-16
lines changed

3 files changed

+28
-16
lines changed

arch/arm64/include/asm/kvm_host.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,7 @@ int __kvm_arm_vcpu_set_events(struct kvm_vcpu *vcpu,
11401140
void kvm_arm_halt_guest(struct kvm *kvm);
11411141
void kvm_arm_resume_guest(struct kvm *kvm);
11421142

1143-
#define vcpu_has_run_once(vcpu) !!rcu_access_pointer((vcpu)->pid)
1143+
#define vcpu_has_run_once(vcpu) (!!READ_ONCE((vcpu)->pid))
11441144

11451145
#ifndef __KVM_NVHE_HYPERVISOR__
11461146
#define kvm_call_hyp_nvhe(f, ...) \

include/linux/kvm_host.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ struct kvm_vcpu {
334334
#ifndef __KVM_HAVE_ARCH_WQP
335335
struct rcuwait wait;
336336
#endif
337-
struct pid __rcu *pid;
337+
struct pid *pid;
338+
rwlock_t pid_lock;
338339
int sigset_active;
339340
sigset_t sigset;
340341
unsigned int halt_poll_ns;

virt/kvm/kvm_main.c

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ static void kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id)
447447
vcpu->kvm = kvm;
448448
vcpu->vcpu_id = id;
449449
vcpu->pid = NULL;
450+
rwlock_init(&vcpu->pid_lock);
450451
#ifndef __KVM_HAVE_ARCH_WQP
451452
rcuwait_init(&vcpu->wait);
452453
#endif
@@ -474,7 +475,7 @@ static void kvm_vcpu_destroy(struct kvm_vcpu *vcpu)
474475
* the vcpu->pid pointer, and at destruction time all file descriptors
475476
* are already gone.
476477
*/
477-
put_pid(rcu_dereference_protected(vcpu->pid, 1));
478+
put_pid(vcpu->pid);
478479

479480
free_page((unsigned long)vcpu->run);
480481
kmem_cache_free(kvm_vcpu_cache, vcpu);
@@ -3770,15 +3771,17 @@ EXPORT_SYMBOL_GPL(kvm_vcpu_kick);
37703771

37713772
int kvm_vcpu_yield_to(struct kvm_vcpu *target)
37723773
{
3773-
struct pid *pid;
37743774
struct task_struct *task = NULL;
37753775
int ret;
37763776

3777-
rcu_read_lock();
3778-
pid = rcu_dereference(target->pid);
3779-
if (pid)
3780-
task = get_pid_task(pid, PIDTYPE_PID);
3781-
rcu_read_unlock();
3777+
if (!read_trylock(&target->pid_lock))
3778+
return 0;
3779+
3780+
if (target->pid)
3781+
task = get_pid_task(target->pid, PIDTYPE_PID);
3782+
3783+
read_unlock(&target->pid_lock);
3784+
37823785
if (!task)
37833786
return 0;
37843787
ret = yield_to(task, 1);
@@ -4030,9 +4033,9 @@ static int vcpu_get_pid(void *data, u64 *val)
40304033
{
40314034
struct kvm_vcpu *vcpu = data;
40324035

4033-
rcu_read_lock();
4034-
*val = pid_nr(rcu_dereference(vcpu->pid));
4035-
rcu_read_unlock();
4036+
read_lock(&vcpu->pid_lock);
4037+
*val = pid_nr(vcpu->pid);
4038+
read_unlock(&vcpu->pid_lock);
40364039
return 0;
40374040
}
40384041

@@ -4318,7 +4321,14 @@ static long kvm_vcpu_ioctl(struct file *filp,
43184321
r = -EINVAL;
43194322
if (arg)
43204323
goto out;
4321-
oldpid = rcu_access_pointer(vcpu->pid);
4324+
4325+
/*
4326+
* Note, vcpu->pid is primarily protected by vcpu->mutex. The
4327+
* dedicated r/w lock allows other tasks, e.g. other vCPUs, to
4328+
* read vcpu->pid while this vCPU is in KVM_RUN, e.g. to yield
4329+
* directly to this vCPU
4330+
*/
4331+
oldpid = vcpu->pid;
43224332
if (unlikely(oldpid != task_pid(current))) {
43234333
/* The thread running this VCPU changed. */
43244334
struct pid *newpid;
@@ -4328,9 +4338,10 @@ static long kvm_vcpu_ioctl(struct file *filp,
43284338
break;
43294339

43304340
newpid = get_task_pid(current, PIDTYPE_PID);
4331-
rcu_assign_pointer(vcpu->pid, newpid);
4332-
if (oldpid)
4333-
synchronize_rcu();
4341+
write_lock(&vcpu->pid_lock);
4342+
vcpu->pid = newpid;
4343+
write_unlock(&vcpu->pid_lock);
4344+
43344345
put_pid(oldpid);
43354346
}
43364347
vcpu->wants_to_run = !READ_ONCE(vcpu->run->immediate_exit__unsafe);

0 commit comments

Comments
 (0)