Skip to content

Commit a4b9722

Browse files
CmdrMoozybonzini
authored andcommitted
KVM: selftests: allow using UFFD minor faults for demand paging
UFFD handling of MINOR faults is a new feature whose use case is to speed up demand paging (compared to MISSING faults). So, it's interesting to let this selftest exercise this new mode. Modify the demand paging test to have the option of using UFFD minor faults, as opposed to missing faults. Now, when turning on userfaultfd with '-u', the desired mode has to be specified ("MISSING" or "MINOR"). If we're in minor mode, before registering, prefault via the *alias*. This way, the guest will trigger minor faults, instead of missing faults, and we can UFFDIO_CONTINUE to resolve them. Modify the page fault handler function to use the right ioctl depending on the mode we're running in. In MINOR mode, use UFFDIO_CONTINUE. Signed-off-by: Axel Rasmussen <[email protected]> Message-Id: <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
1 parent 94f3f2b commit a4b9722

File tree

1 file changed

+79
-33
lines changed

1 file changed

+79
-33
lines changed

tools/testing/selftests/kvm/demand_paging_test.c

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -73,33 +73,48 @@ static void *vcpu_worker(void *data)
7373
return NULL;
7474
}
7575

76-
static int handle_uffd_page_request(int uffd, uint64_t addr)
76+
static int handle_uffd_page_request(int uffd_mode, int uffd, uint64_t addr)
7777
{
78-
pid_t tid;
78+
pid_t tid = syscall(__NR_gettid);
7979
struct timespec start;
8080
struct timespec ts_diff;
81-
struct uffdio_copy copy;
8281
int r;
8382

84-
tid = syscall(__NR_gettid);
83+
clock_gettime(CLOCK_MONOTONIC, &start);
8584

86-
copy.src = (uint64_t)guest_data_prototype;
87-
copy.dst = addr;
88-
copy.len = demand_paging_size;
89-
copy.mode = 0;
85+
if (uffd_mode == UFFDIO_REGISTER_MODE_MISSING) {
86+
struct uffdio_copy copy;
9087

91-
clock_gettime(CLOCK_MONOTONIC, &start);
88+
copy.src = (uint64_t)guest_data_prototype;
89+
copy.dst = addr;
90+
copy.len = demand_paging_size;
91+
copy.mode = 0;
92+
93+
r = ioctl(uffd, UFFDIO_COPY, &copy);
94+
if (r == -1) {
95+
pr_info("Failed UFFDIO_COPY in 0x%lx from thread %d with errno: %d\n",
96+
addr, tid, errno);
97+
return r;
98+
}
99+
} else if (uffd_mode == UFFDIO_REGISTER_MODE_MINOR) {
100+
struct uffdio_continue cont = {0};
101+
102+
cont.range.start = addr;
103+
cont.range.len = demand_paging_size;
92104

93-
r = ioctl(uffd, UFFDIO_COPY, &copy);
94-
if (r == -1) {
95-
pr_info("Failed Paged in 0x%lx from thread %d with errno: %d\n",
96-
addr, tid, errno);
97-
return r;
105+
r = ioctl(uffd, UFFDIO_CONTINUE, &cont);
106+
if (r == -1) {
107+
pr_info("Failed UFFDIO_CONTINUE in 0x%lx from thread %d with errno: %d\n",
108+
addr, tid, errno);
109+
return r;
110+
}
111+
} else {
112+
TEST_FAIL("Invalid uffd mode %d", uffd_mode);
98113
}
99114

100115
ts_diff = timespec_elapsed(start);
101116

102-
PER_PAGE_DEBUG("UFFDIO_COPY %d \t%ld ns\n", tid,
117+
PER_PAGE_DEBUG("UFFD page-in %d \t%ld ns\n", tid,
103118
timespec_to_ns(ts_diff));
104119
PER_PAGE_DEBUG("Paged in %ld bytes at 0x%lx from thread %d\n",
105120
demand_paging_size, addr, tid);
@@ -110,6 +125,7 @@ static int handle_uffd_page_request(int uffd, uint64_t addr)
110125
bool quit_uffd_thread;
111126

112127
struct uffd_handler_args {
128+
int uffd_mode;
113129
int uffd;
114130
int pipefd;
115131
useconds_t delay;
@@ -186,7 +202,7 @@ static void *uffd_handler_thread_fn(void *arg)
186202
if (delay)
187203
usleep(delay);
188204
addr = msg.arg.pagefault.address;
189-
r = handle_uffd_page_request(uffd, addr);
205+
r = handle_uffd_page_request(uffd_args->uffd_mode, uffd, addr);
190206
if (r < 0)
191207
return NULL;
192208
pages++;
@@ -202,13 +218,32 @@ static void *uffd_handler_thread_fn(void *arg)
202218

203219
static void setup_demand_paging(struct kvm_vm *vm,
204220
pthread_t *uffd_handler_thread, int pipefd,
205-
useconds_t uffd_delay,
221+
int uffd_mode, useconds_t uffd_delay,
206222
struct uffd_handler_args *uffd_args,
207-
void *hva, uint64_t len)
223+
void *hva, void *alias, uint64_t len)
208224
{
225+
bool is_minor = (uffd_mode == UFFDIO_REGISTER_MODE_MINOR);
209226
int uffd;
210227
struct uffdio_api uffdio_api;
211228
struct uffdio_register uffdio_register;
229+
uint64_t expected_ioctls = ((uint64_t) 1) << _UFFDIO_COPY;
230+
231+
PER_PAGE_DEBUG("Userfaultfd %s mode, faults resolved with %s\n",
232+
is_minor ? "MINOR" : "MISSING",
233+
is_minor ? "UFFDIO_CONINUE" : "UFFDIO_COPY");
234+
235+
/* In order to get minor faults, prefault via the alias. */
236+
if (is_minor) {
237+
size_t p;
238+
239+
expected_ioctls = ((uint64_t) 1) << _UFFDIO_CONTINUE;
240+
241+
TEST_ASSERT(alias != NULL, "Alias required for minor faults");
242+
for (p = 0; p < (len / demand_paging_size); ++p) {
243+
memcpy(alias + (p * demand_paging_size),
244+
guest_data_prototype, demand_paging_size);
245+
}
246+
}
212247

213248
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
214249
TEST_ASSERT(uffd >= 0, "uffd creation failed, errno: %d", errno);
@@ -221,12 +256,13 @@ static void setup_demand_paging(struct kvm_vm *vm,
221256

222257
uffdio_register.range.start = (uint64_t)hva;
223258
uffdio_register.range.len = len;
224-
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
259+
uffdio_register.mode = uffd_mode;
225260
TEST_ASSERT(ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) != -1,
226261
"ioctl UFFDIO_REGISTER failed");
227-
TEST_ASSERT((uffdio_register.ioctls & UFFD_API_RANGE_IOCTLS) ==
228-
UFFD_API_RANGE_IOCTLS, "unexpected userfaultfd ioctl set");
262+
TEST_ASSERT((uffdio_register.ioctls & expected_ioctls) ==
263+
expected_ioctls, "missing userfaultfd ioctls");
229264

265+
uffd_args->uffd_mode = uffd_mode;
230266
uffd_args->uffd = uffd;
231267
uffd_args->pipefd = pipefd;
232268
uffd_args->delay = uffd_delay;
@@ -238,7 +274,7 @@ static void setup_demand_paging(struct kvm_vm *vm,
238274
}
239275

240276
struct test_params {
241-
bool use_uffd;
277+
int uffd_mode;
242278
useconds_t uffd_delay;
243279
enum vm_mem_backing_src_type src_type;
244280
bool partition_vcpu_memory_access;
@@ -275,7 +311,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
275311
perf_test_setup_vcpus(vm, nr_vcpus, guest_percpu_mem_size,
276312
p->partition_vcpu_memory_access);
277313

278-
if (p->use_uffd) {
314+
if (p->uffd_mode) {
279315
uffd_handler_threads =
280316
malloc(nr_vcpus * sizeof(*uffd_handler_threads));
281317
TEST_ASSERT(uffd_handler_threads, "Memory allocation failed");
@@ -289,6 +325,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
289325
for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++) {
290326
vm_paddr_t vcpu_gpa;
291327
void *vcpu_hva;
328+
void *vcpu_alias;
292329
uint64_t vcpu_mem_size;
293330

294331

@@ -303,8 +340,9 @@ static void run_test(enum vm_guest_mode mode, void *arg)
303340
PER_VCPU_DEBUG("Added VCPU %d with test mem gpa [%lx, %lx)\n",
304341
vcpu_id, vcpu_gpa, vcpu_gpa + vcpu_mem_size);
305342

306-
/* Cache the HVA pointer of the region */
343+
/* Cache the host addresses of the region */
307344
vcpu_hva = addr_gpa2hva(vm, vcpu_gpa);
345+
vcpu_alias = addr_gpa2alias(vm, vcpu_gpa);
308346

309347
/*
310348
* Set up user fault fd to handle demand paging
@@ -315,8 +353,9 @@ static void run_test(enum vm_guest_mode mode, void *arg)
315353
TEST_ASSERT(!r, "Failed to set up pipefd");
316354

317355
setup_demand_paging(vm, &uffd_handler_threads[vcpu_id],
318-
pipefds[vcpu_id * 2], p->uffd_delay,
319-
&uffd_args[vcpu_id], vcpu_hva,
356+
pipefds[vcpu_id * 2], p->uffd_mode,
357+
p->uffd_delay, &uffd_args[vcpu_id],
358+
vcpu_hva, vcpu_alias,
320359
vcpu_mem_size);
321360
}
322361
}
@@ -345,7 +384,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
345384

346385
pr_info("All vCPU threads joined\n");
347386

348-
if (p->use_uffd) {
387+
if (p->uffd_mode) {
349388
char c;
350389

351390
/* Tell the user fault fd handler threads to quit */
@@ -367,7 +406,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
367406

368407
free(guest_data_prototype);
369408
free(vcpu_threads);
370-
if (p->use_uffd) {
409+
if (p->uffd_mode) {
371410
free(uffd_handler_threads);
372411
free(uffd_args);
373412
free(pipefds);
@@ -377,11 +416,11 @@ static void run_test(enum vm_guest_mode mode, void *arg)
377416
static void help(char *name)
378417
{
379418
puts("");
380-
printf("usage: %s [-h] [-m mode] [-u] [-d uffd_delay_usec]\n"
419+
printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-d uffd_delay_usec]\n"
381420
" [-b memory] [-t type] [-v vcpus] [-o]\n", name);
382421
guest_modes_help();
383-
printf(" -u: use User Fault FD to handle vCPU page\n"
384-
" faults.\n");
422+
printf(" -u: use userfaultfd to handle vCPU page faults. Mode is a\n"
423+
" UFFD registration mode: 'MISSING' or 'MINOR'.\n");
385424
printf(" -d: add a delay in usec to the User Fault\n"
386425
" FD handler to simulate demand paging\n"
387426
" overheads. Ignored without -u.\n");
@@ -408,13 +447,17 @@ int main(int argc, char *argv[])
408447

409448
guest_modes_append_default();
410449

411-
while ((opt = getopt(argc, argv, "hm:ud:b:t:v:o")) != -1) {
450+
while ((opt = getopt(argc, argv, "hm:u:d:b:t:v:o")) != -1) {
412451
switch (opt) {
413452
case 'm':
414453
guest_modes_cmdline(optarg);
415454
break;
416455
case 'u':
417-
p.use_uffd = true;
456+
if (!strcmp("MISSING", optarg))
457+
p.uffd_mode = UFFDIO_REGISTER_MODE_MISSING;
458+
else if (!strcmp("MINOR", optarg))
459+
p.uffd_mode = UFFDIO_REGISTER_MODE_MINOR;
460+
TEST_ASSERT(p.uffd_mode, "UFFD mode must be 'MISSING' or 'MINOR'.");
418461
break;
419462
case 'd':
420463
p.uffd_delay = strtoul(optarg, NULL, 0);
@@ -441,6 +484,9 @@ int main(int argc, char *argv[])
441484
}
442485
}
443486

487+
TEST_ASSERT(p.uffd_mode != UFFDIO_REGISTER_MODE_MINOR || p.src_type == VM_MEM_SRC_SHMEM,
488+
"userfaultfd MINOR mode requires shared memory; pick a different -t");
489+
444490
for_each_guest_mode(run_test, &p);
445491

446492
return 0;

0 commit comments

Comments
 (0)