[PATCH v2 3/4] KVM: selftests: Test steal time across vCPU pid changes on x86

Fuad Tabba fuad.tabba at linux.dev
Sun Sep 6 10:27:42 PDT 2026


Hi Dongli,

On Fri, 4 Sept 2026 at 18:56, Dongli Zhang <dongli.zhang at oracle.com> wrote:
>
> Add a selftest for the case where the same vCPU fd is run from a new host
> thread after steal time has already been enabled and updated.

Since this is based on steal_time.c anyway, would it make sense to
fold it in rather than add a separate file?

steal_time.c already has the per-arch scaffolding, so the pid-change
case could reuse the existing riscv and loongarch support (which you
said you couldn't add here for lack of hosts) instead of duplicating
the x86/arm64 setup. Or am I missing something?

Cheers,
/fuad



>
> Pin the vCPU thread and a busy-loop thread to CPU 0, force host-side
> run_delay to accumulate, and run the vCPU again to observe guest steal
> time. Then run the same vCPU fd from a newly created host thread and verify
> that the next steal time value observed on the new thread remains monotonic
> and sane relative to the value observed on the old thread.
>
> This indirectly validates that vcpu->last_steal is reset when the vCPU run
> PID changes.
>
> Assisted-by: Codex:GPT-5.5
> Signed-off-by: Dongli Zhang <dongli.zhang at oracle.com>
> ---
> As I have access to only x86 and arm64 KVM hosts, I created and validated
> the selftest on those two architectures only.
>
>  tools/testing/selftests/kvm/Makefile.kvm      |   1 +
>  .../selftests/kvm/steal_time_change_pid.c     | 162 ++++++++++++++++++
>  2 files changed, 163 insertions(+)
>  create mode 100644 tools/testing/selftests/kvm/steal_time_change_pid.c
>
> diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
> index 96bab7002d39..036c28849f84 100644
> --- a/tools/testing/selftests/kvm/Makefile.kvm
> +++ b/tools/testing/selftests/kvm/Makefile.kvm
> @@ -164,6 +164,7 @@ TEST_GEN_PROGS_x86 += irq_test
>  TEST_GEN_PROGS_x86 += mmu_stress_test
>  TEST_GEN_PROGS_x86 += rseq_test
>  TEST_GEN_PROGS_x86 += steal_time
> +TEST_GEN_PROGS_x86 += steal_time_change_pid
>  TEST_GEN_PROGS_x86 += system_counter_offset_test
>  TEST_GEN_PROGS_x86 += pre_fault_memory_test
>
> diff --git a/tools/testing/selftests/kvm/steal_time_change_pid.c b/tools/testing/selftests/kvm/steal_time_change_pid.c
> new file mode 100644
> index 000000000000..3c39594db398
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/steal_time_change_pid.c
> @@ -0,0 +1,162 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Verify that KVM resets steal-time accounting when a vCPU fd is run from
> + * a different host PID.
> + */
> +
> +#include <pthread.h>
> +#include <asm/kvm_para.h>
> +#include "kvm_util.h"
> +#include "processor.h"
> +
> +#define ST_GPA_BASE            (1 << 30)
> +#define ST_SANE_DELTA_NS       (1ULL << 63)
> +
> +static void *st_gva;
> +static u64 guest_stolen_time;
> +static u64 main_steal;
> +static u64 thread_steal;
> +
> +#if defined(__x86_64__)
> +
> +#define STEAL_TIME_SIZE        ((sizeof(struct kvm_steal_time) + 63) & ~63)
> +
> +static void guest_code(void)
> +{
> +       struct kvm_steal_time *st = st_gva;
> +
> +       WRITE_ONCE(guest_stolen_time, READ_ONCE(st->steal));
> +       GUEST_SYNC(0);
> +
> +       WRITE_ONCE(guest_stolen_time, READ_ONCE(st->steal));
> +       GUEST_SYNC(1);
> +
> +       WRITE_ONCE(guest_stolen_time, READ_ONCE(st->steal));
> +       GUEST_DONE();
> +}
> +
> +static bool steal_time_supported(struct kvm_vcpu *vcpu)
> +{
> +       return kvm_cpu_has(X86_FEATURE_KVM_STEAL_TIME);
> +}
> +
> +static void steal_time_enable(struct kvm_vcpu *vcpu)
> +{
> +       vcpu_set_msr(vcpu, MSR_KVM_STEAL_TIME,
> +                    (ulong)st_gva | KVM_MSR_ENABLED);
> +}
> +
> +#else
> +#error "steal_time_change_pid is not implemented on this architecture"
> +#endif
> +
> +static void run_vcpu(struct kvm_vcpu *vcpu)
> +{
> +       struct ucall uc;
> +
> +       vcpu_run(vcpu);
> +
> +       switch (get_ucall(vcpu, &uc)) {
> +       case UCALL_SYNC:
> +       case UCALL_DONE:
> +               break;
> +       case UCALL_ABORT:
> +               REPORT_GUEST_ASSERT(uc);
> +       default:
> +               TEST_ASSERT(false, "Unexpected exit: %s",
> +                           exit_reason_str(vcpu->run->exit_reason));
> +       }
> +}
> +
> +static void *do_steal_time(void *arg)
> +{
> +       struct timespec ts, stop;
> +
> +       clock_gettime(CLOCK_MONOTONIC, &ts);
> +       stop = timespec_add_ns(ts, MIN_RUN_DELAY_NS);
> +
> +       while (timespec_to_ns(timespec_sub(ts, stop)) < 0)
> +               clock_gettime(CLOCK_MONOTONIC, &ts);
> +
> +       return NULL;
> +}
> +
> +static void *vcpu_thread(void *arg)
> +{
> +       struct kvm_vcpu *vcpu = arg;
> +
> +       run_vcpu(vcpu);
> +       sync_global_from_guest(vcpu->vm, guest_stolen_time);
> +       thread_steal = guest_stolen_time;
> +
> +       return NULL;
> +}
> +
> +int main(void)
> +{
> +       struct kvm_vcpu *vcpu;
> +       struct kvm_vm *vm;
> +       pthread_attr_t attr;
> +       pthread_t thread;
> +       cpu_set_t cpuset;
> +       long run_delay;
> +       long run_delay_delta;
> +
> +       ksft_print_header();
> +       ksft_set_plan(1);
> +
> +       CPU_ZERO(&cpuset);
> +       CPU_SET(0, &cpuset);
> +       pthread_attr_init(&attr);
> +       pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset);
> +       pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
> +
> +       vm = vm_create_with_one_vcpu(&vcpu, guest_code);
> +       vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
> +                                   ST_GPA_BASE, 1, 1, 0);
> +       virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, 1);
> +
> +       st_gva = (void *)ST_GPA_BASE;
> +       sync_global_to_guest(vm, st_gva);
> +       memset(addr_gva2hva(vm, ST_GPA_BASE), 0, STEAL_TIME_SIZE);
> +
> +       TEST_REQUIRE(steal_time_supported(vcpu));
> +
> +       steal_time_enable(vcpu);
> +       run_vcpu(vcpu);
> +
> +       run_delay = get_run_delay();
> +       pthread_create(&thread, &attr, do_steal_time, NULL);
> +
> +       while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS)
> +               sched_yield();
> +
> +       pthread_join(thread, NULL);
> +       run_delay_delta = get_run_delay() - run_delay;
> +       TEST_ASSERT(run_delay_delta >= MIN_RUN_DELAY_NS,
> +                   "Expected run_delay >= %ld, got %ld",
> +                   MIN_RUN_DELAY_NS, run_delay_delta);
> +
> +       run_vcpu(vcpu);
> +       sync_global_from_guest(vm, guest_stolen_time);
> +       main_steal = guest_stolen_time;
> +
> +       TEST_ASSERT(main_steal >= MIN_RUN_DELAY_NS,
> +                   "Expected steal time >= %ld, got %"PRIu64,
> +                   MIN_RUN_DELAY_NS, main_steal);
> +
> +       pthread_create(&thread, NULL, vcpu_thread, vcpu);
> +       pthread_join(thread, NULL);
> +
> +       TEST_ASSERT(thread_steal >= main_steal &&
> +                   thread_steal - main_steal < ST_SANE_DELTA_NS,
> +                   "Expected sane steal after vCPU pid change: "
> +                   "old=%"PRIu64", new=%"PRIu64,
> +                   main_steal, thread_steal);
> +
> +       ksft_test_result_pass("steal time remains sane across vCPU pid change\n");
> +
> +       pthread_attr_destroy(&attr);
> +       kvm_vm_free(vm);
> +       ksft_finished();
> +}
> --
> 2.43.7
>



More information about the kvm-riscv mailing list