KVM selftests changes for 6.5:

- Add a test for splitting and reconstituting hugepages during and after
    dirty logging
 
  - Add support for CPU pinning in demand paging test
 
  - Generate dependency files so that partial rebuilds work as expected
 
  - Misc cleanups and fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCgAwFiEEMHr+pfEFOIzK+KY1YJEiAU0MEvkFAmSaKE8SHHNlYW5qY0Bn
 b29nbGUuY29tAAoJEGCRIgFNDBL5xYsP/0D614Kig677ZtMCvrxmExHvsKLOCQ5i
 hDOg9PgYFnqTBTwiK9d+QDm7xX+p9M6gHpqZD1pH8VbxQn+ybH6yP3LhIMgewJEd
 Aa9rKspX7Kljmy4J5JkzQmAO2gRlrAaGMDxDZPpmPmHCt/mn/Z7nCPYDMVjjBJxe
 p+OI4Pa6N1TYrixT7mGEcgvpjTGFXIzmJdNr+OxlGVwWp7ab/vvlqpiJbRLrRfLB
 FHDIhI5ahxLhIsz0dSp97C7vrowJT17YdFPd5JAEod9HtXEYmdQR/yxOTexzmU5L
 Zd+mu+c4px3cz+YjJO3LYk5CYVXnYXM4YKQUHxmS4jiXQ6X7irBZDLcNMc1Zflh8
 QRjHvnxzmFWY4/UXVSPwRYfw+ZEgW5WOrytMp4e+i2W7ElAcHbdjJK0QHmATO19v
 pz0uDjFadFG0PFWjE70HnpB93gU/okWjrKNQ0ARdgkTJFMfFHEFuwoBUphDUB6OZ
 c1Nab8XTB11jjJM8coTJf+hwaoD3IWWzIyBXdqCOhue1kCzxDiSaFkHfDbN2rgZS
 xUjJVljmaktGTysUYrK38jzUTDCdJWLFEyq4fxxwVCvGkF96pCNZepM6wtMwIzNq
 ku7VYiysBQuqMGIOs82v5X0Vs7zXqM4K/kU/Z8MD8eXt5tVuibpqEoCTYvw22V1b
 qDViIde6kQey
 =Wwws
 -----END PGP SIGNATURE-----

Merge tag 'kvm-x86-selftests-6.5' of https://github.com/kvm-x86/linux into HEAD

KVM selftests changes for 6.5:

 - Add a test for splitting and reconstituting hugepages during and after
   dirty logging

 - Add support for CPU pinning in demand paging test

 - Generate dependency files so that partial rebuilds work as expected

 - Misc cleanups and fixes
This commit is contained in:
Paolo Bonzini 2023-07-01 07:19:09 -04:00
commit bb05b0ef62
11 changed files with 416 additions and 118 deletions

View File

@ -61,6 +61,7 @@ TEST_PROGS_x86_64 += x86_64/nx_huge_pages_test.sh
# Compiled test targets
TEST_GEN_PROGS_x86_64 = x86_64/cpuid_test
TEST_GEN_PROGS_x86_64 += x86_64/cr4_cpuid_sync_test
TEST_GEN_PROGS_x86_64 += x86_64/dirty_log_page_splitting_test
TEST_GEN_PROGS_x86_64 += x86_64/get_msr_index_features
TEST_GEN_PROGS_x86_64 += x86_64/exit_on_emulation_failure_test
TEST_GEN_PROGS_x86_64 += x86_64/fix_hypercall_test
@ -185,6 +186,8 @@ TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH_DIR))
TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH_DIR))
LIBKVM += $(LIBKVM_$(ARCH_DIR))
OVERRIDE_TARGETS = 1
# lib.mak defines $(OUTPUT), prepends $(OUTPUT)/ to $(TEST_GEN_PROGS), and most
# importantly defines, i.e. overwrites, $(CC) (unless `make -e` or `make CC=`,
# which causes the environment variable to override the makefile).
@ -199,7 +202,7 @@ else
LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include
endif
CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -O2 -g -std=gnu99 \
-Wno-gnu-variable-sized-type-not-at-end \
-Wno-gnu-variable-sized-type-not-at-end -MD\
-fno-builtin-memcmp -fno-builtin-memcpy -fno-builtin-memset \
-fno-stack-protector -fno-PIE -I$(LINUX_TOOL_INCLUDE) \
-I$(LINUX_TOOL_ARCH_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude \
@ -226,7 +229,18 @@ LIBKVM_S_OBJ := $(patsubst %.S, $(OUTPUT)/%.o, $(LIBKVM_S))
LIBKVM_STRING_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_STRING))
LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(LIBKVM_STRING_OBJ)
EXTRA_CLEAN += $(LIBKVM_OBJS) cscope.*
TEST_GEN_OBJ = $(patsubst %, %.o, $(TEST_GEN_PROGS))
TEST_GEN_OBJ += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED))
TEST_DEP_FILES = $(patsubst %.o, %.d, $(TEST_GEN_OBJ))
TEST_DEP_FILES += $(patsubst %.o, %.d, $(LIBKVM_OBJS))
-include $(TEST_DEP_FILES)
$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): %: %.o
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIBKVM_OBJS) $(LDLIBS) -o $@
$(TEST_GEN_OBJ): $(OUTPUT)/%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
EXTRA_CLEAN += $(LIBKVM_OBJS) $(TEST_DEP_FILES) $(TEST_GEN_OBJ) cscope.*
x := $(shell mkdir -p $(sort $(dir $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ))))
$(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c

View File

@ -128,6 +128,7 @@ static void prefault_mem(void *alias, uint64_t len)
static void run_test(enum vm_guest_mode mode, void *arg)
{
struct memstress_vcpu_args *vcpu_args;
struct test_params *p = arg;
struct uffd_desc **uffd_descs = NULL;
struct timespec start;
@ -145,24 +146,24 @@ static void run_test(enum vm_guest_mode mode, void *arg)
"Failed to allocate buffer for guest data pattern");
memset(guest_data_prototype, 0xAB, demand_paging_size);
if (p->uffd_mode == UFFDIO_REGISTER_MODE_MINOR) {
for (i = 0; i < nr_vcpus; i++) {
vcpu_args = &memstress_args.vcpu_args[i];
prefault_mem(addr_gpa2alias(vm, vcpu_args->gpa),
vcpu_args->pages * memstress_args.guest_page_size);
}
}
if (p->uffd_mode) {
uffd_descs = malloc(nr_vcpus * sizeof(struct uffd_desc *));
TEST_ASSERT(uffd_descs, "Memory allocation failed");
for (i = 0; i < nr_vcpus; i++) {
struct memstress_vcpu_args *vcpu_args;
void *vcpu_hva;
void *vcpu_alias;
vcpu_args = &memstress_args.vcpu_args[i];
/* Cache the host addresses of the region */
vcpu_hva = addr_gpa2hva(vm, vcpu_args->gpa);
vcpu_alias = addr_gpa2alias(vm, vcpu_args->gpa);
prefault_mem(vcpu_alias,
vcpu_args->pages * memstress_args.guest_page_size);
/*
* Set up user fault fd to handle demand paging
* requests.
@ -207,10 +208,11 @@ static void help(char *name)
{
puts("");
printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-d uffd_delay_usec]\n"
" [-b memory] [-s type] [-v vcpus] [-o]\n", name);
" [-b memory] [-s type] [-v vcpus] [-c cpu_list] [-o]\n", name);
guest_modes_help();
printf(" -u: use userfaultfd to handle vCPU page faults. Mode is a\n"
" UFFD registration mode: 'MISSING' or 'MINOR'.\n");
kvm_print_vcpu_pinning_help();
printf(" -d: add a delay in usec to the User Fault\n"
" FD handler to simulate demand paging\n"
" overheads. Ignored without -u.\n");
@ -228,6 +230,7 @@ static void help(char *name)
int main(int argc, char *argv[])
{
int max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
const char *cpulist = NULL;
struct test_params p = {
.src_type = DEFAULT_VM_MEM_SRC,
.partition_vcpu_memory_access = true,
@ -236,7 +239,7 @@ int main(int argc, char *argv[])
guest_modes_append_default();
while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:o")) != -1) {
while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:c:o")) != -1) {
switch (opt) {
case 'm':
guest_modes_cmdline(optarg);
@ -263,6 +266,9 @@ int main(int argc, char *argv[])
TEST_ASSERT(nr_vcpus <= max_vcpus,
"Invalid number of vcpus, must be between 1 and %d", max_vcpus);
break;
case 'c':
cpulist = optarg;
break;
case 'o':
p.partition_vcpu_memory_access = false;
break;
@ -278,6 +284,12 @@ int main(int argc, char *argv[])
TEST_FAIL("userfaultfd MINOR mode requires shared memory; pick a different -s");
}
if (cpulist) {
kvm_parse_vcpu_pinning(cpulist, memstress_args.vcpu_to_pcpu,
nr_vcpus);
memstress_args.pin_vcpus = true;
}
for_each_guest_mode(run_test, &p);
return 0;

View File

@ -136,77 +136,6 @@ struct test_params {
bool random_access;
};
static void toggle_dirty_logging(struct kvm_vm *vm, int slots, bool enable)
{
int i;
for (i = 0; i < slots; i++) {
int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
int flags = enable ? KVM_MEM_LOG_DIRTY_PAGES : 0;
vm_mem_region_set_flags(vm, slot, flags);
}
}
static inline void enable_dirty_logging(struct kvm_vm *vm, int slots)
{
toggle_dirty_logging(vm, slots, true);
}
static inline void disable_dirty_logging(struct kvm_vm *vm, int slots)
{
toggle_dirty_logging(vm, slots, false);
}
static void get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots)
{
int i;
for (i = 0; i < slots; i++) {
int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
kvm_vm_get_dirty_log(vm, slot, bitmaps[i]);
}
}
static void clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[],
int slots, uint64_t pages_per_slot)
{
int i;
for (i = 0; i < slots; i++) {
int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
kvm_vm_clear_dirty_log(vm, slot, bitmaps[i], 0, pages_per_slot);
}
}
static unsigned long **alloc_bitmaps(int slots, uint64_t pages_per_slot)
{
unsigned long **bitmaps;
int i;
bitmaps = malloc(slots * sizeof(bitmaps[0]));
TEST_ASSERT(bitmaps, "Failed to allocate bitmaps array.");
for (i = 0; i < slots; i++) {
bitmaps[i] = bitmap_zalloc(pages_per_slot);
TEST_ASSERT(bitmaps[i], "Failed to allocate slot bitmap.");
}
return bitmaps;
}
static void free_bitmaps(unsigned long *bitmaps[], int slots)
{
int i;
for (i = 0; i < slots; i++)
free(bitmaps[i]);
free(bitmaps);
}
static void run_test(enum vm_guest_mode mode, void *arg)
{
struct test_params *p = arg;
@ -236,7 +165,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
host_num_pages = vm_num_host_pages(mode, guest_num_pages);
pages_per_slot = host_num_pages / p->slots;
bitmaps = alloc_bitmaps(p->slots, pages_per_slot);
bitmaps = memstress_alloc_bitmaps(p->slots, pages_per_slot);
if (dirty_log_manual_caps)
vm_enable_cap(vm, KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2,
@ -277,7 +206,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
/* Enable dirty logging */
clock_gettime(CLOCK_MONOTONIC, &start);
enable_dirty_logging(vm, p->slots);
memstress_enable_dirty_logging(vm, p->slots);
ts_diff = timespec_elapsed(start);
pr_info("Enabling dirty logging time: %ld.%.9lds\n\n",
ts_diff.tv_sec, ts_diff.tv_nsec);
@ -306,7 +235,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
iteration, ts_diff.tv_sec, ts_diff.tv_nsec);
clock_gettime(CLOCK_MONOTONIC, &start);
get_dirty_log(vm, bitmaps, p->slots);
memstress_get_dirty_log(vm, bitmaps, p->slots);
ts_diff = timespec_elapsed(start);
get_dirty_log_total = timespec_add(get_dirty_log_total,
ts_diff);
@ -315,7 +244,8 @@ static void run_test(enum vm_guest_mode mode, void *arg)
if (dirty_log_manual_caps) {
clock_gettime(CLOCK_MONOTONIC, &start);
clear_dirty_log(vm, bitmaps, p->slots, pages_per_slot);
memstress_clear_dirty_log(vm, bitmaps, p->slots,
pages_per_slot);
ts_diff = timespec_elapsed(start);
clear_dirty_log_total = timespec_add(clear_dirty_log_total,
ts_diff);
@ -334,7 +264,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
/* Disable dirty logging */
clock_gettime(CLOCK_MONOTONIC, &start);
disable_dirty_logging(vm, p->slots);
memstress_disable_dirty_logging(vm, p->slots);
ts_diff = timespec_elapsed(start);
pr_info("Disabling dirty logging time: %ld.%.9lds\n",
ts_diff.tv_sec, ts_diff.tv_nsec);
@ -359,7 +289,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
clear_dirty_log_total.tv_nsec, avg.tv_sec, avg.tv_nsec);
}
free_bitmaps(bitmaps, p->slots);
memstress_free_bitmaps(bitmaps, p->slots);
arch_cleanup_vm(vm);
memstress_destroy_vm(vm);
}
@ -402,17 +332,7 @@ static void help(char *name)
" so -w X means each page has an X%% chance of writing\n"
" and a (100-X)%% chance of reading.\n"
" (default: 100 i.e. all pages are written to.)\n");
printf(" -c: Pin tasks to physical CPUs. Takes a list of comma separated\n"
" values (target pCPU), one for each vCPU, plus an optional\n"
" entry for the main application task (specified via entry\n"
" <nr_vcpus + 1>). If used, entries must be provided for all\n"
" vCPUs, i.e. pinning vCPUs is all or nothing.\n\n"
" E.g. to create 3 vCPUs, pin vCPU0=>pCPU22, vCPU1=>pCPU23,\n"
" vCPU2=>pCPU24, and pin the application task to pCPU50:\n\n"
" ./dirty_log_perf_test -v 3 -c 22,23,24,50\n\n"
" To leave the application task unpinned, drop the final entry:\n\n"
" ./dirty_log_perf_test -v 3 -c 22,23,24\n\n"
" (default: no pinning)\n");
kvm_print_vcpu_pinning_help();
puts("");
exit(0);
}

View File

@ -733,6 +733,7 @@ static inline struct kvm_vm *vm_create_with_one_vcpu(struct kvm_vcpu **vcpu,
struct kvm_vcpu *vm_recreate_with_one_vcpu(struct kvm_vm *vm);
void kvm_pin_this_task_to_pcpu(uint32_t pcpu);
void kvm_print_vcpu_pinning_help(void);
void kvm_parse_vcpu_pinning(const char *pcpus_string, uint32_t vcpu_to_pcpu[],
int nr_vcpus);

View File

@ -72,4 +72,12 @@ void memstress_guest_code(uint32_t vcpu_id);
uint64_t memstress_nested_pages(int nr_vcpus);
void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vcpus[]);
void memstress_enable_dirty_logging(struct kvm_vm *vm, int slots);
void memstress_disable_dirty_logging(struct kvm_vm *vm, int slots);
void memstress_get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots);
void memstress_clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[],
int slots, uint64_t pages_per_slot);
unsigned long **memstress_alloc_bitmaps(int slots, uint64_t pages_per_slot);
void memstress_free_bitmaps(unsigned long *bitmaps[], int slots);
#endif /* SELFTEST_KVM_MEMSTRESS_H */

View File

@ -494,6 +494,23 @@ static uint32_t parse_pcpu(const char *cpu_str, const cpu_set_t *allowed_mask)
return pcpu;
}
void kvm_print_vcpu_pinning_help(void)
{
const char *name = program_invocation_name;
printf(" -c: Pin tasks to physical CPUs. Takes a list of comma separated\n"
" values (target pCPU), one for each vCPU, plus an optional\n"
" entry for the main application task (specified via entry\n"
" <nr_vcpus + 1>). If used, entries must be provided for all\n"
" vCPUs, i.e. pinning vCPUs is all or nothing.\n\n"
" E.g. to create 3 vCPUs, pin vCPU0=>pCPU22, vCPU1=>pCPU23,\n"
" vCPU2=>pCPU24, and pin the application task to pCPU50:\n\n"
" %s -v 3 -c 22,23,24,50\n\n"
" To leave the application task unpinned, drop the final entry:\n\n"
" %s -v 3 -c 22,23,24\n\n"
" (default: no pinning)\n", name, name);
}
void kvm_parse_vcpu_pinning(const char *pcpus_string, uint32_t vcpu_to_pcpu[],
int nr_vcpus)
{

View File

@ -5,6 +5,7 @@
#define _GNU_SOURCE
#include <inttypes.h>
#include <linux/bitmap.h>
#include "kvm_util.h"
#include "memstress.h"
@ -64,6 +65,9 @@ void memstress_guest_code(uint32_t vcpu_idx)
GUEST_ASSERT(vcpu_args->vcpu_idx == vcpu_idx);
while (true) {
for (i = 0; i < sizeof(memstress_args); i += args->guest_page_size)
(void) *((volatile char *)args + i);
for (i = 0; i < pages; i++) {
if (args->random_access)
page = guest_random_u32(&rand_state) % pages;
@ -320,3 +324,74 @@ void memstress_join_vcpu_threads(int nr_vcpus)
for (i = 0; i < nr_vcpus; i++)
pthread_join(vcpu_threads[i].thread, NULL);
}
static void toggle_dirty_logging(struct kvm_vm *vm, int slots, bool enable)
{
int i;
for (i = 0; i < slots; i++) {
int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
int flags = enable ? KVM_MEM_LOG_DIRTY_PAGES : 0;
vm_mem_region_set_flags(vm, slot, flags);
}
}
void memstress_enable_dirty_logging(struct kvm_vm *vm, int slots)
{
toggle_dirty_logging(vm, slots, true);
}
void memstress_disable_dirty_logging(struct kvm_vm *vm, int slots)
{
toggle_dirty_logging(vm, slots, false);
}
void memstress_get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots)
{
int i;
for (i = 0; i < slots; i++) {
int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
kvm_vm_get_dirty_log(vm, slot, bitmaps[i]);
}
}
void memstress_clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[],
int slots, uint64_t pages_per_slot)
{
int i;
for (i = 0; i < slots; i++) {
int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
kvm_vm_clear_dirty_log(vm, slot, bitmaps[i], 0, pages_per_slot);
}
}
unsigned long **memstress_alloc_bitmaps(int slots, uint64_t pages_per_slot)
{
unsigned long **bitmaps;
int i;
bitmaps = malloc(slots * sizeof(bitmaps[0]));
TEST_ASSERT(bitmaps, "Failed to allocate bitmaps array.");
for (i = 0; i < slots; i++) {
bitmaps[i] = bitmap_zalloc(pages_per_slot);
TEST_ASSERT(bitmaps[i], "Failed to allocate slot bitmap.");
}
return bitmaps;
}
void memstress_free_bitmaps(unsigned long *bitmaps[], int slots)
{
int i;
for (i = 0; i < slots; i++)
free(bitmaps[i]);
free(bitmaps);
}

View File

@ -70,7 +70,7 @@ static void *uffd_handler_thread_fn(void *arg)
r = read(pollfd[1].fd, &tmp_chr, 1);
TEST_ASSERT(r == 1,
"Error reading pipefd in UFFD thread\n");
return NULL;
break;
}
if (!(pollfd[0].revents & POLLIN))
@ -103,7 +103,7 @@ static void *uffd_handler_thread_fn(void *arg)
ts_diff = timespec_elapsed(start);
PER_VCPU_DEBUG("userfaulted %ld pages over %ld.%.9lds. (%f/sec)\n",
pages, ts_diff.tv_sec, ts_diff.tv_nsec,
pages / ((double)ts_diff.tv_sec + (double)ts_diff.tv_nsec / 100000000.0));
pages / ((double)ts_diff.tv_sec + (double)ts_diff.tv_nsec / NSEC_PER_SEC));
return NULL;
}

View File

@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KVM dirty logging page splitting test
*
* Based on dirty_log_perf.c
*
* Copyright (C) 2018, Red Hat, Inc.
* Copyright (C) 2023, Google, Inc.
*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <linux/bitmap.h>
#include "kvm_util.h"
#include "test_util.h"
#include "memstress.h"
#include "guest_modes.h"
#define VCPUS 2
#define SLOTS 2
#define ITERATIONS 2
static uint64_t guest_percpu_mem_size = DEFAULT_PER_VCPU_MEM_SIZE;
static enum vm_mem_backing_src_type backing_src = VM_MEM_SRC_ANONYMOUS_HUGETLB;
static u64 dirty_log_manual_caps;
static bool host_quit;
static int iteration;
static int vcpu_last_completed_iteration[KVM_MAX_VCPUS];
struct kvm_page_stats {
uint64_t pages_4k;
uint64_t pages_2m;
uint64_t pages_1g;
uint64_t hugepages;
};
static void get_page_stats(struct kvm_vm *vm, struct kvm_page_stats *stats, const char *stage)
{
stats->pages_4k = vm_get_stat(vm, "pages_4k");
stats->pages_2m = vm_get_stat(vm, "pages_2m");
stats->pages_1g = vm_get_stat(vm, "pages_1g");
stats->hugepages = stats->pages_2m + stats->pages_1g;
pr_debug("\nPage stats after %s: 4K: %ld 2M: %ld 1G: %ld huge: %ld\n",
stage, stats->pages_4k, stats->pages_2m, stats->pages_1g,
stats->hugepages);
}
static void run_vcpu_iteration(struct kvm_vm *vm)
{
int i;
iteration++;
for (i = 0; i < VCPUS; i++) {
while (READ_ONCE(vcpu_last_completed_iteration[i]) !=
iteration)
;
}
}
static void vcpu_worker(struct memstress_vcpu_args *vcpu_args)
{
struct kvm_vcpu *vcpu = vcpu_args->vcpu;
int vcpu_idx = vcpu_args->vcpu_idx;
while (!READ_ONCE(host_quit)) {
int current_iteration = READ_ONCE(iteration);
vcpu_run(vcpu);
ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_SYNC);
vcpu_last_completed_iteration[vcpu_idx] = current_iteration;
/* Wait for the start of the next iteration to be signaled. */
while (current_iteration == READ_ONCE(iteration) &&
READ_ONCE(iteration) >= 0 &&
!READ_ONCE(host_quit))
;
}
}
static void run_test(enum vm_guest_mode mode, void *unused)
{
struct kvm_vm *vm;
unsigned long **bitmaps;
uint64_t guest_num_pages;
uint64_t host_num_pages;
uint64_t pages_per_slot;
int i;
uint64_t total_4k_pages;
struct kvm_page_stats stats_populated;
struct kvm_page_stats stats_dirty_logging_enabled;
struct kvm_page_stats stats_dirty_pass[ITERATIONS];
struct kvm_page_stats stats_clear_pass[ITERATIONS];
struct kvm_page_stats stats_dirty_logging_disabled;
struct kvm_page_stats stats_repopulated;
vm = memstress_create_vm(mode, VCPUS, guest_percpu_mem_size,
SLOTS, backing_src, false);
guest_num_pages = (VCPUS * guest_percpu_mem_size) >> vm->page_shift;
guest_num_pages = vm_adjust_num_guest_pages(mode, guest_num_pages);
host_num_pages = vm_num_host_pages(mode, guest_num_pages);
pages_per_slot = host_num_pages / SLOTS;
bitmaps = memstress_alloc_bitmaps(SLOTS, pages_per_slot);
if (dirty_log_manual_caps)
vm_enable_cap(vm, KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2,
dirty_log_manual_caps);
/* Start the iterations */
iteration = -1;
host_quit = false;
for (i = 0; i < VCPUS; i++)
vcpu_last_completed_iteration[i] = -1;
memstress_start_vcpu_threads(VCPUS, vcpu_worker);
run_vcpu_iteration(vm);
get_page_stats(vm, &stats_populated, "populating memory");
/* Enable dirty logging */
memstress_enable_dirty_logging(vm, SLOTS);
get_page_stats(vm, &stats_dirty_logging_enabled, "enabling dirty logging");
while (iteration < ITERATIONS) {
run_vcpu_iteration(vm);
get_page_stats(vm, &stats_dirty_pass[iteration - 1],
"dirtying memory");
memstress_get_dirty_log(vm, bitmaps, SLOTS);
if (dirty_log_manual_caps) {
memstress_clear_dirty_log(vm, bitmaps, SLOTS, pages_per_slot);
get_page_stats(vm, &stats_clear_pass[iteration - 1], "clearing dirty log");
}
}
/* Disable dirty logging */
memstress_disable_dirty_logging(vm, SLOTS);
get_page_stats(vm, &stats_dirty_logging_disabled, "disabling dirty logging");
/* Run vCPUs again to fault pages back in. */
run_vcpu_iteration(vm);
get_page_stats(vm, &stats_repopulated, "repopulating memory");
/*
* Tell the vCPU threads to quit. No need to manually check that vCPUs
* have stopped running after disabling dirty logging, the join will
* wait for them to exit.
*/
host_quit = true;
memstress_join_vcpu_threads(VCPUS);
memstress_free_bitmaps(bitmaps, SLOTS);
memstress_destroy_vm(vm);
/* Make assertions about the page counts. */
total_4k_pages = stats_populated.pages_4k;
total_4k_pages += stats_populated.pages_2m * 512;
total_4k_pages += stats_populated.pages_1g * 512 * 512;
/*
* Check that all huge pages were split. Since large pages can only
* exist in the data slot, and the vCPUs should have dirtied all pages
* in the data slot, there should be no huge pages left after splitting.
* Splitting happens at dirty log enable time without
* KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2 and after the first clear pass
* with that capability.
*/
if (dirty_log_manual_caps) {
ASSERT_EQ(stats_clear_pass[0].hugepages, 0);
ASSERT_EQ(stats_clear_pass[0].pages_4k, total_4k_pages);
ASSERT_EQ(stats_dirty_logging_enabled.hugepages, stats_populated.hugepages);
} else {
ASSERT_EQ(stats_dirty_logging_enabled.hugepages, 0);
ASSERT_EQ(stats_dirty_logging_enabled.pages_4k, total_4k_pages);
}
/*
* Once dirty logging is disabled and the vCPUs have touched all their
* memory again, the page counts should be the same as they were
* right after initial population of memory.
*/
ASSERT_EQ(stats_populated.pages_4k, stats_repopulated.pages_4k);
ASSERT_EQ(stats_populated.pages_2m, stats_repopulated.pages_2m);
ASSERT_EQ(stats_populated.pages_1g, stats_repopulated.pages_1g);
}
static void help(char *name)
{
puts("");
printf("usage: %s [-h] [-b vcpu bytes] [-s mem type]\n",
name);
puts("");
printf(" -b: specify the size of the memory region which should be\n"
" dirtied by each vCPU. e.g. 10M or 3G.\n"
" (default: 1G)\n");
backing_src_help("-s");
puts("");
}
int main(int argc, char *argv[])
{
int opt;
TEST_REQUIRE(get_kvm_param_bool("eager_page_split"));
TEST_REQUIRE(get_kvm_param_bool("tdp_mmu"));
while ((opt = getopt(argc, argv, "b:hs:")) != -1) {
switch (opt) {
case 'b':
guest_percpu_mem_size = parse_size(optarg);
break;
case 'h':
help(argv[0]);
exit(0);
case 's':
backing_src = parse_backing_src_type(optarg);
break;
default:
help(argv[0]);
exit(1);
}
}
if (!is_backing_src_hugetlb(backing_src)) {
pr_info("This test will only work reliably with HugeTLB memory. "
"It can work with THP, but that is best effort.\n");
}
guest_modes_append_default();
dirty_log_manual_caps = 0;
for_each_guest_mode(run_test, NULL);
dirty_log_manual_caps =
kvm_check_cap(KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2);
if (dirty_log_manual_caps) {
dirty_log_manual_caps &= (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE |
KVM_DIRTY_LOG_INITIALLY_SET);
for_each_guest_mode(run_test, NULL);
} else {
pr_info("Skipping testing with MANUAL_PROTECT as it is not supported");
}
return 0;
}

View File

@ -226,7 +226,7 @@ static void help(char *name)
puts("");
printf("usage: %s [-h] [-p period_ms] [-t token]\n", name);
puts("");
printf(" -p: The NX reclaim period in miliseconds.\n");
printf(" -p: The NX reclaim period in milliseconds.\n");
printf(" -t: The magic token to indicate environment setup is done.\n");
printf(" -r: The test has reboot permissions and can disable NX huge pages.\n");
puts("");

View File

@ -116,29 +116,21 @@ static void l1_guest_code(struct vmx_pages *vmx_pages)
GUEST_DONE();
}
static void stable_tsc_check_supported(void)
static bool system_has_stable_tsc(void)
{
bool tsc_is_stable;
FILE *fp;
char buf[4];
fp = fopen("/sys/devices/system/clocksource/clocksource0/current_clocksource", "r");
if (fp == NULL)
goto skip_test;
return false;
if (fgets(buf, sizeof(buf), fp) == NULL)
goto close_fp;
if (strncmp(buf, "tsc", sizeof(buf)))
goto close_fp;
tsc_is_stable = fgets(buf, sizeof(buf), fp) &&
!strncmp(buf, "tsc", sizeof(buf));
fclose(fp);
return;
close_fp:
fclose(fp);
skip_test:
print_skip("Kernel does not use TSC clocksource - assuming that host TSC is not stable");
exit(KSFT_SKIP);
return tsc_is_stable;
}
int main(int argc, char *argv[])
@ -156,7 +148,7 @@ int main(int argc, char *argv[])
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
TEST_REQUIRE(kvm_has_cap(KVM_CAP_TSC_CONTROL));
stable_tsc_check_supported();
TEST_REQUIRE(system_has_stable_tsc());
/*
* We set L1's scale factor to be a random number from 2 to 10.