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

Konstantin Khorenko khorenko at virtuozzo.com
Wed Oct 7 01:12:53 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)
Signed-off-by: Konstantin Khorenko <khorenko at virtuozzo.com>
---
 .../testing/selftests/ve/ve_cpuid_override_test.c  | 62 ++++++++++++++++------
 1 file changed, 45 insertions(+), 17 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_cpuid_override_test.c b/tools/testing/selftests/ve/ve_cpuid_override_test.c
index f2883539bac3f..d940acc7ee792 100644
--- a/tools/testing/selftests/ve/ve_cpuid_override_test.c
+++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
@@ -31,6 +31,7 @@
 #define CPUID_OVERRIDE_PATH	"/proc/vz/cpuid_override"
 #define CPUID_LEAF		1
 #define CPUID_LINE_LEN		128
+#define NO_RESCHED_ROUNDS	20
 /* The kernel takes a table back only in one write below a page. */
 #define CPUID_WRITE_MAX		4096
 #define CPUID_SAVED_MAX		(CPUID_WRITE_MAX + 1)
@@ -49,14 +50,15 @@ static void do_cpuid(unsigned int leaf, struct cpuid_regs *r)
 
 struct child_ctx {
 	int result_pipe[2];
+	int stop;
 };
 
 /*
  * 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.
  */
 static int cpuid_child(struct child_ctx *ctx)
 {
@@ -66,7 +68,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,
 			       FIXTURE_DATA(ve_cpuid_override) * self,
 			       struct child_ctx *ctx)
@@ -170,16 +172,20 @@ static pid_t spawn_cpuid_child(struct __test_metadata *_metadata,
 	ASSERT_GE(pid, 0);
 	close(ctx->result_pipe[1]);
 
-	ASSERT_GE(waitpid(pid, &status, WUNTRACED), 0);
-	if (!WIFSTOPPED(status)) {
-		/* Reaped: the pid may be reused, keep it from teardown. */
-		self->child = 0;
-		if (WIFEXITED(status))
-			TH_LOG("child exited with %d", WEXITSTATUS(status));
-		if (WIFSIGNALED(status))
-			TH_LOG("child killed by signal %d", WTERMSIG(status));
+	if (ctx->stop) {
+		ASSERT_GE(waitpid(pid, &status, WUNTRACED), 0);
+		if (!WIFSTOPPED(status)) {
+			/* Reaped: the pid may be reused, keep it from teardown. */
+			self->child = 0;
+			if (WIFEXITED(status))
+				TH_LOG("child exited with %d",
+				       WEXITSTATUS(status));
+			if (WIFSIGNALED(status))
+				TH_LOG("child killed by signal %d",
+				       WTERMSIG(status));
+		}
+		ASSERT_TRUE(WIFSTOPPED(status));
 	}
-	ASSERT_TRUE(WIFSTOPPED(status));
 	return pid;
 }
 
@@ -208,7 +214,7 @@ static void collect_cpuid_child(struct __test_metadata *_metadata,
 
 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;
 
@@ -229,7 +235,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;
 
@@ -244,4 +250,26 @@ TEST_F(ve_cpuid_override, flushed_table_falls_back_to_hardware)
 	EXPECT_NE(r.edx & self->bit, 0u);
 }
 
+/*
+ * 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;
+	int i;
+
+	ASSERT_EQ(write_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, self->table), 0);
+
+	for (i = 0; i < NO_RESCHED_ROUNDS; i++) {
+		struct child_ctx ctx = { .stop = 0 };
+
+		spawn_cpuid_child(_metadata, self, &ctx);
+		collect_cpuid_child(_metadata, self, &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