[LTP] [PATCH v2 2/2] syscalls/madvise13: Check MADV_DONTNEED across an unmapped hole
Wenyuan Li
2063309626@qq.com
Wed Nov 4 12:00:21 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.
Existing 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.
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, 4096-byte pages, kernel 6.18.54.1: 18 TPASS, 36 TPASS
with -i 2.
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 | 154 ++++++++++++++++++
3 files changed, 156 insertions(+)
create mode 100644 testcases/kernel/syscalls/madvise/madvise13.c
diff --git a/runtest/syscalls b/runtest/syscalls
index ff3de1b15..48cbdab39 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1095,6 +1095,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 758e601a9..3e7dbe653 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 000000000..cb4323e5b
--- /dev/null
+++ b/testcases/kernel/syscalls/madvise/madvise13.c
@@ -0,0 +1,154 @@
+// 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
+/* Test-local range, not a madvise(2) advice. */
+#define RANGE_FIRST 2
+#define RANGE_LAST 4
+#define RANGE_PAGES (RANGE_LAST - RANGE_FIRST + 1)
+
+static unsigned char *addr;
+static size_t page_size;
+
+static void cleanup(void)
+{
+ if (addr) {
+ SAFE_MUNMAP(addr, PAGES * page_size);
+ addr = NULL;
+ }
+}
+
+/*
+ * Return 1 if the page is mapped, 0 if mincore() reports ENOMEM (a hole).
+ * Any other mincore() failure is a test library error.
+ */
+static int page_mapped(unsigned int page)
+{
+ unsigned char vec;
+
+ TEST(mincore(addr + page * page_size, page_size, &vec));
+
+ if (TST_RET == -1) {
+ if (TST_ERR == ENOMEM)
+ return 0;
+
+ tst_brk(TBROK | TTERRNO, "mincore() on page %u", page);
+ }
+
+ return 1;
+}
+
+/*
+ * Return the offset of the first byte that differs from expected, or
+ * page_size when the whole page matches.
+ */
+static size_t page_first_mismatch(unsigned int page, unsigned char expected)
+{
+ unsigned char *ptr = addr + page * page_size;
+ size_t i;
+
+ for (i = 0; i < page_size; i++) {
+ if (ptr[i] != expected)
+ return i;
+ }
+
+ return page_size;
+}
+
+static void run(unsigned int hole)
+{
+ unsigned int page;
+ unsigned char expected;
+ size_t off;
+
+ 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);
+
+ for (page = 0; page < PAGES; page++) {
+ if (!page_mapped(page))
+ tst_brk(TBROK, "Page %u is not mapped after mmap()", page);
+
+ memset(addr + page * page_size, 0x40 + page, page_size);
+
+ off = page_first_mismatch(page, 0x40 + page);
+ if (off != page_size)
+ tst_brk(TBROK, "Page %u byte %zu was not initialized", page, off);
+ }
+
+ if (hole) {
+ SAFE_MUNMAP(addr + HOLE_PAGE * page_size, page_size);
+
+ if (page_mapped(HOLE_PAGE))
+ tst_brk(TBROK, "The hole was not created");
+
+ TST_EXP_FAIL(madvise(addr + RANGE_FIRST * page_size,
+ RANGE_PAGES * page_size, MADV_DONTNEED), ENOMEM);
+ } else {
+ TST_EXP_PASS(madvise(addr + RANGE_FIRST * page_size,
+ RANGE_PAGES * page_size, MADV_DONTNEED));
+ }
+
+ for (page = 0; page < PAGES; page++) {
+ if (hole && page == HOLE_PAGE)
+ continue;
+
+ expected = 0x40 + page;
+ if (page >= RANGE_FIRST && page <= RANGE_LAST)
+ expected = 0;
+
+ if (!page_mapped(page)) {
+ tst_res(TFAIL, "Page %u is no longer mapped", page);
+ continue;
+ }
+
+ off = page_first_mismatch(page, expected);
+ if (off == page_size) {
+ tst_res(TPASS, "Page %u contains only %#x", page, expected);
+ } else {
+ tst_res(TFAIL, "Page %u byte %zu: expected %#x, got %#x",
+ page, off, expected,
+ addr[page * page_size + off]);
+ }
+ }
+
+ if (hole) {
+ if (page_mapped(HOLE_PAGE))
+ tst_res(TFAIL, "The hole is mapped after MADV_DONTNEED");
+ else
+ tst_res(TPASS, "The hole remains unmapped");
+ }
+
+ cleanup();
+}
+
+static void setup(void)
+{
+ page_size = SAFE_SYSCONF(_SC_PAGESIZE);
+}
+
+static struct tst_test test = {
+ .test = run,
+ .tcnt = 2,
+ .setup = setup,
+ .cleanup = cleanup,
+};
--
2.43.0
More information about the ltp
mailing list