Test the per-mm cache aware scheduling prctl: get/set round trips
for every attribute including resets via PR_SCHED_CACHE_DEFAULT in
both its sign-extended and 32-bit truncated form, argument
validation (out of range values, unknown attributes and sub-commands,
nonzero unused arguments, a faulting GET pointer, and that rejected
values are not stored), that attribute values live on the shared mm
while the inherit mask stays per thread, that fork() always inherits
without changes leaking back to the parent, and that execve() resets
attributes unless selected in the inherit mask, verified by
re-executing the test binary in a checker mode.

All tests SKIP on kernels without PR_SCHED_CACHE.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Yangyu Chen <[email protected]>
---
 tools/testing/selftests/prctl/.gitignore    |   1 +
 tools/testing/selftests/prctl/Makefile      |  15 +-
 tools/testing/selftests/prctl/config        |   1 +
 tools/testing/selftests/prctl/sched-cache.c | 394 ++++++++++++++++++++
 4 files changed, 406 insertions(+), 5 deletions(-)
 create mode 100644 tools/testing/selftests/prctl/sched-cache.c

diff --git a/tools/testing/selftests/prctl/.gitignore 
b/tools/testing/selftests/prctl/.gitignore
index 05d5e31661df..1b22c9f45c4b 100644
--- a/tools/testing/selftests/prctl/.gitignore
+++ b/tools/testing/selftests/prctl/.gitignore
@@ -2,5 +2,6 @@
 disable-tsc-ctxt-sw-stress-test
 disable-tsc-on-off-stress-test
 disable-tsc-test
+sched-cache
 set-anon-vma-name-test
 set-process-name
diff --git a/tools/testing/selftests/prctl/Makefile 
b/tools/testing/selftests/prctl/Makefile
index e770e86fad9a..f889721a0749 100644
--- a/tools/testing/selftests/prctl/Makefile
+++ b/tools/testing/selftests/prctl/Makefile
@@ -1,14 +1,19 @@
 # SPDX-License-Identifier: GPL-2.0
-ifndef CROSS_COMPILE
 ARCH ?= $(shell uname -m 2>/dev/null || echo not)
 override ARCH := $(shell echo $(ARCH) | sed -e s/i.86/x86/ -e s/x86_64/x86/)
 
+# sched-cache tests an arch-independent prctl
+TEST_PROGS := sched-cache
+
+ifndef CROSS_COMPILE
 ifeq ($(ARCH),x86)
-TEST_PROGS := disable-tsc-ctxt-sw-stress-test disable-tsc-on-off-stress-test \
+TEST_PROGS += disable-tsc-ctxt-sw-stress-test disable-tsc-on-off-stress-test \
                disable-tsc-test set-anon-vma-name-test set-process-name
+endif
+endif
+
+LDLIBS += -pthread
+
 all: $(TEST_PROGS)
 
 include ../lib.mk
-
-endif
-endif
diff --git a/tools/testing/selftests/prctl/config 
b/tools/testing/selftests/prctl/config
index c6ed03c544e5..f44bf7e888e6 100644
--- a/tools/testing/selftests/prctl/config
+++ b/tools/testing/selftests/prctl/config
@@ -1 +1,2 @@
 CONFIG_ANON_VMA_NAME=y
+CONFIG_SCHED_CACHE=y
diff --git a/tools/testing/selftests/prctl/sched-cache.c 
b/tools/testing/selftests/prctl/sched-cache.c
new file mode 100644
index 000000000000..6babbf205a0f
--- /dev/null
+++ b/tools/testing/selftests/prctl/sched-cache.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for prctl(PR_SCHED_CACHE): per-process (per-mm) control of
+ * cache aware scheduling.
+ *
+ * The prctl stores attributes on the calling process's mm, so values
+ * are shared by all threads, always inherited over fork(), and
+ * inherited over execve() according to the per-thread
+ * PR_SCHED_CACHE_INHERIT mask.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+
+#ifndef PR_SCHED_CACHE
+#define PR_SCHED_CACHE                         82
+# define PR_SCHED_CACHE_GET                    1
+# define PR_SCHED_CACHE_SET                    2
+# define PR_SCHED_CACHE_ENABLE                 1
+# define PR_SCHED_CACHE_AGGR_TOLERANCE_NR      2
+# define PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE    3
+# define PR_SCHED_CACHE_OVERAGGR_PCT           4
+# define PR_SCHED_CACHE_INHERIT                        5
+# define PR_SCHED_CACHE_DEFAULT                        (-1)
+# define PR_SCHED_CACHE_INHERIT_ENABLE                 (1UL << 0)
+# define PR_SCHED_CACHE_INHERIT_AGGR_TOLERANCE_NR      (1UL << 1)
+# define PR_SCHED_CACHE_INHERIT_AGGR_TOLERANCE_SIZE    (1UL << 2)
+# define PR_SCHED_CACHE_INHERIT_OVERAGGR_PCT           (1UL << 3)
+# define PR_SCHED_CACHE_INHERIT_MASK                   \
+       (PR_SCHED_CACHE_INHERIT_ENABLE |                \
+        PR_SCHED_CACHE_INHERIT_AGGR_TOLERANCE_NR |     \
+        PR_SCHED_CACHE_INHERIT_AGGR_TOLERANCE_SIZE |   \
+        PR_SCHED_CACHE_INHERIT_OVERAGGR_PCT)
+#endif
+
+static int sc_get(unsigned long attr, int *val)
+{
+       if (prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_GET, attr, val, 0))
+               return -errno;
+       return 0;
+}
+
+static int sc_set(unsigned long attr, unsigned long val)
+{
+       if (prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_SET, attr, val, 0))
+               return -errno;
+       return 0;
+}
+
+static int sc_supported(void)
+{
+       int val;
+
+       return sc_get(PR_SCHED_CACHE_ENABLE, &val) == 0;
+}
+
+static void sc_reset_all(void)
+{
+       sc_set(PR_SCHED_CACHE_ENABLE, PR_SCHED_CACHE_DEFAULT);
+       sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, PR_SCHED_CACHE_DEFAULT);
+       sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, PR_SCHED_CACHE_DEFAULT);
+       sc_set(PR_SCHED_CACHE_OVERAGGR_PCT, PR_SCHED_CACHE_DEFAULT);
+       sc_set(PR_SCHED_CACHE_INHERIT, 0);
+}
+
+TEST(get_set_roundtrip)
+{
+       int val;
+
+       if (!sc_supported())
+               SKIP(return, "PR_SCHED_CACHE not supported");
+
+       sc_reset_all();
+
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_ENABLE, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_DEFAULT, val);
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_DEFAULT, val);
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_DEFAULT, val);
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_OVERAGGR_PCT, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_DEFAULT, val);
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_INHERIT, &val));
+       ASSERT_EQ(0, val);
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_ENABLE, 0));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_ENABLE, &val));
+       ASSERT_EQ(0, val);
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_ENABLE, 1));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_ENABLE, &val));
+       ASSERT_EQ(1, val);
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, 7));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, &val));
+       ASSERT_EQ(7, val);
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, 13));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, &val));
+       ASSERT_EQ(13, val);
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_OVERAGGR_PCT, 155));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_OVERAGGR_PCT, &val));
+       ASSERT_EQ(155, val);
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_INHERIT,
+                           PR_SCHED_CACHE_INHERIT_AGGR_TOLERANCE_NR));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_INHERIT, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_INHERIT_AGGR_TOLERANCE_NR, (unsigned long)val);
+
+       /* DEFAULT resets the inherit mask to empty */
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_INHERIT, PR_SCHED_CACHE_DEFAULT));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_INHERIT, &val));
+       ASSERT_EQ(0, val);
+
+       /* both the sign-extended and the 32-bit truncated DEFAULT reset */
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR,
+                           PR_SCHED_CACHE_DEFAULT));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_DEFAULT, val);
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, 0xffffffffUL));
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_DEFAULT, val);
+
+       sc_reset_all();
+}
+
+TEST(invalid_arguments)
+{
+       int val;
+
+       if (!sc_supported())
+               SKIP(return, "PR_SCHED_CACHE not supported");
+
+       sc_reset_all();
+
+       /* out of range values */
+       ASSERT_EQ(-EINVAL, sc_set(PR_SCHED_CACHE_ENABLE, 2));
+       ASSERT_EQ(-EINVAL, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, 101));
+       ASSERT_EQ(-EINVAL, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, 101));
+       ASSERT_EQ(-EINVAL, sc_set(PR_SCHED_CACHE_OVERAGGR_PCT, 1001));
+       ASSERT_EQ(-EINVAL, sc_set(PR_SCHED_CACHE_INHERIT,
+                                 PR_SCHED_CACHE_INHERIT_MASK + 1));
+
+       /* unknown attribute */
+       ASSERT_EQ(-EINVAL, sc_set(0, 1));
+       ASSERT_EQ(-EINVAL, sc_set(PR_SCHED_CACHE_INHERIT + 1, 1));
+       ASSERT_EQ(-EINVAL, sc_get(0, &val));
+       ASSERT_EQ(-EINVAL, sc_get(PR_SCHED_CACHE_INHERIT + 1, &val));
+
+       /* unknown op */
+       errno = 0;
+       ASSERT_EQ(-1, prctl(PR_SCHED_CACHE, 0, PR_SCHED_CACHE_ENABLE, 0, 0));
+       ASSERT_EQ(EINVAL, errno);
+       errno = 0;
+       ASSERT_EQ(-1, prctl(PR_SCHED_CACHE, 3, PR_SCHED_CACHE_ENABLE, 0, 0));
+       ASSERT_EQ(EINVAL, errno);
+
+       /* nonzero unused argument */
+       errno = 0;
+       ASSERT_EQ(-1, prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_SET,
+                           PR_SCHED_CACHE_ENABLE, 1, 1));
+       ASSERT_EQ(EINVAL, errno);
+
+       /* bad GET pointer */
+       errno = 0;
+       ASSERT_EQ(-1, prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_GET,
+                           PR_SCHED_CACHE_ENABLE, NULL, 0));
+       ASSERT_EQ(EFAULT, errno);
+
+       /* values rejected with -EINVAL must not be stored */
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_ENABLE, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_DEFAULT, val);
+}
+
+struct thread_ctx {
+       int nr_seen;
+       int inherit_seen;
+       int ret;
+};
+
+static void *thread_fn(void *arg)
+{
+       struct thread_ctx *ctx = arg;
+
+       ctx->ret = sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, &ctx->nr_seen);
+       if (!ctx->ret)
+               ctx->ret = sc_get(PR_SCHED_CACHE_INHERIT, &ctx->inherit_seen);
+       if (!ctx->ret)
+               ctx->ret = sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, 42);
+       /* clearing this thread's mask must not affect the creator's */
+       if (!ctx->ret)
+               ctx->ret = sc_set(PR_SCHED_CACHE_INHERIT, 0);
+
+       return NULL;
+}
+
+TEST(values_shared_by_threads_inherit_mask_is_not)
+{
+       struct thread_ctx ctx = { .nr_seen = -2, .inherit_seen = -2 };
+       pthread_t thread;
+       int val;
+
+       if (!sc_supported())
+               SKIP(return, "PR_SCHED_CACHE not supported");
+
+       sc_reset_all();
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, 31));
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_INHERIT,
+                           PR_SCHED_CACHE_INHERIT_ENABLE));
+
+       ASSERT_EQ(0, pthread_create(&thread, NULL, thread_fn, &ctx));
+       ASSERT_EQ(0, pthread_join(thread, NULL));
+       ASSERT_EQ(0, ctx.ret);
+
+       /* attribute values live on the shared mm */
+       ASSERT_EQ(31, ctx.nr_seen);
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, &val));
+       ASSERT_EQ(42, val);
+
+       /*
+        * The inherit mask is per thread: the new thread starts with a
+        * copy of its creator's mask, and clearing it in the thread
+        * does not touch the creator's.
+        */
+       ASSERT_EQ(PR_SCHED_CACHE_INHERIT_ENABLE,
+                 (unsigned long)ctx.inherit_seen);
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_INHERIT, &val));
+       ASSERT_EQ(PR_SCHED_CACHE_INHERIT_ENABLE, (unsigned long)val);
+
+       sc_reset_all();
+}
+
+TEST(fork_always_inherits)
+{
+       pid_t pid;
+       int status;
+       int val;
+
+       if (!sc_supported())
+               SKIP(return, "PR_SCHED_CACHE not supported");
+
+       sc_reset_all();
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_ENABLE, 1));
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, 9));
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, 11));
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_OVERAGGR_PCT, 77));
+
+       pid = fork();
+       ASSERT_LE(0, pid);
+       if (pid == 0) {
+               int val;
+
+               if (sc_get(PR_SCHED_CACHE_ENABLE, &val) || val != 1)
+                       _exit(1);
+               if (sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, &val) || val != 9)
+                       _exit(2);
+               if (sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, &val) || val != 
11)
+                       _exit(3);
+               if (sc_get(PR_SCHED_CACHE_OVERAGGR_PCT, &val) || val != 77)
+                       _exit(4);
+
+               /* changes in the child must not leak back to the parent */
+               if (sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, 50))
+                       _exit(5);
+
+               _exit(0);
+       }
+
+       ASSERT_EQ(pid, waitpid(pid, &status, 0));
+       ASSERT_TRUE(WIFEXITED(status));
+       ASSERT_EQ(0, WEXITSTATUS(status));
+
+       ASSERT_EQ(0, sc_get(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, &val));
+       ASSERT_EQ(9, val);
+
+       sc_reset_all();
+}
+
+static int exec_check(int expect_enable, int expect_nr,
+                     int expect_size, int expect_pct)
+{
+       char enable[16], nr[16], size[16], pct[16];
+       pid_t pid;
+       int status;
+
+       snprintf(enable, sizeof(enable), "%d", expect_enable);
+       snprintf(nr, sizeof(nr), "%d", expect_nr);
+       snprintf(size, sizeof(size), "%d", expect_size);
+       snprintf(pct, sizeof(pct), "%d", expect_pct);
+
+       pid = fork();
+       if (pid < 0)
+               return -1;
+
+       if (pid == 0) {
+               setenv("SCHED_CACHE_EXEC_MODE", "1", 1);
+               setenv("SCHED_CACHE_EXPECT_ENABLE", enable, 1);
+               setenv("SCHED_CACHE_EXPECT_NR", nr, 1);
+               setenv("SCHED_CACHE_EXPECT_SIZE", size, 1);
+               setenv("SCHED_CACHE_EXPECT_PCT", pct, 1);
+               execl("/proc/self/exe", "sched-cache", NULL);
+               _exit(126);
+       }
+
+       if (waitpid(pid, &status, 0) != pid)
+               return -1;
+       if (!WIFEXITED(status))
+               return -1;
+
+       return WEXITSTATUS(status);
+}
+
+TEST(execve_inheritance)
+{
+       if (!sc_supported())
+               SKIP(return, "PR_SCHED_CACHE not supported");
+
+       sc_reset_all();
+
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_ENABLE, 1));
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_NR, 21));
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE, 22));
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_OVERAGGR_PCT, 23));
+
+       /* without inherit flags execve() resets everything */
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_INHERIT, 0));
+       ASSERT_EQ(0, exec_check(PR_SCHED_CACHE_DEFAULT,
+                               PR_SCHED_CACHE_DEFAULT,
+                               PR_SCHED_CACHE_DEFAULT,
+                               PR_SCHED_CACHE_DEFAULT));
+
+       /* selected attributes survive execve() */
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_INHERIT,
+                           PR_SCHED_CACHE_INHERIT_AGGR_TOLERANCE_NR |
+                           PR_SCHED_CACHE_INHERIT_OVERAGGR_PCT));
+       ASSERT_EQ(0, exec_check(PR_SCHED_CACHE_DEFAULT, 21,
+                               PR_SCHED_CACHE_DEFAULT, 23));
+
+       /* all of them */
+       ASSERT_EQ(0, sc_set(PR_SCHED_CACHE_INHERIT,
+                           PR_SCHED_CACHE_INHERIT_MASK));
+       ASSERT_EQ(0, exec_check(1, 21, 22, 23));
+
+       sc_reset_all();
+}
+
+static int exec_expect(const char *env, unsigned long attr)
+{
+       const char *str = getenv(env);
+       int val;
+
+       if (!str)
+               return 1;
+       if (sc_get(attr, &val))
+               return 1;
+       if (val != atoi(str))
+               return 1;
+
+       return 0;
+}
+
+static int exec_check_main(void)
+{
+       int bad = 0;
+
+       bad |= exec_expect("SCHED_CACHE_EXPECT_ENABLE", PR_SCHED_CACHE_ENABLE);
+       bad |= exec_expect("SCHED_CACHE_EXPECT_NR",
+                          PR_SCHED_CACHE_AGGR_TOLERANCE_NR);
+       bad |= exec_expect("SCHED_CACHE_EXPECT_SIZE",
+                          PR_SCHED_CACHE_AGGR_TOLERANCE_SIZE);
+       bad |= exec_expect("SCHED_CACHE_EXPECT_PCT",
+                          PR_SCHED_CACHE_OVERAGGR_PCT);
+
+       return bad;
+}
+
+int main(int argc, char **argv)
+{
+       if (getenv("SCHED_CACHE_EXEC_MODE"))
+               return exec_check_main();
+
+       return test_harness_run(argc, argv);
+}
-- 
2.47.3


Reply via email to