[LTP] [PATCH] mmap22: Improve MAP_DROPPABLE test stability using mincore()

Li Wang liwang@redhat.com
Wed Sep 17 09:57:29 CEST 2025


The current mmap22 test validates MAP_DROPPABLE by writing to the
allocated memory and checking for zero-filled pages under memory
pressure. However, this approach is unreliable because:

  - Child process memory pressure (malloc + single writes) may not
    reliably trigger cgroup memory reclaim.
  - Checking memory content (alloc[i] == 0) is insufficient to
    confirm kernel reclamation, as pages may remain resident but not
    overwritten.
  - Race conditions during child cleanup could leave residual cgroup
    processes.

Error logs:

   command: mmap22
   tst_tmpdir.c:316: TINFO: Using /tmp/LTP_mmaxrtyKb as tmpdir (xfs filesystem)
   tst_test.c:1953: TINFO: LTP version: 20250530
   tst_kconfig.c:88: TINFO: Parsing kernel config '/lib/modules/6.12.0-130.1445_2041086229.el10.x86_64+rt/build/.config'
   tst_test.c:1774: TINFO: Overall timeout per run is 0h 05m 54s
   tst_test.c:1837: TINFO: Killed the leftover descendant processes
   tst_test.c:1846: TINFO: If you are running on slow machine, try exporting LTP_TIMEOUT_MUL > 1
   tst_test.c:1848: TBROK: Test killed! (timeout?)
   tst_cgroup.c:1043: TBROK: unlinkat(5</sys/fs/cgroup/ltp>, 'test-92902', AT_REMOVEDIR): EBUSY (16)
   ...

This patch improves the test in the following ways:

  * Use mincore() to precisely detect if MAP_DROPPABLE pages have
    been reclaimed by the kernel.
  * Replace the old child loop with a stronger memory pressure loop
    (malloc + memset), ensuring cgroup limits are hit quickly.
  * Use SAFE_KILL + SAFE_WAITPID for robust child cleanup.
  * Extend runtime and add short sleeps to reduce busy looping and
    stabilize test results.
  * Record page size during setup for consistent use across functions.

As a result, the test becomes more stable, deterministic, and easier
to reproduce under different kernels and configurations.

Signed-off-by: Li Wang <liwang@redhat.com>
---
 testcases/kernel/syscalls/mmap/mmap22.c | 78 ++++++++++++++++---------
 1 file changed, 52 insertions(+), 26 deletions(-)

diff --git a/testcases/kernel/syscalls/mmap/mmap22.c b/testcases/kernel/syscalls/mmap/mmap22.c
index 1507fdfa7..b9db8e1b6 100644
--- a/testcases/kernel/syscalls/mmap/mmap22.c
+++ b/testcases/kernel/syscalls/mmap/mmap22.c
@@ -9,12 +9,19 @@
  * Test mmap(2) with MAP_DROPPABLE flag.
  *
  * Test base on kernel selftests/mm/droppable.c
+ *
+ * Ensure that memory allocated with MAP_DROPPABLE can be reclaimed
+ * under memory pressure within a cgroup.
  */
 
 #define _GNU_SOURCE
 #include <errno.h>
 #include <stdio.h>
 #include <sys/types.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
 #include "tst_test.h"
 #include "lapi/mmap.h"
 
@@ -22,13 +29,24 @@
 #define ALLOC_SIZE (128 * TST_MB)
 
 static struct tst_cg_group *cg_child;
+static pid_t child;
+static int page_size;
+
+static void stress_child(void)
+{
+	for (;;) {
+		char *buf = malloc(page_size);
+		if (!buf)
+			exit(1);
+		memset(buf, 'B', page_size);
+	}
+}
 
 static void test_mmap(void)
 {
-	size_t alloc_size = ALLOC_SIZE;
-	size_t page_size = getpagesize();
 	char *alloc;
-	pid_t child;
+	unsigned char *vec;
+	size_t npages = ALLOC_SIZE / page_size;
 
 	cg_child = tst_cg_group_mk(tst_cg, "child");
 	SAFE_CG_PRINTF(tst_cg, "memory.max", "%d", MEM_LIMIT);
@@ -38,38 +56,45 @@ static void test_mmap(void)
 		SAFE_CG_PRINTF(tst_cg, "memory.swap.max", "%d", MEM_LIMIT);
 	SAFE_CG_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
 
-	alloc = SAFE_MMAP(0, alloc_size, PROT_READ | PROT_WRITE,
+	alloc = SAFE_MMAP(0, ALLOC_SIZE, PROT_READ | PROT_WRITE,
 			MAP_ANONYMOUS | MAP_DROPPABLE, -1, 0);
 
-	memset(alloc, 'A', alloc_size);
-	for (size_t i = 0; i < alloc_size; i += page_size) {
-		if (alloc[i]  != 'A')
-			tst_res(TFAIL, "memset failed");
-	}
+	memset(alloc, 'A', ALLOC_SIZE);
+
+	vec = SAFE_MALLOC(npages);
 
 	child = SAFE_FORK();
-	if (!child) {
-		for (;;)
-			*(char *)malloc(page_size) = 'B';
-	}
+	if (!child)
+		stress_child();
 
-	while (1) {
-		for (size_t i = 0; i < alloc_size; i += page_size) {
-			if (!tst_remaining_runtime()) {
-				tst_res(TFAIL, "MAP_DROPPABLE did not drop memory within the timeout period.");
-				goto kill_child;
-			}
-			if (!alloc[i]) {
-				tst_res(TPASS, "MAP_DROPPABLE test pass.");
-				goto kill_child;
+	for (;;) {
+		if (!tst_remaining_runtime()) {
+			tst_res(TFAIL, "MAP_DROPPABLE did not drop pages within timeout");
+			goto cleanup;
+		}
+
+		if (mincore(alloc, ALLOC_SIZE, vec) == -1)
+			tst_brk(TBROK | TERRNO, "mincore failed");
+
+		for (size_t i = 0; i < npages; i++) {
+			if (!(vec[i] & 1)) {
+				tst_res(TPASS, "MAP_DROPPABLE page reclaimed by kernel");
+				goto cleanup;
 			}
 		}
+
+		usleep(100000);
 	}
 
-kill_child:
-	SAFE_KILL(child, SIGKILL);
-	SAFE_WAITPID(child, NULL, 0);
-	SAFE_MUNMAP(alloc, alloc_size);
+cleanup:
+	if (child > 0) {
+		SAFE_KILL(child, SIGKILL);
+		SAFE_WAITPID(child, NULL, 0);
+	}
+	SAFE_MUNMAP(alloc, ALLOC_SIZE);
+	free(vec);
+	SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
+	cg_child = tst_cg_group_rm(cg_child);
 }
 
 static void setup(void)
@@ -84,6 +109,7 @@ static void setup(void)
 		tst_brk(TBROK | TERRNO, "mmap() MAP_DROPPABLE failed");
 
 	SAFE_MUNMAP(addr, 1);
+	page_size = getpagesize();
 }
 
 static void cleanup(void)
-- 
2.51.0



More information about the ltp mailing list