On Tue, 6 Oct 2026 17:56:40 +0900 Hajime Tazaki <[email protected]> wrote:
> This patchset adds an ability to execute kselftest on nommu platforms.
>
> Currently there are several issues if we wish to run kselftests on nommu
> targets:
>
> - it cannot compile/build test binaries because the current files mainly
> assume to build with glibc,
> - some of the tests are not able to run on nommu targets as there are no
> fork(2) syscall.
>
> The first issue can be avoided if we can build static PIE binaries (if
> targets support it), but in our case (build on ubuntu/glibc and run on
> alpine/musl-libc), it fails to invoke due to lack of the GNU ifunc
> mechanism. Thus, we need to cross-compile with musl toolchain, which
> needs to be solved the first issue.
>
> The second issue is the lack of fork(2) syscall on those platforms.
> Especially the test harness helper (kselftest_harness.h) uses the
> syscall, which cannot be simply with vfork(2). `timeout` command used
> in `runner.sh` never works for nommu platform as it uses fork(2).
>
> nommu component in the mm subsystem has several known issues and having
> test cases should help this situation, thus this patchset is very first
> step toward enriching test environment which has not been well tested
> for a while. The test cases is implemented based on the document
> (Documentation/admin-guide/mm/nommu-mmap.rst).
>
> Initial shape for this patches is implemented as an opt-in TARGET, not
> built/invoked automatically if users don't specify this nommu target.
Thanks, I've updated mm.git's mm-unstable branch with this version.
Then I moved the series down into the tail of the mm-unstable branch,
in preparation for excluding it from the 7.3-rcX queue.
It's changing a lot and it isn't converging quickly and review hasn't
happened and everyone's traveling so I'll likely hold this series over
for 7.4-rcX.
Hang in there - it's great that you're caring for nommu!
> --
That should be --- (in the patches too).
<grabs the v4->v5 logging from the individual patches>
> v4 => v5
> - remove build-time selection and detect NOMMU at runtime
> - remove #ifdef NOMMU and use a global variable detected from /proc/meminfo
Here's how v5 altered mm.git:
Documentation/dev-tools/kselftest.rst | 18 +-
tools/testing/selftests/kselftest/runner.sh | 9 -
tools/testing/selftests/nommu/Makefile | 1
tools/testing/selftests/nommu/local.mk | 7
tools/testing/selftests/nommu/nommu.h | 33 ++++
tools/testing/selftests/nommu/nommu_mmap_test.c | 91 ++++++------
tools/testing/selftests/nommu/nommu_mremap_test.c | 72 ++++-----
7 files changed, 129 insertions(+), 102 deletions(-)
--- a/Documentation/dev-tools/kselftest.rst~b
+++ a/Documentation/dev-tools/kselftest.rst
@@ -233,16 +233,20 @@ section::
Build and test on nommu target
==============================
-If you (cross-)build kselftests for nommu targets, or run tests on nommu
targets, use
-``NOMMU=1`` as a make variable/environment setting to tell build system to do
the additional
-checks. These nommu targets may differ in several ways, such as not
supporting fork(2) or
-using musl or another libc. Set this variable to apply the necessary build
and test adjustments.
+The nommu selftests detect NOMMU at runtime by checking for the ``MmapCopy``
+entry in ``/proc/meminfo``. Ensure procfs is mounted at ``/proc`` before
+building and running the tests.
::
- $ make ARCH=um NOMMU=1 O=build kselftest-all TARGETS=nommu # <= build-only
- $ make ARCH=um NOMMU=1 O=build kselftest-install TARGETS=nommu
- $ NOMMU=1 ./build/kselftest/kselftest_install/run_kselftest.sh -p -c nommu
+ $ make ARCH=um O=build kselftest-all TARGETS=nommu # <= build-only
+ $ make ARCH=um O=build kselftest-install TARGETS=nommu
+ $ ./build/kselftest/kselftest_install/run_kselftest.sh -p -c nommu
+
+The C tests report a skip if ``/proc/meminfo`` is unavailable. On a NOMMU
+system, the runner needs that file to avoid wrapping the tests in ``timeout``
+before they can report a skip. Mounting procfs is therefore a prerequisite
+for running this target through the kselftest runner.
Contributing new tests
======================
--- a/tools/testing/selftests/kselftest/runner.sh~b
+++ a/tools/testing/selftests/kselftest/runner.sh
@@ -36,14 +36,19 @@ tap_prefix()
fi
}
+is_nommu()
+{
+ grep -q '^MmapCopy:' /proc/meminfo 2>/dev/null
+}
+
tap_timeout()
{
# nommu doesn't support timeout command (missing fork(2))
- if [ "$NOMMU" = "1" ] ; then
+ if is_nommu; then
echo "timeout isn't supported for NOMMU"
$1
# Make sure tests will time out if utility is available.
- elif [ -x /usr/bin/timeout ] ; then
+ elif [ -x /usr/bin/timeout ]; then
/usr/bin/timeout --foreground "$kselftest_timeout" \
/usr/bin/timeout "$kselftest_timeout" $1
else
diff --git a/tools/testing/selftests/nommu/local.mk
a/tools/testing/selftests/nommu/local.mk
deleted file mode 100644
--- a/tools/testing/selftests/nommu/local.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-# detect if users request NOMMU build or not
-# User can set NOMMU to 1 to build/test for NOMMU platforms
-NOMMU ?= 0
-ifeq ($(NOMMU),1)
-CFLAGS += -DNOMMU
-export NOMMU
-endif
--- a/tools/testing/selftests/nommu/Makefile~b
+++ a/tools/testing/selftests/nommu/Makefile
@@ -5,4 +5,3 @@ TEST_GEN_PROGS += nommu_mmap_test
TEST_GEN_PROGS += nommu_mremap_test
include ../lib.mk
-include local.mk
diff --git a/tools/testing/selftests/nommu/nommu.h
a/tools/testing/selftests/nommu/nommu.h
new file mode 100644
--- /dev/null
+++ a/tools/testing/selftests/nommu/nommu.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef NOMMU_H
+#define NOMMU_H
+
+#include <stdio.h>
+#include <string.h>
+
+/* Returns 1 for NOMMU, 0 for MMU, and -1 if /proc/meminfo is unavailable. */
+static inline int ksft_is_nommu(void)
+{
+ FILE *fp;
+ char line[256];
+ int nommu = 0;
+
+ fp = fopen("/proc/meminfo", "r");
+ if (!fp)
+ return -1;
+
+ while (fgets(line, sizeof(line), fp)) {
+ if (strncmp(line, "MmapCopy:", sizeof("MmapCopy:") - 1) == 0) {
+ nommu = 1;
+ break;
+ }
+ }
+
+ if (ferror(fp))
+ nommu = -1;
+ fclose(fp);
+
+ return nommu;
+}
+
+#endif
--- a/tools/testing/selftests/nommu/nommu_mmap_test.c~b
+++ a/tools/testing/selftests/nommu/nommu_mmap_test.c
@@ -9,6 +9,7 @@
#include <string.h>
#include <limits.h>
#include "kselftest.h"
+#include "nommu.h"
#include <sys/vfs.h>
#ifndef RAMFS_MAGIC
@@ -16,6 +17,7 @@
#endif
static size_t ps;
+static int nommu;
struct test_case_t {
const char *name;
@@ -31,6 +33,9 @@ static int get_shm_expected_error(const
{
struct statfs fs;
+ if (!nommu)
+ return 0;
+
if (statfs(path, &fs) == 0) {
if (fs.f_type == RAMFS_MAGIC)
return 0; /* ramfs succeed with contiguous memory */
@@ -74,11 +79,7 @@ static struct test_case_t test_cases[] =
.mmap_prot = PROT_READ | PROT_WRITE,
.mmap_flags = MAP_SHARED,
.exp_err = 0,
-#ifdef NOMMU
.resolve_exp_err = get_shm_expected_error,
-#else
- .resolve_exp_err = NULL,
-#endif
},
{
.name = "non-anonymous shared file mapping (r--)",
@@ -87,18 +88,14 @@ static struct test_case_t test_cases[] =
.mmap_prot = PROT_READ,
.mmap_flags = MAP_SHARED,
.exp_err = 0,
-#ifdef NOMMU
.resolve_exp_err = get_shm_expected_error,
-#else
- .resolve_exp_err = 0,
-#endif
},
};
static int run_mapping_matrix_test(struct test_case_t *tcase)
{
int fd;
- void *ptr;
+ void *ptr = MAP_FAILED;
char path_buf[PATH_MAX];
const char *path = tcase->pathname;
int rc = KSFT_PASS;
@@ -120,10 +117,8 @@ static int run_mapping_matrix_test(struc
}
if (ftruncate(fd, ps) != 0) {
ksft_print_msg("ftruncate failed for: %s\n",
tcase->pathname);
- ksft_test_result_fail("%s\n", tcase->name);
- close(fd);
- unlink(path_buf);
- return KSFT_FAIL;
+ rc = KSFT_FAIL;
+ goto cleanup;
}
path = path_buf;
} else {
@@ -146,41 +141,42 @@ static int run_mapping_matrix_test(struc
if (ptr != MAP_FAILED) {
ksft_print_msg("mmap unexpectedly succeeded (exp error
%d)\n",
expected_error);
- ksft_test_result_fail("%s\n", tcase->name);
- munmap(ptr, ps);
rc = KSFT_FAIL;
goto cleanup;
}
if (errno != expected_error) {
ksft_print_msg("mmap failed with %d (%s), but expected
%d\n",
errno, strerror(errno), expected_error);
- ksft_test_result_fail("%s\n", tcase->name);
rc = KSFT_FAIL;
goto cleanup;
}
ksft_print_msg("Correctly rejected with expected error
%s(%d)\n",
strerror(expected_error), expected_error);
- ksft_test_result_pass("%s\n", tcase->name);
rc = KSFT_PASS;
goto cleanup;
}
if (ptr == MAP_FAILED) {
ksft_print_msg("mmap failed unexpectedly: %s\n",
strerror(errno));
- ksft_test_result_fail("%s\n", tcase->name);
rc = KSFT_FAIL;
goto cleanup;
}
- ksft_test_result_pass("%s\n", tcase->name);
- munmap(ptr, ps);
-
cleanup:
+ if (ptr != MAP_FAILED && munmap(ptr, ps)) {
+ ksft_print_msg("munmap failed: %s\n", strerror(errno));
+ rc = KSFT_FAIL;
+ }
if (fd >= 0) {
close(fd);
- if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
- unlink(path_buf);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX") &&
+ unlink(path_buf)) {
+ ksft_print_msg("unlink failed for %s: %s\n",
+ path_buf, strerror(errno));
+ rc = KSFT_FAIL;
+ }
}
+ ksft_test_result_report(rc, "%s\n", tcase->name);
return rc;
}
@@ -210,48 +206,55 @@ static int test_map_fixed(void)
ptr = mmap(fixed_addr, ps, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
-#ifdef NOMMU
- if (ptr == MAP_FAILED && (errno == ENODEV || errno == EINVAL)) {
- ksft_print_msg("MAP_FIXED correctly rejected under nommu\n");
- ksft_test_result_pass("MAP_FIXED behavior\n");
- return KSFT_PASS;
- }
- if (ptr != MAP_FAILED) {
- ksft_print_msg("MAP_FIXED unexpectedly allowed under nommu\n");
+ if (nommu) {
+ if (ptr == MAP_FAILED && (errno == ENODEV || errno == EINVAL)) {
+ ksft_print_msg("MAP_FIXED correctly rejected under
nommu\n");
+ ksft_test_result_pass("MAP_FIXED behavior\n");
+ return KSFT_PASS;
+ }
+ if (ptr != MAP_FAILED) {
+ ksft_print_msg("MAP_FIXED unexpectedly allowed under
nommu\n");
+ ksft_test_result_fail("MAP_FIXED behavior\n");
+ if (munmap(ptr, ps))
+ ksft_print_msg("munmap failed: %s\n",
strerror(errno));
+ return KSFT_FAIL;
+ }
+ ksft_print_msg("MAP_FIXED failed under NOMMU: %s\n",
+ strerror(errno));
ksft_test_result_fail("MAP_FIXED behavior\n");
- munmap(ptr, ps);
return KSFT_FAIL;
}
- ksft_print_msg("MAP_FIXED failed under NOMMU: %s\n",
- strerror(errno));
- ksft_test_result_fail("MAP_FIXED behavior\n");
- return KSFT_FAIL;
-#else
if (ptr != MAP_FAILED) {
ksft_print_msg("MAP_FIXED successfully allocated under MMU\n");
+ if (munmap(ptr, ps)) {
+ ksft_print_msg("munmap failed: %s\n", strerror(errno));
+ ksft_test_result_fail("MAP_FIXED behavior\n");
+ return KSFT_FAIL;
+ }
ksft_test_result_pass("MAP_FIXED behavior\n");
- munmap(ptr, ps);
return KSFT_PASS;
}
ksft_print_msg("MAP_FIXED failed allocation under MMU\n");
ksft_test_result_fail("MAP_FIXED behavior\n");
return KSFT_FAIL;
-#endif
}
int main(int argc, char **argv)
{
int i;
+ nommu = ksft_is_nommu();
+ if (nommu < 0)
+ ksft_exit_skip("Cannot read /proc/meminfo; mount procfs at
/proc\n");
+
ps = sysconf(_SC_PAGESIZE);
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(test_cases) + 1);
-#ifdef NOMMU
- ksft_print_msg("Running strict MMAP test criteria under nommu
architecture\n");
-#else
- ksft_print_msg("Running MMAP test criteria under MMU architecture\n");
-#endif
+ if (nommu)
+ ksft_print_msg("Running strict MMAP test criteria under nommu
architecture\n");
+ else
+ ksft_print_msg("Running MMAP test criteria under MMU
architecture\n");
test_map_fixed();
for (i = 0; i < (int)ARRAY_SIZE(test_cases); i++)
--- a/tools/testing/selftests/nommu/nommu_mremap_test.c~b
+++ a/tools/testing/selftests/nommu/nommu_mremap_test.c
@@ -9,6 +9,7 @@
#include <string.h>
#include <limits.h>
#include "kselftest.h"
+#include "nommu.h"
#include <sys/vfs.h>
#ifndef RAMFS_MAGIC
@@ -16,6 +17,7 @@
#endif
static size_t ps;
+static int nommu;
static long get_fs_type(const char *path)
{
@@ -45,8 +47,7 @@ static void munmap_shrink_test(void)
if (ret != 0) {
ksft_print_msg("memory %p isn't unmapped at %p\n",
addr, (char *)addr + ps * i);
- ksft_test_result_fail("munmap shrink\n");
- return;
+ goto error;
}
if (i == 0) {
@@ -67,8 +68,10 @@ static void munmap_shrink_test(void)
ksft_test_result_pass("munmap shrink\n");
return;
error:
- for (int j = 0; j < 4; j++)
- munmap((char *)addr + j * ps, ps);
+ for (int j = 0; j < 4; j++) {
+ if (munmap((char *)addr + j * ps, ps))
+ ksft_print_msg("cleanup munmap failed: %s\n",
strerror(errno));
+ }
ksft_print_msg("clean up failures\n");
ksft_test_result_fail("munmap shrink\n");
}
@@ -132,7 +135,7 @@ static void mremap_shrink_test(void)
static int get_shared_writable_file_expected_error(const char *path)
{
- if (get_fs_type(path) == RAMFS_MAGIC)
+ if (nommu && get_fs_type(path) == RAMFS_MAGIC)
return EPERM; /* ramfs failed */
return 0;
@@ -145,6 +148,7 @@ struct mremap_case_t {
int mmap_prot;
int mmap_flags;
int exp_err;
+ int nommu_exp_err;
int (*resolve_exp_err)(const char *path);
unsigned int old_pages;
unsigned int new_pages;
@@ -167,11 +171,7 @@ static struct mremap_case_t mremap_cases
.mmap_prot = PROT_READ,
.mmap_flags = MAP_SHARED,
.exp_err = 0,
-#ifdef NOMMU
.resolve_exp_err = get_shared_writable_file_expected_error,
-#else
- .resolve_exp_err = 0,
-#endif
},
{
.name = "private file unchanged length (r-)",
@@ -179,11 +179,8 @@ static struct mremap_case_t mremap_cases
.open_flags = O_CREAT | O_RDWR | O_EXCL,
.mmap_prot = PROT_READ,
.mmap_flags = MAP_PRIVATE,
-#ifdef NOMMU
- .exp_err = EPERM,
-#else
.exp_err = 0,
-#endif
+ .nommu_exp_err = EPERM,
.resolve_exp_err = 0,
.old_pages = 4,
.new_pages = 4,
@@ -205,11 +202,8 @@ static struct mremap_case_t mremap_cases
.open_flags = O_CREAT | O_RDWR | O_EXCL,
.mmap_prot = PROT_READ,
.mmap_flags = MAP_PRIVATE,
-#ifdef NOMMU
- .exp_err = EPERM,
-#else
.exp_err = 0,
-#endif
+ .nommu_exp_err = EPERM,
.resolve_exp_err = 0,
.old_pages = 4,
.new_pages = 8,
@@ -220,11 +214,8 @@ static struct mremap_case_t mremap_cases
.open_flags = O_CREAT | O_RDWR | O_EXCL,
.mmap_prot = PROT_READ | PROT_WRITE,
.mmap_flags = MAP_PRIVATE,
-#ifdef NOMMU
- .exp_err = ENOMEM,
-#else
.exp_err = 0,
-#endif
+ .nommu_exp_err = ENOMEM,
.resolve_exp_err = 0,
.old_pages = 4,
.new_pages = 8,
@@ -257,22 +248,17 @@ static int run_mremap_test(struct mremap
}
if (ftruncate(fd, ps * file_pages) != 0) {
ksft_print_msg("Failed to setup file backing\n");
- ksft_test_result_fail("%s\n", tcase->name);
- close(fd);
- unlink(pb);
- return KSFT_FAIL;
+ rc = KSFT_FAIL;
+ goto out;
}
-#ifdef NOMMU
- if ((tcase->mmap_flags & MAP_SHARED) && get_fs_type(pb) !=
RAMFS_MAGIC) {
+ if (nommu && (tcase->mmap_flags & MAP_SHARED) &&
+ get_fs_type(pb) != RAMFS_MAGIC) {
ksft_print_msg("Skip the test under non-ramfs
filesystem (%s)\n",
pb);
- ksft_test_result_skip("%s\n", tcase->name);
- close(fd);
- unlink(pb);
- return KSFT_SKIP;
+ rc = KSFT_SKIP;
+ goto out;
}
-#endif
path = pb;
} else if (tcase->pathname) {
fd = open(tcase->pathname, tcase->open_flags, 0600);
@@ -282,16 +268,13 @@ static int run_mremap_test(struct mremap
return KSFT_SKIP;
}
-#ifdef NOMMU
- if ((tcase->mmap_flags & MAP_SHARED) &&
+ if (nommu && (tcase->mmap_flags & MAP_SHARED) &&
get_fs_type(tcase->pathname) != RAMFS_MAGIC) {
ksft_print_msg("Skip the test under non-ramfs
filesystem (%s)\n",
tcase->pathname);
- ksft_test_result_skip("%s\n", tcase->name);
- close(fd);
- return KSFT_SKIP;
+ rc = KSFT_SKIP;
+ goto out;
}
-#endif
}
addr = mmap(NULL, ps * old_pages, tcase->mmap_prot,
@@ -302,7 +285,7 @@ static int run_mremap_test(struct mremap
goto out;
}
- expected_error = tcase->exp_err;
+ expected_error = nommu ? tcase->nommu_exp_err : tcase->exp_err;
if (tcase->resolve_exp_err && fd >= 0)
expected_error = tcase->resolve_exp_err(path);
@@ -340,8 +323,11 @@ static int run_mremap_test(struct mremap
out:
if (fd >= 0) {
close(fd);
- if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
- unlink(pb);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX") &&
+ unlink(pb)) {
+ ksft_print_msg("unlink failed for %s: %s\n", pb,
strerror(errno));
+ rc = KSFT_FAIL;
+ }
}
ksft_test_result_report(rc, "%s\n", tcase->name);
@@ -352,6 +338,10 @@ int main(int argc, char **argv)
{
int i;
+ nommu = ksft_is_nommu();
+ if (nommu < 0)
+ ksft_exit_skip("Cannot read /proc/meminfo; mount procfs at
/proc\n");
+
ps = sysconf(_SC_PAGESIZE);
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(mremap_cases) + 2);
_