[LTP] [RFC PATCH] syscalls/madvise: Check DONTNEED across an unmapped hole

Wenyuan Li 2063309626@qq.com
Fri Oct 30 13:41:55 CET 2026


madvise(2) documents that Linux applies the advice to the mapped parts of
a range containing unmapped pages, while returning ENOMEM for the request.
For a private anonymous mapping, MADV_DONTNEED should therefore discard
the contents of the mapped portions on both sides of a hole.

These tests do not cover that combination. madvise02 checks the ENOMEM
return for MADV_NORMAL and MADV_WILLNEED on a shared file mapping whose
final page has been unmapped; it does not check MADV_DONTNEED, and it does
not look at the contents of the mapped portions after the call. madvise03
checks zero-fill-on-demand for MADV_DONTNEED on a fully mapped range.
Neither checks the contents of private anonymous pages on both sides of
an unmapped hole after MADV_DONTNEED returns ENOMEM.

Use eight pages with distinct nonzero byte patterns and advise pages 2
through 4. Run once with all pages mapped and once with page 3 unmapped.
Check the return value, zero-filled target pages, unchanged data outside
the range, and preservation of the hole. Use mincore() to check the hole
without dereferencing it, not to infer physical-page reclamation.

Allocate a fresh mapping for each case, use the runtime page size, and
release the complete region in cleanup, including the case with a hole.
The test does not require root privileges.

Tested on x86_64, page size 4096, gcc 13.3.0, kernel 6.8.0: madvise13
18 TPASS (three consecutive runs), 36 TPASS with -i2, 1800 TPASS with
-i100; madvise03 1 TPASS on the same kernel.

Link: https://man7.org/linux/man-pages/man2/madvise.2.html
Signed-off-by: Wenyuan Li <2063309626@qq.com>
---
 runtest/syscalls                              |   1 +
 testcases/kernel/syscalls/madvise/.gitignore  |   1 +
 testcases/kernel/syscalls/madvise/madvise13.c | 145 ++++++++++++++++++
 3 files changed, 147 insertions(+)
 create mode 100644 testcases/kernel/syscalls/madvise/madvise13.c

diff --git a/runtest/syscalls b/runtest/syscalls
index 4d3f53e..7e255c3 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1094,6 +1094,7 @@ madvise09 madvise09
 madvise10 madvise10
 madvise11 madvise11
 madvise12 madvise12
+madvise13 madvise13
 
 newuname01 newuname01
 
diff --git a/testcases/kernel/syscalls/madvise/.gitignore b/testcases/kernel/syscalls/madvise/.gitignore
index 758e601..3e7dbe6 100644
--- a/testcases/kernel/syscalls/madvise/.gitignore
+++ b/testcases/kernel/syscalls/madvise/.gitignore
@@ -9,3 +9,4 @@
 /madvise10
 /madvise11
 /madvise12
+/madvise13
diff --git a/testcases/kernel/syscalls/madvise/madvise13.c b/testcases/kernel/syscalls/madvise/madvise13.c
new file mode 100644
index 0000000..eed99dc
--- /dev/null
+++ b/testcases/kernel/syscalls/madvise/madvise13.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Wenyuan Li <2063309626@qq.com>
+ */
+
+/*\
+ * Verify MADV_DONTNEED on a private anonymous range containing an unmapped
+ * page. The call must return ENOMEM but still discard the mapped pages on
+ * both sides of the hole, without changing data outside the requested range.
+ * Also test the same request with the middle page mapped.
+ *
+ * madvise(2), VERSIONS/Linux, specifies that Linux applies the advice to
+ * the mapped parts of a range containing holes, even though it returns
+ * ENOMEM.
+ */
+
+#include <sys/mman.h>
+#include "tst_test.h"
+#include "lapi/mmap.h"
+
+#define PAGES 8
+#define HOLE_PAGE 3
+#define FIRST_PAGE 2
+#define LAST_PAGE 4
+
+static unsigned char *addr;
+static size_t page_size;
+
+static void cleanup(void)
+{
+	if (addr) {
+		SAFE_MUNMAP(addr, PAGES * page_size);
+		addr = NULL;
+	}
+}
+
+static void check_page(unsigned int page, unsigned char expected, int prepare)
+{
+	unsigned char vec;
+	unsigned char *ptr = addr + page * page_size;
+	size_t i;
+
+	TEST(mincore(ptr, page_size, &vec));
+	if (TST_RET == -1) {
+		if (prepare || TST_ERR != ENOMEM)
+			tst_brk(TBROK | TTERRNO, "mincore() on page %u", page);
+
+		tst_res(TFAIL, "Page %u is no longer mapped", page);
+		return;
+	}
+
+	for (i = 0; i < page_size; i++) {
+		if (ptr[i] == expected)
+			continue;
+
+		if (prepare)
+			tst_brk(TBROK, "Page %u was not initialized", page);
+
+		tst_res(TFAIL, "Page %u byte %zu: expected %#x, got %#x",
+			page, i, expected, ptr[i]);
+		return;
+	}
+
+	if (!prepare)
+		tst_res(TPASS, "Page %u contains only %#x", page, expected);
+}
+
+static void check_hole(int prepare)
+{
+	unsigned char vec;
+
+	TEST(mincore(addr + HOLE_PAGE * page_size, page_size, &vec));
+	if (TST_RET == -1 && TST_ERR == ENOMEM) {
+		if (!prepare)
+			tst_res(TPASS, "The hole remains unmapped");
+		return;
+	}
+
+	if (TST_RET == -1)
+		tst_brk(TBROK | TTERRNO, "mincore() on the hole");
+
+	if (prepare)
+		tst_brk(TBROK, "The hole was not created");
+
+	tst_res(TFAIL, "The hole is mapped after MADV_DONTNEED");
+}
+
+static void run(unsigned int hole)
+{
+	unsigned int page;
+	unsigned char expected;
+
+	tst_res(TINFO, "MADV_DONTNEED %s a hole", hole ? "across" : "without");
+	addr = SAFE_MMAP(NULL, PAGES * page_size, PROT_READ | PROT_WRITE,
+			MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+	/* EINVAL is also returned when THP support is not configured. */
+	if (madvise(addr, PAGES * page_size, MADV_NOHUGEPAGE) &&
+	    errno != EINVAL)
+		tst_brk(TBROK | TERRNO, "madvise(MADV_NOHUGEPAGE)");
+
+	for (page = 0; page < PAGES; page++) {
+		memset(addr + page * page_size, 0x40 + page, page_size);
+		check_page(page, 0x40 + page, 1);
+	}
+
+	if (hole) {
+		SAFE_MUNMAP(addr + HOLE_PAGE * page_size, page_size);
+		check_hole(1);
+		TST_EXP_FAIL(madvise(addr + FIRST_PAGE * page_size,
+			3 * page_size, MADV_DONTNEED), ENOMEM);
+	} else {
+		TST_EXP_PASS(madvise(addr + FIRST_PAGE * page_size,
+			3 * page_size, MADV_DONTNEED));
+	}
+
+	for (page = 0; page < PAGES; page++) {
+		if (hole && page == HOLE_PAGE)
+			continue;
+
+		expected = 0x40 + page;
+		if (page >= FIRST_PAGE && page <= LAST_PAGE)
+			expected = 0;
+		check_page(page, expected, 0);
+	}
+
+	if (hole)
+		check_hole(0);
+
+	cleanup();
+}
+
+static void setup(void)
+{
+	page_size = SAFE_SYSCONF(_SC_PAGESIZE);
+	if (!page_size)
+		tst_brk(TBROK, "Invalid page size");
+}
+
+static struct tst_test test = {
+	.test = run,
+	.tcnt = 2,
+	.setup = setup,
+	.cleanup = cleanup,
+};
-- 
2.43.0

base-commit: 46688815513714a28fd2ae300adae6b6e68b838b



More information about the ltp mailing list