Fork a process that maps a device DAX instance with a PUD, in a VMA
whose page tables fork copies because it has a guard region. The child
must account the copied PUD in its RssFile.

Without a reference, an rmap and a counter for the copied PUD, the
child's RssFile misses the PUD, and its exit leaves the child's file
counter negative: "Bad rss-counter state".

It runs only on a device named in argv[1] or $FORK_DAX_PUD_DEVICE: on
a kernel without the fix it leaves the device folio's mapcount below
zero.

Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <[email protected]>
---
 MAINTAINERS                               |   1 +
 tools/testing/selftests/mm/Makefile       |   1 +
 tools/testing/selftests/mm/fork_dax_pud.c | 136 ++++++++++++++++++++++++++++++
 tools/testing/selftests/mm/run_vmtests.sh |   1 +
 4 files changed, 139 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 7b5cab72d851b..48653704007f3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17481,6 +17481,7 @@ F:      include/trace/events/huge_memory.h
 F:     mm/huge_memory.c
 F:     mm/khugepaged.c
 F:     mm/mm_slot.h
+F:     tools/testing/selftests/mm/fork_dax_pud.c
 F:     tools/testing/selftests/mm/khugepaged.c
 F:     tools/testing/selftests/mm/split_huge_page_test.c
 F:     tools/testing/selftests/mm/transhuge-stress.c
diff --git a/tools/testing/selftests/mm/Makefile 
b/tools/testing/selftests/mm/Makefile
index 2d5366196e309..86914aaf8e6ad 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -104,6 +104,7 @@ TEST_GEN_FILES += guard-regions
 TEST_GEN_FILES += merge
 TEST_GEN_FILES += rmap
 TEST_GEN_FILES += folio_split_race_test
+TEST_GEN_FILES += fork_dax_pud
 
 ifneq ($(ARCH),arm64)
 TEST_GEN_FILES += soft-dirty
diff --git a/tools/testing/selftests/mm/fork_dax_pud.c 
b/tools/testing/selftests/mm/fork_dax_pud.c
new file mode 100644
index 0000000000000..1fcf46341ace2
--- /dev/null
+++ b/tools/testing/selftests/mm/fork_dax_pud.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * fork() of a process that maps a device DAX instance with a PUD, in a VMA
+ * whose page tables fork copies because it has a guard region. The child
+ * must account the copied PUD in its RssFile.
+ *
+ * Runs only on a device named in argv[1] or $FORK_DAX_PUD_DEVICE that
+ * nothing else maps, with 1 GiB alignment and at least 2 GiB. To make one
+ * on pmem emulated in RAM, with CONFIG_X86_PMEM_LEGACY and
+ * CONFIG_DEV_DAX_PMEM:
+ *
+ *     boot with memmap=3G!4G
+ *     ndctl create-namespace -f -e namespace0.0 -m devdax -a 1G
+ *     FORK_DAX_PUD_DEVICE=/dev/dax0.0 ./fork_dax_pud
+ */
+#define _GNU_SOURCE
+#include <asm-generic/mman.h> /* Force the import of the tools version. */
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include "kselftest.h"
+#include "vm_util.h"
+
+#define SZ_1G          (1UL << 30)
+#define DAX_SYSFS      "/sys/bus/dax/devices"
+
+static sigjmp_buf sigbus_jmp;
+
+static void sigbus_handler(int sig)
+{
+       siglongjmp(sigbus_jmp, 1);
+}
+
+static unsigned long sysfs_ul(const char *dev, const char *attr)
+{
+       char path[PATH_MAX], buf[64];
+       unsigned long val = 0;
+       FILE *f;
+
+       snprintf(path, sizeof(path), DAX_SYSFS "/%s/%s", dev, attr);
+       f = fopen(path, "r");
+       if (!f)
+               return 0;
+       if (fgets(buf, sizeof(buf), f))
+               val = strtoul(buf, NULL, 0);
+       fclose(f);
+       return val;
+}
+
+static bool dax_dev_ok(const char *path)
+{
+       const char *name = strrchr(path, '/');
+
+       name = name ? name + 1 : path;
+       return sysfs_ul(name, "align") == SZ_1G &&
+              sysfs_ul(name, "size") >= 2 * SZ_1G;
+}
+
+static long rss_file_kb(void)
+{
+       char line[256];
+       long kb = -1;
+       FILE *f = fopen("/proc/self/status", "r");
+
+       if (!f)
+               return -1;
+       while (fgets(line, sizeof(line), f))
+               if (sscanf(line, "RssFile: %ld", &kb) == 1)
+                       break;
+       fclose(f);
+       return kb;
+}
+
+int main(int argc, char **argv)
+{
+       const char *path = argc > 1 ? argv[1] : getenv("FORK_DAX_PUD_DEVICE");
+       char *area, *mem;
+       int fd, status;
+       pid_t pid;
+
+       ksft_print_header();
+       if (!path)
+               ksft_exit_skip("no devdax device given\n");
+       if (!dax_dev_ok(path))
+               ksft_exit_skip("%s: needs 1 GiB alignment and 2 GiB\n", path);
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               ksft_exit_skip("%s: %s\n", path, strerror(errno));
+       area = mmap(NULL, 3 * SZ_1G, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 
-1, 0);
+       if (area == MAP_FAILED)
+               ksft_exit_fail_perror("mmap");
+       mem = (char *)(((unsigned long)area + SZ_1G - 1) & ~(SZ_1G - 1));
+       mem = mmap(mem, 2 * SZ_1G, PROT_READ, MAP_SHARED | MAP_FIXED, fd, 0);
+       if (mem == MAP_FAILED)
+               ksft_exit_skip("mmap %s: %s\n", path, strerror(errno));
+
+       /* A PUD in the first GiB, a guard page at the end of the second. */
+       signal(SIGBUS, sigbus_handler);
+       if (sigsetjmp(sigbus_jmp, 1))
+               ksft_exit_skip("%s cannot be mapped with a PUD\n", path);
+       FORCE_READ(mem[0]);
+       signal(SIGBUS, SIG_DFL);
+       if (madvise(mem + 2 * SZ_1G - getpagesize(), getpagesize(),
+                   MADV_GUARD_INSTALL))
+               ksft_exit_skip("MADV_GUARD_INSTALL: %s\n", strerror(errno));
+
+       ksft_set_plan(1);
+       fflush(stdout);
+       pid = fork();
+       if (pid < 0)
+               ksft_exit_fail_perror("fork");
+       if (!pid)
+               _exit(rss_file_kb() < (long)(SZ_1G >> 10) ? 10 : 0);
+       if (waitpid(pid, &status, 0) != pid)
+               ksft_exit_fail_perror("waitpid");
+
+       if (!WIFEXITED(status) || WEXITSTATUS(status))
+               ksft_test_result_fail("child does not account the copied 
PUD\n");
+       else
+               ksft_test_result_pass("devdax PUD copied by fork\n");
+
+       munmap(area, 3 * SZ_1G);
+       close(fd);
+       ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh 
b/tools/testing/selftests/mm/run_vmtests.sh
index d09f9f6a384ee..7294e1334d1da 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -445,6 +445,7 @@ fi
 CATEGORY="thp" run_test ./transhuge-stress -d 20
 
 CATEGORY="thp" run_test ./folio_split_race_test
+CATEGORY="thp" run_test ./fork_dax_pud
 
 CATEGORY="migration" run_test ./migration
 

-- 
2.43.0


Reply via email to