Re: [PATCH v2 11/14] KVM: selftests: Add support for vCPU pinning

Sean Christopherson
2026-04-01T20:55:49Z

On Tue, Mar 31, 2026, Josh Hilke wrote:
> From: David Matlack <dmatlack@google.com>
>
> Add the '-p' flag to vfio_pci_irq_test to pin vCPU threads to random
> physical CPUs throughout the test. This stresses the interaction between
> vCPU migration and interrupt delivery, ensuring that interrupts find
> their target even as the vCPU moves across the host's pCPUs.

This isn't "pinning" in the normal sense of the word in KVM-land. Pinning in
KVM usually means affining a vCPU to _one_ pCPU and never changing that
affinity. What you're doing here is *very* different; you're delibrately
mucking with affinity to force pCPU migrations. That isn't bad by any means,
but calling it "pinning" is super confusing, and I would argue the use of '-p'
is flat out wrong. I would suggest '-m' for migrate if it's available.

> Signed-off-by: David Matlack <dmatlack@google.com>
> Signed-off-by: Josh Hilke <jrhilke@google.com>
> Co-developed-by: Josh Hilke <jrhilke@google.com>
> ---
> .../selftests/kvm/include/kvm_syscalls.h | 7 ++
> .../testing/selftests/kvm/vfio_pci_irq_test.c | 79 ++++++++++++++++++-
> 2 files changed, 83 insertions(+), 3 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/include/kvm_syscalls.h b/tools/testing/selftests/kvm/include/kvm_syscalls.h
> index d4e613162bba..5f839f735640 100644
> --- a/tools/testing/selftests/kvm/include/kvm_syscalls.h
> +++ b/tools/testing/selftests/kvm/include/kvm_syscalls.h
> @@ -73,9 +73,16 @@ static inline int kvm_dup(int fd)
> return new_fd;
> }
>
> +static inline int gettid(void)
> +{
> + return syscall(__NR_gettid);
> +}
> +
> __KVM_SYSCALL_DEFINE(munmap, 2, void *, mem, size_t, size);
> __KVM_SYSCALL_DEFINE(close, 1, int, fd);
> __KVM_SYSCALL_DEFINE(fallocate, 4, int, fd, int, mode, loff_t, offset, loff_t, len);
> __KVM_SYSCALL_DEFINE(ftruncate, 2, unsigned int, fd, off_t, length);
> +__KVM_SYSCALL_DEFINE(sched_setaffinity, 3, pid_t, pid, size_t, cpusetsize, const cpu_set_t *, mask);
> +__KVM_SYSCALL_DEFINE(sched_getaffinity, 3, pid_t, pid, size_t, cpusetsize, cpu_set_t *, mask);

Please add these separately and use then throughout, e.g. in rseq_test.c (which
doesn't very similar migration shenanigans).

> #endif /* SELFTEST_KVM_SYSCALLS_H */
> diff --git a/tools/testing/selftests/kvm/vfio_pci_irq_test.c b/tools/testing/selftests/kvm/vfio_pci_irq_test.c
> index fa77ce348251..c2f48c6710dd 100644
> --- a/tools/testing/selftests/kvm/vfio_pci_irq_test.c
> +++ b/tools/testing/selftests/kvm/vfio_pci_irq_test.c
> @@ -22,6 +22,8 @@ static bool guest_ready_for_irqs[KVM_MAX_VCPUS];
> static bool guest_received_irq[KVM_MAX_VCPUS];
> static bool guest_received_nmi[KVM_MAX_VCPUS];
>
> +static pid_t vcpu_tids[KVM_MAX_VCPUS];
> +
> #define TIMEOUT_NS (2ULL * 1000 * 1000 * 1000)
>
> static u32 guest_get_vcpu_id(void)
> @@ -64,12 +66,64 @@ static void *vcpu_thread_main(void *arg)
> struct kvm_vcpu *vcpu = arg;
> struct ucall uc;
>
> + WRITE_ONCE(vcpu_tids[vcpu->id], syscall(__NR_gettid));

Heh, what's the point of adding gettid() if you don't use it?

> +
> vcpu_run(vcpu);
> TEST_ASSERT_EQ(UCALL_DONE, get_ucall(vcpu, &uc));
>
> return NULL;
> }
>
> +static int get_cpu(struct kvm_vcpu *vcpu)
> +{
> + pid_t tid = vcpu_tids[vcpu->id];
> + cpu_set_t cpus;
> + int cpu = -1;
> + int i;
> +
> + kvm_sched_getaffinity(tid, sizeof(cpus), &cpus);
> +
> + for (i = 0; i < get_nprocs(); i++) {
> + if (!CPU_ISSET(i, &cpus))
> + continue;
> +
> + if (cpu != -1) {
> + cpu = i;
> + } else {
> + /* vCPU is pinned to multiple CPUs */
> + return -1;

Given that this is for debugging failures, printing -1 isn't as helpful as it
could be. Ideally the test would print something similar to the output of
`taskset -c -p <pid>`.

> + }
> + }
> +
> + return cpu;
> +}
> +
> +static void pin_vcpu_threads(int nr_vcpus, int start_cpu, cpu_set_t *available_cpus)
> +{
> + const size_t size = sizeof(cpu_set_t);
> + int nr_cpus, cpu, vcpu_index = 0;
> + cpu_set_t target_cpu;
> +
> + nr_cpus = get_nprocs();
> + CPU_ZERO(&target_cpu);
> +
> + for (cpu = start_cpu;; cpu = (cpu + 1) % nr_cpus) {

Why round-robin the pCPUs? If anything, that _reduces_ coverage because it
creates a single pattern for how vCPUs are affined. And vCPUs will never be
affined to the same pCPU.

Why not fully randomize the affinity? Again, this isn't pinning, it's forced
task migration.

> + if (vcpu_index == nr_vcpus)
> + break;
> +
> + if (!CPU_ISSET(cpu, available_cpus))
> + continue;
> +
> + CPU_SET(cpu, &target_cpu);
> +
> + kvm_sched_setaffinity(vcpu_tids[vcpu_index], size, &target_cpu);

If you're affining to _one_ pCPU, just use pin_task_to_cpu(). Actually, all
of this belongs in a helper, e.g. pin_task_to_random_cpu().

> + CPU_CLR(cpu, &target_cpu);
> +
> + vcpu_index++;
> + }
> +}