Skip to content

Commit f73a344

Browse files
Ben Gardonbonzini
authored andcommitted
KVM: selftests: Add memslot modification stress test
Add a memslot modification stress test in which a memslot is repeatedly created and removed while vCPUs access memory in another memslot. Most userspaces do not create or remove memslots on running VMs which makes it hard to test races in adding and removing memslots without a dedicated test. Adding and removing a memslot also has the effect of tearing down the entire paging structure, which leads to more page faults and pressure on the page fault handling path than a one-and-done memory population test. Reviewed-by: Jacob Xu <[email protected]> Signed-off-by: Ben Gardon <[email protected]> Message-Id: <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
1 parent 82f9133 commit f73a344

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed

tools/testing/selftests/kvm/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@
3030
/dirty_log_test
3131
/dirty_log_perf_test
3232
/kvm_create_max_vcpus
33+
/memslot_modification_stress_test
3334
/set_memory_region_test
3435
/steal_time

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ TEST_GEN_PROGS_x86_64 += demand_paging_test
6464
TEST_GEN_PROGS_x86_64 += dirty_log_test
6565
TEST_GEN_PROGS_x86_64 += dirty_log_perf_test
6666
TEST_GEN_PROGS_x86_64 += kvm_create_max_vcpus
67+
TEST_GEN_PROGS_x86_64 += memslot_modification_stress_test
6768
TEST_GEN_PROGS_x86_64 += set_memory_region_test
6869
TEST_GEN_PROGS_x86_64 += steal_time
6970

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* KVM memslot modification stress test
4+
* Adapted from demand_paging_test.c
5+
*
6+
* Copyright (C) 2018, Red Hat, Inc.
7+
* Copyright (C) 2020, Google, Inc.
8+
*/
9+
10+
#define _GNU_SOURCE /* for program_invocation_name */
11+
12+
#include <stdio.h>
13+
#include <stdlib.h>
14+
#include <sys/syscall.h>
15+
#include <unistd.h>
16+
#include <asm/unistd.h>
17+
#include <time.h>
18+
#include <poll.h>
19+
#include <pthread.h>
20+
#include <linux/bitmap.h>
21+
#include <linux/bitops.h>
22+
#include <linux/userfaultfd.h>
23+
24+
#include "perf_test_util.h"
25+
#include "processor.h"
26+
#include "test_util.h"
27+
#include "guest_modes.h"
28+
29+
#define DUMMY_MEMSLOT_INDEX 7
30+
31+
#define DEFAULT_MEMSLOT_MODIFICATION_ITERATIONS 10
32+
33+
34+
static int nr_vcpus = 1;
35+
static uint64_t guest_percpu_mem_size = DEFAULT_PER_VCPU_MEM_SIZE;
36+
37+
static bool run_vcpus = true;
38+
39+
static void *vcpu_worker(void *data)
40+
{
41+
int ret;
42+
struct perf_test_vcpu_args *vcpu_args =
43+
(struct perf_test_vcpu_args *)data;
44+
int vcpu_id = vcpu_args->vcpu_id;
45+
struct kvm_vm *vm = perf_test_args.vm;
46+
struct kvm_run *run;
47+
48+
vcpu_args_set(vm, vcpu_id, 1, vcpu_id);
49+
run = vcpu_state(vm, vcpu_id);
50+
51+
/* Let the guest access its memory until a stop signal is received */
52+
while (READ_ONCE(run_vcpus)) {
53+
ret = _vcpu_run(vm, vcpu_id);
54+
TEST_ASSERT(ret == 0, "vcpu_run failed: %d\n", ret);
55+
56+
if (get_ucall(vm, vcpu_id, NULL) == UCALL_SYNC)
57+
continue;
58+
59+
TEST_ASSERT(false,
60+
"Invalid guest sync status: exit_reason=%s\n",
61+
exit_reason_str(run->exit_reason));
62+
}
63+
64+
return NULL;
65+
}
66+
67+
struct memslot_antagonist_args {
68+
struct kvm_vm *vm;
69+
useconds_t delay;
70+
uint64_t nr_modifications;
71+
};
72+
73+
static void add_remove_memslot(struct kvm_vm *vm, useconds_t delay,
74+
uint64_t nr_modifications, uint64_t gpa)
75+
{
76+
int i;
77+
78+
for (i = 0; i < nr_modifications; i++) {
79+
usleep(delay);
80+
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa,
81+
DUMMY_MEMSLOT_INDEX, 1, 0);
82+
83+
vm_mem_region_delete(vm, DUMMY_MEMSLOT_INDEX);
84+
}
85+
}
86+
87+
struct test_params {
88+
useconds_t memslot_modification_delay;
89+
uint64_t nr_memslot_modifications;
90+
bool partition_vcpu_memory_access;
91+
};
92+
93+
static void run_test(enum vm_guest_mode mode, void *arg)
94+
{
95+
struct test_params *p = arg;
96+
pthread_t *vcpu_threads;
97+
struct kvm_vm *vm;
98+
int vcpu_id;
99+
100+
vm = perf_test_create_vm(mode, nr_vcpus, guest_percpu_mem_size);
101+
102+
perf_test_args.wr_fract = 1;
103+
104+
vcpu_threads = malloc(nr_vcpus * sizeof(*vcpu_threads));
105+
TEST_ASSERT(vcpu_threads, "Memory allocation failed");
106+
107+
perf_test_setup_vcpus(vm, nr_vcpus, guest_percpu_mem_size,
108+
p->partition_vcpu_memory_access);
109+
110+
/* Export the shared variables to the guest */
111+
sync_global_to_guest(vm, perf_test_args);
112+
113+
pr_info("Finished creating vCPUs\n");
114+
115+
for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++)
116+
pthread_create(&vcpu_threads[vcpu_id], NULL, vcpu_worker,
117+
&perf_test_args.vcpu_args[vcpu_id]);
118+
119+
pr_info("Started all vCPUs\n");
120+
121+
add_remove_memslot(vm, p->memslot_modification_delay,
122+
p->nr_memslot_modifications,
123+
guest_test_phys_mem +
124+
(guest_percpu_mem_size * nr_vcpus) +
125+
perf_test_args.host_page_size +
126+
perf_test_args.guest_page_size);
127+
128+
run_vcpus = false;
129+
130+
/* Wait for the vcpu threads to quit */
131+
for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++)
132+
pthread_join(vcpu_threads[vcpu_id], NULL);
133+
134+
pr_info("All vCPU threads joined\n");
135+
136+
ucall_uninit(vm);
137+
kvm_vm_free(vm);
138+
139+
free(vcpu_threads);
140+
}
141+
142+
static void help(char *name)
143+
{
144+
puts("");
145+
printf("usage: %s [-h] [-m mode] [-d delay_usec]\n"
146+
" [-b memory] [-v vcpus] [-o] [-i iterations]\n", name);
147+
guest_modes_help();
148+
printf(" -d: add a delay between each iteration of adding and\n"
149+
" deleting a memslot in usec.\n");
150+
printf(" -b: specify the size of the memory region which should be\n"
151+
" accessed by each vCPU. e.g. 10M or 3G.\n"
152+
" Default: 1G\n");
153+
printf(" -v: specify the number of vCPUs to run.\n");
154+
printf(" -o: Overlap guest memory accesses instead of partitioning\n"
155+
" them into a separate region of memory for each vCPU.\n");
156+
printf(" -i: specify the number of iterations of adding and removing\n"
157+
" a memslot.\n"
158+
" Default: %d\n", DEFAULT_MEMSLOT_MODIFICATION_ITERATIONS);
159+
puts("");
160+
exit(0);
161+
}
162+
163+
int main(int argc, char *argv[])
164+
{
165+
int max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
166+
int opt;
167+
struct test_params p = {
168+
.memslot_modification_delay = 0,
169+
.nr_memslot_modifications =
170+
DEFAULT_MEMSLOT_MODIFICATION_ITERATIONS,
171+
.partition_vcpu_memory_access = true
172+
};
173+
174+
guest_modes_append_default();
175+
176+
while ((opt = getopt(argc, argv, "hm:d:b:v:oi:")) != -1) {
177+
switch (opt) {
178+
case 'm':
179+
guest_modes_cmdline(optarg);
180+
break;
181+
case 'd':
182+
p.memslot_modification_delay = strtoul(optarg, NULL, 0);
183+
TEST_ASSERT(p.memslot_modification_delay >= 0,
184+
"A negative delay is not supported.");
185+
break;
186+
case 'b':
187+
guest_percpu_mem_size = parse_size(optarg);
188+
break;
189+
case 'v':
190+
nr_vcpus = atoi(optarg);
191+
TEST_ASSERT(nr_vcpus > 0 && nr_vcpus <= max_vcpus,
192+
"Invalid number of vcpus, must be between 1 and %d",
193+
max_vcpus);
194+
break;
195+
case 'o':
196+
p.partition_vcpu_memory_access = false;
197+
break;
198+
case 'i':
199+
p.nr_memslot_modifications = atoi(optarg);
200+
break;
201+
case 'h':
202+
default:
203+
help(argv[0]);
204+
break;
205+
}
206+
}
207+
208+
for_each_guest_mode(run_test, &p);
209+
210+
return 0;
211+
}

0 commit comments

Comments
 (0)