[Devel] [PATCH hci-8.1 v2 3/5] selftests/ve: check CPUID override right after entering a VE

Konstantin Khorenko khorenko at virtuozzo.com
Tue Oct 6 20:19:12 MSK 2026


Add a case where the child executes CPUID straight after unshare(),
without stopping or blocking in between, and expects the table values.
Before the kernel change the override took effect only after the first
context switch, so the child saw the hardware values.

A tick can still preempt the child in that window, so the case runs
the sequence several times: a kernel that arms faulting only at the
switch has to get lucky every time.

https://virtuozzo.atlassian.net/browse/VSTOR-148843

Feature: x86: cpu pools (cpuid override)
---
 .../testing/selftests/ve/ve_cpuid_override_test.c  | 54 ++++++++++++++++------
 1 file changed, 41 insertions(+), 13 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_cpuid_override_test.c b/tools/testing/selftests/ve/ve_cpuid_override_test.c
index 291b88404a53e..0e748d4fd5f33 100644
--- a/tools/testing/selftests/ve/ve_cpuid_override_test.c
+++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
@@ -27,6 +27,7 @@
 #define CPUID_OVERRIDE_PATH	"/proc/vz/cpuid_override"
 #define CPUID_LEAF		1
 #define CPUID_TABLE_LEN		128
+#define NO_RESCHED_ROUNDS	20
 /* 128 entries of up to 67 characters each, as the kernel prints them. */
 #define CPUID_TABLE_MAX		16384
 
@@ -56,13 +57,14 @@ static int write_cpuid_override(const char *table)
 
 /*
  * The child enters a fresh VE, so it is armed with the table state of that
- * moment, then reports its view of the leaf to the parent over a pipe. It
- * stops itself in between: a stopped task is off the CPU, so the resume is
- * a context switch, and the parent may change the table while the child
- * stands still.
+ * moment, then reports its view of the leaf to the parent over a pipe. With
+ * @stop it stops itself in between: a stopped task is off the CPU, so the
+ * resume is a context switch, and the parent may change the table while
+ * the child stands still.
  */
 struct child_ctx {
 	int result_pipe[2];
+	int stop;
 };
 
 static int cpuid_child(struct child_ctx *ctx)
@@ -73,7 +75,7 @@ static int cpuid_child(struct child_ctx *ctx)
 
 	if (unshare(CLONE_NEWVE) < 0)
 		return 21;
-	if (raise(SIGSTOP))
+	if (ctx->stop && raise(SIGSTOP))
 		return 22;
 
 	do_cpuid(CPUID_LEAF, &r);
@@ -148,7 +150,7 @@ FIXTURE_TEARDOWN_PARENT(ve_cpuid_override)
 	close(cgv2_fd);
 }
 
-/* Fork the child into the VE and return once it stands still there. */
+/* Fork the child into the VE; with @stop return once it stands still. */
 static pid_t spawn_cpuid_child(struct __test_metadata *_metadata,
 			       int cgv2_fd, int ctid, struct child_ctx *ctx)
 {
@@ -166,11 +168,13 @@ static pid_t spawn_cpuid_child(struct __test_metadata *_metadata,
 	ASSERT_GE(enter_cgroup(cgv2_fd, 0), 0);
 	close(ctx->result_pipe[1]);
 
-	ASSERT_GE(waitpid(pid, &status, WUNTRACED), 0);
-	if (WIFEXITED(status))
-		TH_LOG("child exited with %d before stopping",
-		       WEXITSTATUS(status));
-	ASSERT_TRUE(WIFSTOPPED(status));
+	if (ctx->stop) {
+		ASSERT_GE(waitpid(pid, &status, WUNTRACED), 0);
+		if (WIFEXITED(status))
+			TH_LOG("child exited with %d before stopping",
+			       WEXITSTATUS(status));
+		ASSERT_TRUE(WIFSTOPPED(status));
+	}
 	return pid;
 }
 
@@ -197,7 +201,7 @@ static void collect_cpuid_child(struct __test_metadata *_metadata, pid_t pid,
 
 TEST_F(ve_cpuid_override, ve_sees_table_host_sees_hardware)
 {
-	struct child_ctx ctx;
+	struct child_ctx ctx = { .stop = 1 };
 	struct cpuid_regs r;
 	pid_t pid;
 
@@ -218,7 +222,7 @@ TEST_F(ve_cpuid_override, ve_sees_table_host_sees_hardware)
 
 TEST_F(ve_cpuid_override, flushed_table_falls_back_to_hardware)
 {
-	struct child_ctx ctx;
+	struct child_ctx ctx = { .stop = 1 };
 	struct cpuid_regs r;
 	pid_t pid;
 
@@ -233,4 +237,28 @@ TEST_F(ve_cpuid_override, flushed_table_falls_back_to_hardware)
 	EXPECT_NE(r.edx & self->bit, 0);
 }
 
+/*
+ * The child does not block between entering the VE and the CPUID, but a
+ * tick can still preempt it there. Repeat, so that a kernel which arms
+ * faulting only at the switch has to get lucky every time.
+ */
+TEST_F(ve_cpuid_override, armed_before_first_reschedule)
+{
+	struct cpuid_regs r;
+	pid_t pid;
+	int i;
+
+	ASSERT_EQ(write_cpuid_override(self->table), 0);
+
+	for (i = 0; i < NO_RESCHED_ROUNDS; i++) {
+		struct child_ctx ctx = { .stop = 0 };
+
+		pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid,
+					&ctx);
+		collect_cpuid_child(_metadata, pid, &ctx, &r);
+		ASSERT_EQ(r.edx, self->host.edx & ~self->bit)
+			TH_LOG("round %d", i);
+	}
+}
+
 TEST_HARNESS_MAIN

-- 
2.43.0



More information about the Devel mailing list