Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests
@ 2026-10-19 17:11 Gregory Price (Meta)
  2026-10-19 17:12 ` [PATCH v3 1/7] " Gregory Price (Meta)
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:11 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah

MADV_COLD and MADV_PAGEOUT share a page-table walker in mm/madvise.c
that handles ordinary PTEs, PTE-mapped large folios and huge PMDs.
Existing selftests cover little of its range, folio and permission
handling.

The walker has needed several fixes recently, and it's clear we need
to refactor this walker to make it maintainable.  Ahead of that, let's
add some selftests to at least cover contractual behaviors.

This series adds tools/testing/selftests/mm/madv_cold_pageout.c to test
only behaviors userspace can rely on, so that it stays valid across
changes to the walker's implementation.

Each patch adds one group of tests, simplest first:

  1. neither hint populates a hole, faults a swapped page back in, or
     accepts a locked VMA
  2. a zero-length range succeeds, an unaligned start fails, and a gap
     fails with ENOMEM after both sides are advised
  3. neither hint accepts a hugetlb VMA
  4. both hints preserve shared memory, and MADV_PAGEOUT leaves a
     SHM_LOCK'd page mapped
  5. paging out all or half of a THP reclaims only the advised pages,
     and MADV_COLD on half a THP leaves it intact
  6. only a caller that owns or can write a file may page out its pages;
     anyone else only gets their private COW pages paged out
  7. nor may anyone else split a large file folio, whether a PMD or PTEs
     map it

We check whether MADV_PAGEOUT can swap/evict pages at all and skip
relevant tests that would fail if it can't.  We do expect that the
swap/eviction will happen within 1 second with retries, but this has
been reliable on a reasonably unloaded system. We may need to increase
that timeout if it becomes an issue.

Tests that need root, THP, or hugetlbs skip accordingly as well.

All 24 tests pass over three consecutive runs, with no skips and no
warnings in dmesg, as root in a virtme-ng guest with 4 CPUs, two NUMA
nodes, a 2G swap disk, THP set to always, CONFIG_DEBUG_VM and
CONFIG_PROVE_LOCKING.

In the same guest:
  - unprivileged: 18 pass, 6 skip (5 need root, 1 needs hugetlb pages)
  - root without CAP_SYS_ADMIN: 22 pass, 2 skip (need page flags)
  - no swap: 16 pass, 8 skip (need swap)

Changes since v2:
  - renamed to madv_cold_pageout.c (Sarthak)
  - pin to one CPU for the whole run (Sarthak)
  - check swap and file unmapping once in main() instead of per-test.
  - COW test retries, and fails if the COW page isn't swapped (Sarthak)
  - gap test checks ENOMEM before it needs swap (Sarthak)
  - reserve a hugetlb page as root, since run_vmtests.sh doesn't
  - ignore /proc/kpageflags when pagemap hides PFNs (no CAP_SYS_ADMIN)
  - shmem tests no longer require swap
  - owned file test no longer needs /tmp writable by nobody
  - folded patch 2 into patch 1 since it's really the same thing.
  - comment and changelog fixes

v2: https://lore.kernel.org/linux-mm/20261014175756.2035176-1-gourry@gourry.net/
original set: https://lore.kernel.org/linux-mm/20260922235830.2350770-2-gourry@gourry.net/

Gregory Price (Meta) (7):
  selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests
  selftests/mm: check MADV_COLD and MADV_PAGEOUT range handling
  selftests/mm: check that MADV_COLD and MADV_PAGEOUT reject hugetlb
  selftests/mm: check that MADV_COLD and MADV_PAGEOUT preserve shared
    memory
  selftests/mm: check MADV_COLD and MADV_PAGEOUT on partly advised THPs
  selftests/mm: check MADV_PAGEOUT file permissions
  selftests/mm: check that unauthorized MADV_PAGEOUT skips large file
    folios

 tools/testing/selftests/mm/Makefile           |   2 +
 .../selftests/mm/ksft_madv_cold_pageout.sh    |   4 +
 .../testing/selftests/mm/madv_cold_pageout.c  | 971 ++++++++++++++++++
 tools/testing/selftests/mm/run_vmtests.sh     |   5 +
 tools/testing/selftests/mm/vm_util.h          |   1 +
 5 files changed, 983 insertions(+)
 create mode 100755 tools/testing/selftests/mm/ksft_madv_cold_pageout.sh
 create mode 100644 tools/testing/selftests/mm/madv_cold_pageout.c

-- 
2.56.0



^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v3 1/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests
  2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
@ 2026-10-19 17:12 ` Gregory Price (Meta)
  2026-10-28 11:22   ` Sarthak Sharma
  2026-10-19 17:12 ` [PATCH v3 2/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT range handling Gregory Price (Meta)
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:12 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah

MADV_COLD and MADV_PAGEOUT share a page-table walker covering ordinary
PTEs, PTE-mapped large folios and huge PMDs. Existing selftests use
MADV_PAGEOUT mostly to swap memory out for other tests, and check only
special mappings (PFNMAP, guard regions, droppable memory), not the
walker's range, folio and permission handling.

Add a kselftest harness test for both hints, starting with the most basic
behavior userspace relies on: neither hint populates a PTE hole or faults
a swapped page back in, and neither may be applied to a locked VMA.

Before running the tests, pin the process to the CPU it starts on,
so that the LRU batch MADV_COLD and MADV_PAGEOUT drain holds the
folios a test faulted in.

Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
v3: Dropped Sarthak's Reviewed-by from v2 1/8 and 2/8, which are folded
here, since this patch now adds main() with the CPU pinning and the
one-time reclaim check, and the swapped-page tests fail rather than skip.
---
 tools/testing/selftests/mm/Makefile           |   2 +
 .../selftests/mm/ksft_madv_cold_pageout.sh    |   4 +
 .../testing/selftests/mm/madv_cold_pageout.c  | 209 ++++++++++++++++++
 tools/testing/selftests/mm/run_vmtests.sh     |   5 +
 4 files changed, 220 insertions(+)
 create mode 100755 tools/testing/selftests/mm/ksft_madv_cold_pageout.sh
 create mode 100644 tools/testing/selftests/mm/madv_cold_pageout.c

diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index beacc0f87304..c7fc20c153b8 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -68,6 +68,7 @@ TEST_GEN_FILES += hugetlb-read-hwpoison
 TEST_GEN_FILES += hugetlb-shm
 TEST_GEN_FILES += hugetlb-soft-offline
 TEST_GEN_FILES += khugepaged
+TEST_GEN_FILES += madv_cold_pageout
 TEST_GEN_FILES += madv_populate
 TEST_GEN_FILES += map_fixed_noreplace
 TEST_GEN_FILES += map_populate
@@ -154,6 +155,7 @@ TEST_PROGS += ksft_kmemleak_confirm.sh
 TEST_PROGS += ksft_kmemleak_dedup.sh
 TEST_PROGS += ksft_ksm.sh
 TEST_PROGS += ksft_ksm_numa.sh
+TEST_PROGS += ksft_madv_cold_pageout.sh
 TEST_PROGS += ksft_madv_guard.sh
 TEST_PROGS += ksft_madv_populate.sh
 TEST_PROGS += ksft_memfd_secret.sh
diff --git a/tools/testing/selftests/mm/ksft_madv_cold_pageout.sh b/tools/testing/selftests/mm/ksft_madv_cold_pageout.sh
new file mode 100755
index 000000000000..502d3fb387aa
--- /dev/null
+++ b/tools/testing/selftests/mm/ksft_madv_cold_pageout.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+# SPDX-License-Identifier: GPL-2.0
+
+./run_vmtests.sh -t madv_cold_pageout
diff --git a/tools/testing/selftests/mm/madv_cold_pageout.c b/tools/testing/selftests/mm/madv_cold_pageout.c
new file mode 100644
index 000000000000..5d451d409937
--- /dev/null
+++ b/tools/testing/selftests/mm/madv_cold_pageout.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MADV_COLD and MADV_PAGEOUT tests.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+#include "vm_util.h"
+
+/* Reclaim can fail transiently, so retry for up to a second. */
+#define PAGEOUT_RETRIES 100
+
+static size_t page_size;
+/* Whether MADV_PAGEOUT can swap out anonymous memory, see main(). */
+static bool can_swap;
+
+/*
+ * Keep this task on one CPU, so that draining that CPU's LRU batches drains
+ * the folios this task faulted in.
+ */
+static void pin_to_current_cpu(void)
+{
+	int cpu = sched_getcpu();
+	cpu_set_t *mask;
+	size_t size;
+
+	if (cpu < 0)
+		ksft_exit_fail_perror("sched_getcpu");
+	/* CPU numbers can exceed CPU_SETSIZE, so size the mask to fit. */
+	mask = CPU_ALLOC(cpu + 1);
+	if (!mask)
+		ksft_exit_fail_perror("CPU_ALLOC");
+	size = CPU_ALLOC_SIZE(cpu + 1);
+	CPU_ZERO_S(size, mask);
+	CPU_SET_S(cpu, size, mask);
+	if (sched_setaffinity(0, size, mask))
+		ksft_exit_fail_perror("sched_setaffinity");
+	CPU_FREE(mask);
+}
+
+FIXTURE(madv_cold_pageout)
+{
+	int pagemap_fd;
+};
+
+FIXTURE_SETUP(madv_cold_pageout)
+{
+	self->pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+	ASSERT_GE(self->pagemap_fd, 0);
+}
+
+FIXTURE_TEARDOWN(madv_cold_pageout)
+{
+	close(self->pagemap_fd);
+}
+
+/* Page out the range until its first page is swapped, or madvise() fails. */
+static bool pageout_until_swapped(char *mapping, size_t size, int pagemap_fd)
+{
+	int retry;
+
+	for (retry = 0; retry < PAGEOUT_RETRIES; retry++) {
+		if (madvise(mapping, size, MADV_PAGEOUT))
+			return false;
+		if (pagemap_is_swapped(pagemap_fd, mapping))
+			return true;
+		usleep(10000);
+	}
+
+	return false;
+}
+
+/*
+ * Both hints are best effort, so these tests check what userspace can rely
+ * on: which mappings are accepted, which pages must never be reclaimed, and
+ * that neither hint changes memory contents or populates the range. main()
+ * checks once whether reclaim works at all. A test that needs a page to be
+ * reclaimed skips if it does not, and fails if the page is then not reclaimed.
+ */
+
+/* Neither hint may populate a hole between two populated pages. */
+static void check_skips_pte_hole(struct __test_metadata *_metadata,
+				 int pagemap_fd, int advice)
+{
+	char *mapping;
+
+	mapping = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	/*
+	 * Keep a large folio from covering the hole. Without THP there are no
+	 * large anonymous folios, and this fails harmlessly.
+	 */
+	madvise(mapping, 3 * page_size, MADV_NOHUGEPAGE);
+	mapping[0] = 1;
+	mapping[2 * page_size] = 1;
+	ASSERT_FALSE(pagemap_is_populated(pagemap_fd, mapping + page_size));
+
+	ASSERT_EQ(madvise(mapping, 3 * page_size, advice), 0);
+	ASSERT_FALSE(pagemap_is_populated(pagemap_fd, mapping + page_size));
+	ASSERT_EQ(mapping[0], 1);
+	ASSERT_EQ(mapping[2 * page_size], 1);
+
+	ASSERT_EQ(munmap(mapping, 3 * page_size), 0);
+}
+
+TEST_F(madv_cold_pageout, cold_skips_pte_hole)
+{
+	check_skips_pte_hole(_metadata, self->pagemap_fd, MADV_COLD);
+}
+
+TEST_F(madv_cold_pageout, pageout_skips_pte_hole)
+{
+	check_skips_pte_hole(_metadata, self->pagemap_fd, MADV_PAGEOUT);
+}
+
+/* Neither hint may be applied to locked memory. */
+TEST_F(madv_cold_pageout, locked_vma_is_rejected)
+{
+	char *mapping;
+
+	mapping = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	mapping[0] = 1;
+	if (mlock(mapping, page_size))
+		SKIP(return, "could not lock a page");
+
+	ASSERT_EQ(madvise(mapping, page_size, MADV_COLD), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_EQ(madvise(mapping, page_size, MADV_PAGEOUT), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+/* Neither hint may fault a swapped page back in. */
+static void check_skips_swapped_pte(struct __test_metadata *_metadata,
+				    int pagemap_fd, int advice)
+{
+	char *mapping;
+
+	if (!can_swap)
+		SKIP(return, "MADV_PAGEOUT cannot swap");
+	mapping = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	mapping[0] = 1;
+	ASSERT_TRUE(pageout_until_swapped(mapping, page_size, pagemap_fd));
+
+	ASSERT_EQ(madvise(mapping, page_size, advice), 0);
+	ASSERT_TRUE(pagemap_is_swapped(pagemap_fd, mapping));
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+TEST_F(madv_cold_pageout, cold_skips_swapped_pte)
+{
+	check_skips_swapped_pte(_metadata, self->pagemap_fd, MADV_COLD);
+}
+
+TEST_F(madv_cold_pageout, pageout_skips_swapped_pte)
+{
+	check_skips_swapped_pte(_metadata, self->pagemap_fd, MADV_PAGEOUT);
+}
+
+/* Swap out an anonymous page, to see whether swap works at all. */
+static bool probe_swap(int pagemap_fd)
+{
+	bool swapped;
+	char *page;
+
+	page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+		    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	if (page == MAP_FAILED)
+		ksft_exit_fail_perror("mmap");
+	page[0] = 1;
+	swapped = pageout_until_swapped(page, page_size, pagemap_fd);
+	munmap(page, page_size);
+
+	return swapped;
+}
+
+int main(int argc, char **argv)
+{
+	int pagemap_fd;
+
+	page_size = getpagesize();
+	/* The harness forks each test, which inherits this. */
+	pin_to_current_cpu();
+
+	pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+	if (pagemap_fd < 0)
+		ksft_exit_fail_perror("open /proc/self/pagemap");
+	can_swap = probe_swap(pagemap_fd);
+	if (!can_swap)
+		ksft_print_msg("MADV_PAGEOUT cannot swap\n");
+	close(pagemap_fd);
+
+	return test_harness_run(argc, argv);
+}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index a1b45a3dedae..782b3ae4b2d2 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -53,6 +53,8 @@ separated by spaces:
 	test madvise(2) MADV_GUARD_INSTALL and MADV_GUARD_REMOVE options
 - madv_populate
 	test memadvise(2) MADV_POPULATE_{READ,WRITE} options
+- madv_cold_pageout
+	test madvise(2) MADV_COLD and MADV_PAGEOUT
 - memfd_secret
 	test memfd_secret(2)
 - process_mrelease
@@ -330,6 +332,9 @@ CATEGORY="madv_guard" run_test ./guard-regions
 # MADV_POPULATE_READ and MADV_POPULATE_WRITE tests
 CATEGORY="madv_populate" run_test ./madv_populate
 
+# MADV_COLD and MADV_PAGEOUT tests
+CATEGORY="madv_cold_pageout" run_test ./madv_cold_pageout
+
 # PROCESS_MADV test
 CATEGORY="process_madv" run_test ./process_madv
 
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 2/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT range handling
  2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
  2026-10-19 17:12 ` [PATCH v3 1/7] " Gregory Price (Meta)
@ 2026-10-19 17:12 ` Gregory Price (Meta)
  2026-10-28 11:23   ` Sarthak Sharma
  2026-10-19 17:12 ` [PATCH v3 3/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT reject hugetlb Gregory Price (Meta)
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:12 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah

madvise(2) accepts a zero-length range, rejects an unaligned start, and
reports an unmapped gap in the range with ENOMEM while still applying the
advice to the mapped pages on either side.

Check each of these for both hints. Around the gap, only MADV_PAGEOUT has
an effect the test can observe, so only MADV_PAGEOUT is checked for
paging out both neighbors, and only if MADV_PAGEOUT can swap at all.
Both hints' ENOMEM is checked first, so it is covered without swap.

Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
 .../testing/selftests/mm/madv_cold_pageout.c  | 80 +++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/tools/testing/selftests/mm/madv_cold_pageout.c b/tools/testing/selftests/mm/madv_cold_pageout.c
index 5d451d409937..8cbbfd4c24c4 100644
--- a/tools/testing/selftests/mm/madv_cold_pageout.c
+++ b/tools/testing/selftests/mm/madv_cold_pageout.c
@@ -141,6 +141,86 @@ TEST_F(madv_cold_pageout, locked_vma_is_rejected)
 	ASSERT_EQ(munmap(mapping, page_size), 0);
 }
 
+/* A zero-length range is accepted. */
+TEST_F(madv_cold_pageout, zero_length_is_accepted)
+{
+	char *mapping;
+
+	mapping = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+
+	ASSERT_EQ(madvise(mapping, 0, MADV_COLD), 0);
+	ASSERT_EQ(madvise(mapping, 0, MADV_PAGEOUT), 0);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+/* The start of the range must be page-aligned. */
+TEST_F(madv_cold_pageout, unaligned_start_is_rejected)
+{
+	char *mapping;
+
+	mapping = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	mapping[0] = 1;
+
+	ASSERT_EQ(madvise(mapping + 1, page_size, MADV_COLD), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_EQ(madvise(mapping + 1, page_size, MADV_PAGEOUT), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(munmap(mapping, 2 * page_size), 0);
+}
+
+/*
+ * An unmapped gap in the range makes either hint fail with ENOMEM, after
+ * advising the mapped pages on both sides of the gap. Only MADV_PAGEOUT has
+ * an effect on those pages that the test can observe.
+ */
+TEST_F(madv_cold_pageout, unmapped_gap_fails_after_advising)
+{
+	bool swapped = false;
+	char *mapping;
+	int retry;
+
+	mapping = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	mapping[0] = 1;
+	mapping[2 * page_size] = 1;
+	ASSERT_EQ(munmap(mapping + page_size, page_size), 0);
+
+	ASSERT_EQ(madvise(mapping, 3 * page_size, MADV_COLD), -1);
+	ASSERT_EQ(errno, ENOMEM);
+	ASSERT_EQ(madvise(mapping, 3 * page_size, MADV_PAGEOUT), -1);
+	ASSERT_EQ(errno, ENOMEM);
+	ASSERT_EQ(mapping[0], 1);
+	ASSERT_EQ(mapping[2 * page_size], 1);
+
+	if (!can_swap)
+		SKIP(return, "MADV_PAGEOUT cannot swap");
+
+	for (retry = 0; retry < PAGEOUT_RETRIES && !swapped; retry++) {
+		ASSERT_EQ(madvise(mapping, 3 * page_size, MADV_PAGEOUT), -1);
+		ASSERT_EQ(errno, ENOMEM);
+		swapped = pagemap_is_swapped(self->pagemap_fd, mapping) &&
+			  pagemap_is_swapped(self->pagemap_fd,
+					     mapping + 2 * page_size);
+		if (!swapped)
+			usleep(10000);
+	}
+	ASSERT_TRUE(swapped)
+		TH_LOG("MADV_PAGEOUT did not page out both sides of the gap");
+	ASSERT_EQ(mapping[0], 1);
+	ASSERT_EQ(mapping[2 * page_size], 1);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+	ASSERT_EQ(munmap(mapping + 2 * page_size, page_size), 0);
+}
+
 /* Neither hint may fault a swapped page back in. */
 static void check_skips_swapped_pte(struct __test_metadata *_metadata,
 				    int pagemap_fd, int advice)
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 3/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT reject hugetlb
  2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
  2026-10-19 17:12 ` [PATCH v3 1/7] " Gregory Price (Meta)
  2026-10-19 17:12 ` [PATCH v3 2/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT range handling Gregory Price (Meta)
@ 2026-10-19 17:12 ` Gregory Price (Meta)
  2026-10-19 17:12 ` [PATCH v3 4/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT preserve shared memory Gregory Price (Meta)
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:12 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah, Sarthak Sharma

Neither hint may be applied to hugetlb memory, so madvise(2) must fail
with EINVAL for a hugetlb VMA. Locked VMAs are already checked here and
PFNMAP VMAs by the pfnmap test.

Check the hugetlb case for both hints. run_vmtests.sh does not reserve
hugetlb pages, so when run as root main() reserves the one page the test
needs, and restores the pool on exit. Otherwise the test skips if no
page is free.

Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
v3: Kept Sarthak's Reviewed-by, but only a couple minor changes,
    e.g. the hugetlb reservations / skips.
---
 .../testing/selftests/mm/madv_cold_pageout.c  | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/tools/testing/selftests/mm/madv_cold_pageout.c b/tools/testing/selftests/mm/madv_cold_pageout.c
index 8cbbfd4c24c4..0874726ec141 100644
--- a/tools/testing/selftests/mm/madv_cold_pageout.c
+++ b/tools/testing/selftests/mm/madv_cold_pageout.c
@@ -141,6 +141,29 @@ TEST_F(madv_cold_pageout, locked_vma_is_rejected)
 	ASSERT_EQ(munmap(mapping, page_size), 0);
 }
 
+/* Neither hint may be applied to hugetlb memory. */
+TEST_F(madv_cold_pageout, hugetlb_vma_is_rejected)
+{
+	size_t size = default_huge_page_size();
+	char *mapping;
+
+	if (!size)
+		SKIP(return, "hugetlb is not supported");
+	mapping = mmap(NULL, size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
+	if (mapping == MAP_FAILED)
+		SKIP(return, "no hugetlb pages are available");
+	mapping[0] = 1;
+
+	ASSERT_EQ(madvise(mapping, size, MADV_COLD), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_EQ(madvise(mapping, size, MADV_PAGEOUT), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(munmap(mapping, size), 0);
+}
+
 /* A zero-length range is accepted. */
 TEST_F(madv_cold_pageout, zero_length_is_accepted)
 {
@@ -276,6 +299,9 @@ int main(int argc, char **argv)
 	page_size = getpagesize();
 	/* The harness forks each test, which inherits this. */
 	pin_to_current_cpu();
+	/* Only root can reserve the page that hugetlb_vma_is_rejected maps. */
+	if (!geteuid())
+		hugetlb_setup_default(1);
 
 	pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
 	if (pagemap_fd < 0)
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 4/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT preserve shared memory
  2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
                   ` (2 preceding siblings ...)
  2026-10-19 17:12 ` [PATCH v3 3/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT reject hugetlb Gregory Price (Meta)
@ 2026-10-19 17:12 ` Gregory Price (Meta)
  2026-10-28 11:24   ` Sarthak Sharma
  2026-10-19 17:12 ` [PATCH v3 5/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT on partly advised THPs Gregory Price (Meta)
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:12 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah

Both hints are non-destructive, but the tests so far only check that for
private anonymous memory. Shared memory reaches reclaim differently:

Test the following:
 - a dirty shared file page must be written back or left resident,
 - shared anonymous memory is paged out through shmem
 - a SHM_LOCK'd segment is unevictable without its VMA being VM_LOCKED.

MADV_PAGEOUT unmaps a shmem page before it tries to swap it out
so none of these needs swap.

The SHM_LOCK test confirms the page is unevictable and skips otherwise.

Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
v3: Dropped Sarthak's Reviewed-by from v2 5/8: added the fixture check
    for hidden PFNs and create_temp_file(), and the shared anonymous
    test now fails rather than skips when the page is not unmapped.
---
 .../testing/selftests/mm/madv_cold_pageout.c  | 177 ++++++++++++++++++
 tools/testing/selftests/mm/vm_util.h          |   1 +
 2 files changed, 178 insertions(+)

diff --git a/tools/testing/selftests/mm/madv_cold_pageout.c b/tools/testing/selftests/mm/madv_cold_pageout.c
index 0874726ec141..7d92f0ecbd27 100644
--- a/tools/testing/selftests/mm/madv_cold_pageout.c
+++ b/tools/testing/selftests/mm/madv_cold_pageout.c
@@ -7,7 +7,11 @@
 #include <fcntl.h>
 #include <sched.h>
 #include <stdbool.h>
+#include <stdint.h>
+#include <sys/ipc.h>
 #include <sys/mman.h>
+#include <sys/shm.h>
+#include <sys/stat.h>
 #include <unistd.h>
 
 #include "kselftest_harness.h"
@@ -44,19 +48,47 @@ static void pin_to_current_cpu(void)
 	CPU_FREE(mask);
 }
 
+/* Whether pagemap shows PFNs, which it only does with CAP_SYS_ADMIN. */
+static bool pagemap_shows_pfns(struct __test_metadata *_metadata,
+			       int pagemap_fd)
+{
+	unsigned long pfn;
+	char *page;
+
+	page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+		    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(page, MAP_FAILED);
+	page[0] = 1;
+	pfn = pagemap_get_pfn(pagemap_fd, page);
+	ASSERT_EQ(munmap(page, page_size), 0);
+
+	return pfn && pfn != -1UL;
+}
+
 FIXTURE(madv_cold_pageout)
 {
 	int pagemap_fd;
+	/* Needs root, and is -1 unless pagemap also shows PFNs. */
+	int kpageflags_fd;
 };
 
 FIXTURE_SETUP(madv_cold_pageout)
 {
 	self->pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
 	ASSERT_GE(self->pagemap_fd, 0);
+	self->kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
+	/* Page flags are no use without the PFNs to look them up by. */
+	if (self->kpageflags_fd >= 0 &&
+	    !pagemap_shows_pfns(_metadata, self->pagemap_fd)) {
+		close(self->kpageflags_fd);
+		self->kpageflags_fd = -1;
+	}
 }
 
 FIXTURE_TEARDOWN(madv_cold_pageout)
 {
+	if (self->kpageflags_fd >= 0)
+		close(self->kpageflags_fd);
 	close(self->pagemap_fd);
 }
 
@@ -76,6 +108,58 @@ static bool pageout_until_swapped(char *mapping, size_t size, int pagemap_fd)
 	return false;
 }
 
+/* Page out the range until its first page is unmapped, or madvise() fails. */
+static bool pageout_until_unmapped(char *mapping, size_t size, int pagemap_fd)
+{
+	int retry;
+
+	for (retry = 0; retry < PAGEOUT_RETRIES; retry++) {
+		if (madvise(mapping, size, MADV_PAGEOUT))
+			return false;
+		if (!pagemap_is_populated(pagemap_fd, mapping))
+			return true;
+		usleep(10000);
+	}
+
+	return false;
+}
+
+/* Create an unlinked, zero-filled temporary file, or return -1. */
+static int create_temp_file(size_t size)
+{
+	char template[] = "/tmp/madv_cold_pageout-XXXXXX";
+	int fd;
+
+	fd = mkstemp(template);
+	if (fd < 0)
+		return -1;
+	unlink(template);
+	if (ftruncate(fd, size)) {
+		close(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+/* Map an unlinked, zero-filled temporary file with the given permissions. */
+static char *map_temp_file(struct __test_metadata *_metadata, size_t size,
+			   int flags, mode_t mode)
+{
+	char *mapping;
+	int fd;
+
+	fd = create_temp_file(size);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(fchmod(fd, mode), 0);
+
+	mapping = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	close(fd);
+
+	return mapping;
+}
+
 /*
  * Both hints are best effort, so these tests check what userspace can rely
  * on: which mappings are accepted, which pages must never be reclaimed, and
@@ -275,6 +359,99 @@ TEST_F(madv_cold_pageout, pageout_skips_swapped_pte)
 	check_skips_swapped_pte(_metadata, self->pagemap_fd, MADV_PAGEOUT);
 }
 
+/* MADV_COLD must not discard a dirty page of a shared file mapping. */
+TEST_F(madv_cold_pageout, cold_preserves_dirty_shared_file_page)
+{
+	char *mapping;
+
+	mapping = map_temp_file(_metadata, page_size, MAP_SHARED, 0600);
+	mapping[0] = 1;
+
+	ASSERT_EQ(madvise(mapping, page_size, MADV_COLD), 0);
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+/*
+ * MADV_PAGEOUT may write a dirty shared file page back or leave it resident,
+ * but must not discard it.
+ */
+TEST_F(madv_cold_pageout, pageout_preserves_dirty_shared_file_page)
+{
+	char *mapping;
+
+	mapping = map_temp_file(_metadata, page_size, MAP_SHARED, 0600);
+	mapping[0] = 1;
+
+	if (!pageout_until_unmapped(mapping, page_size, self->pagemap_fd))
+		SKIP(return, "MADV_PAGEOUT did not unmap the page");
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+/* MADV_PAGEOUT must not lose the contents of shared anonymous memory. */
+TEST_F(madv_cold_pageout, pageout_preserves_shared_anon_page)
+{
+	char *mapping;
+
+	mapping = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+		       MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	mapping[0] = 1;
+
+	/* MADV_PAGEOUT unmaps a shmem page even when it cannot swap it out. */
+	ASSERT_TRUE(pageout_until_unmapped(mapping, page_size,
+					   self->pagemap_fd));
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+/*
+ * SHM_LOCK makes shmem pages unevictable without setting VM_LOCKED on the
+ * VMA, so MADV_PAGEOUT accepts the range but must not reclaim the page.
+ */
+TEST_F(madv_cold_pageout, pageout_skips_unevictable_page)
+{
+	unsigned long pfn;
+	uint64_t flags;
+	char *mapping;
+	int shmid;
+
+	if (self->kpageflags_fd < 0)
+		SKIP(return, "cannot read page flags");
+	shmid = shmget(IPC_PRIVATE, page_size, IPC_CREAT | 0600);
+	ASSERT_GE(shmid, 0);
+	if (shmctl(shmid, SHM_LOCK, NULL)) {
+		shmctl(shmid, IPC_RMID, NULL);
+		SKIP(return, "could not lock a shmem segment");
+	}
+	mapping = shmat(shmid, NULL, 0);
+	/* The segment is destroyed once it is detached. */
+	shmctl(shmid, IPC_RMID, NULL);
+	ASSERT_NE(mapping, (void *)-1);
+	mapping[0] = 1;
+
+	/*
+	 * MADV_COLD drains this CPU's LRU add batch, which makes the page
+	 * unevictable.
+	 */
+	ASSERT_EQ(madvise(mapping, page_size, MADV_COLD), 0);
+	pfn = pagemap_get_pfn(self->pagemap_fd, mapping);
+	ASSERT_NE(pfn, -1UL);
+	ASSERT_EQ(pageflags_get(pfn, self->kpageflags_fd, &flags), 0);
+	if (!(flags & KPF_UNEVICTABLE))
+		SKIP(return, "could not make the page unevictable");
+
+	ASSERT_EQ(madvise(mapping, page_size, MADV_PAGEOUT), 0);
+	ASSERT_TRUE(pagemap_is_populated(self->pagemap_fd, mapping));
+	ASSERT_EQ(mapping[0], 1);
+
+	ASSERT_EQ(shmdt(mapping), 0);
+}
+
 /* Swap out an anonymous page, to see whether swap works at all. */
 static bool probe_swap(int pagemap_fd)
 {
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index b5d59729d432..95898e045bac 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -24,6 +24,7 @@
 
 #define KPF_COMPOUND_HEAD             BIT_ULL(15)
 #define KPF_COMPOUND_TAIL             BIT_ULL(16)
+#define KPF_UNEVICTABLE               BIT_ULL(18)
 #define KPF_HWPOISON                  BIT_ULL(19)
 #define KPF_THP                       BIT_ULL(22)
 /*
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 5/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT on partly advised THPs
  2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
                   ` (3 preceding siblings ...)
  2026-10-19 17:12 ` [PATCH v3 4/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT preserve shared memory Gregory Price (Meta)
@ 2026-10-19 17:12 ` Gregory Price (Meta)
  2026-10-19 17:12 ` [PATCH v3 6/7] selftests/mm: check MADV_PAGEOUT file permissions Gregory Price (Meta)
  2026-10-19 17:12 ` [PATCH v3 7/7] selftests/mm: check that unauthorized MADV_PAGEOUT skips large file folios Gregory Price (Meta)
  6 siblings, 0 replies; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:12 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah, Sarthak Sharma

Check that paging out a whole PMD-mapped THP swaps it without corrupting
it, and that pages outside the range stay mapped after paging out a base
page or half of a PMD- or PTE-mapped THP.

Also check that MADV_COLD on half of either THP preserves its contents.

Tests skip when PMD-sized THPs cannot be allocated, and the MADV_PAGEOUT
tests also when MADV_PAGEOUT cannot swap. With /proc/kpageflags the
PTE-mapped cases also confirm that PTE-mapping left the folio large.

Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
v3: Kept Sarthak's Reviewed-by; the MADV_PAGEOUT tests now skip only if
MADV_PAGEOUT cannot swap, and fail if reclaim then does not happen, as he
suggested for v2 7/8.
---
 .../testing/selftests/mm/madv_cold_pageout.c  | 238 ++++++++++++++++++
 1 file changed, 238 insertions(+)

diff --git a/tools/testing/selftests/mm/madv_cold_pageout.c b/tools/testing/selftests/mm/madv_cold_pageout.c
index 7d92f0ecbd27..b5eec490fb68 100644
--- a/tools/testing/selftests/mm/madv_cold_pageout.c
+++ b/tools/testing/selftests/mm/madv_cold_pageout.c
@@ -8,6 +8,7 @@
 #include <sched.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <string.h>
 #include <sys/ipc.h>
 #include <sys/mman.h>
 #include <sys/shm.h>
@@ -17,10 +18,15 @@
 #include "kselftest_harness.h"
 #include "vm_util.h"
 
+#ifndef MADV_COLLAPSE
+#define MADV_COLLAPSE 25
+#endif
+
 /* Reclaim can fail transiently, so retry for up to a second. */
 #define PAGEOUT_RETRIES 100
 
 static size_t page_size;
+static size_t pmd_size;
 /* Whether MADV_PAGEOUT can swap out anonymous memory, see main(). */
 static bool can_swap;
 
@@ -92,6 +98,111 @@ FIXTURE_TEARDOWN(madv_cold_pageout)
 	close(self->pagemap_fd);
 }
 
+/*
+ * Reserve a PMD-aligned range, so that memory mapped over it with MAP_FIXED
+ * can be backed by PMD-mapped THPs.
+ */
+static char *reserve_aligned(struct __test_metadata *_metadata, size_t size)
+{
+	char *reservation, *aligned;
+
+	reservation = mmap(NULL, size + pmd_size, PROT_NONE,
+			   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(reservation, MAP_FAILED);
+
+	aligned = (char *)(((uintptr_t)reservation + pmd_size - 1) &
+			   ~(pmd_size - 1));
+	if (aligned != reservation)
+		ASSERT_EQ(munmap(reservation, aligned - reservation), 0);
+	ASSERT_EQ(munmap(aligned + size, reservation + pmd_size - aligned), 0);
+
+	return aligned;
+}
+
+/* Map PMD-aligned anonymous memory filled with 1s. */
+static char *map_aligned_pages(struct __test_metadata *_metadata, size_t size)
+{
+	char *mapping = reserve_aligned(_metadata, size);
+
+	ASSERT_EQ(mmap(mapping, size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0),
+		  mapping);
+	memset(mapping, 1, size);
+
+	return mapping;
+}
+
+/*
+ * Collapse a PMD-aligned range into PMD-mapped THPs, and confirm the result
+ * with check_huge(). MADV_COLLAPSE may fail transiently with EAGAIN.
+ */
+static bool collapse(char *mapping, size_t size,
+		     bool (*check_huge)(void *, size_t, int, uint64_t))
+{
+	int nr_hpages = size / pmd_size;
+	int retry;
+
+	for (retry = 0; retry < 10; retry++) {
+		if (madvise(mapping, size, MADV_COLLAPSE)) {
+			if (errno != EAGAIN)
+				return false;
+		} else if (check_huge(mapping, size, nr_hpages, pmd_size)) {
+			return true;
+		}
+		usleep(10000);
+	}
+
+	return false;
+}
+
+/* Map a PMD-mapped anonymous THP filled with 1s, or return NULL. */
+static char *map_thp(struct __test_metadata *_metadata)
+{
+	char *mapping;
+
+	if (!pmd_size)
+		return NULL;
+	mapping = map_aligned_pages(_metadata, pmd_size);
+	if (collapse(mapping, pmd_size, check_huge_anon))
+		return mapping;
+	ASSERT_EQ(munmap(mapping, pmd_size), 0);
+	return NULL;
+}
+
+/*
+ * Changing the protection of one page PTE-maps a THP. When /proc/kpageflags
+ * is readable, also check that the folio is still large.
+ */
+static void pte_map_thp(struct __test_metadata *_metadata, char *mapping,
+			int pagemap_fd, int kpageflags_fd)
+{
+	unsigned long pfn;
+	uint64_t flags;
+
+	ASSERT_EQ(mprotect(mapping + page_size, page_size, PROT_READ), 0);
+	ASSERT_EQ(mprotect(mapping + page_size, page_size,
+			   PROT_READ | PROT_WRITE), 0);
+	ASSERT_TRUE(check_huge_anon(mapping, pmd_size, 0, pmd_size));
+	if (kpageflags_fd < 0)
+		return;
+
+	pfn = pagemap_get_pfn(pagemap_fd, mapping);
+	ASSERT_NE(pfn, -1UL);
+	ASSERT_EQ(pageflags_get(pfn, kpageflags_fd, &flags), 0);
+	ASSERT_TRUE(flags & KPF_COMPOUND_HEAD);
+}
+
+static void assert_filled(struct __test_metadata *_metadata,
+			  const char *mapping, size_t size)
+{
+	size_t offset;
+
+	for (offset = 0; offset < size; offset += page_size) {
+		ASSERT_EQ(mapping[offset], 1)
+			TH_LOG("memory changed at offset %zu", offset);
+	}
+}
+
 /* Page out the range until its first page is swapped, or madvise() fails. */
 static bool pageout_until_swapped(char *mapping, size_t size, int pagemap_fd)
 {
@@ -124,6 +235,11 @@ static bool pageout_until_unmapped(char *mapping, size_t size, int pagemap_fd)
 	return false;
 }
 
+static bool page_is_present(int pagemap_fd, char *addr)
+{
+	return pagemap_get_entry(pagemap_fd, addr) & PM_PRESENT;
+}
+
 /* Create an unlinked, zero-filled temporary file, or return -1. */
 static int create_temp_file(size_t size)
 {
@@ -452,6 +568,127 @@ TEST_F(madv_cold_pageout, pageout_skips_unevictable_page)
 	ASSERT_EQ(shmdt(mapping), 0);
 }
 
+/* MADV_PAGEOUT of a whole THP must swap it without corrupting it. */
+TEST_F(madv_cold_pageout, pageout_swaps_thp)
+{
+	char *mapping;
+
+	if (!can_swap)
+		SKIP(return, "MADV_PAGEOUT cannot swap");
+	mapping = map_thp(_metadata);
+	if (!mapping)
+		SKIP(return, "could not allocate a PMD-sized THP");
+
+	ASSERT_TRUE(pageout_until_swapped(mapping, pmd_size, self->pagemap_fd));
+	assert_filled(_metadata, mapping, pmd_size);
+
+	ASSERT_EQ(munmap(mapping, pmd_size), 0);
+}
+
+/*
+ * MADV_PAGEOUT must not reclaim pages outside the advised range. kswapd or
+ * page migration could also unmap them, but neither should happen when the
+ * selftests run by themselves.
+ */
+TEST_F(madv_cold_pageout, pageout_stays_within_range)
+{
+	char *mapping;
+
+	if (!can_swap)
+		SKIP(return, "MADV_PAGEOUT cannot swap");
+	mapping = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	memset(mapping, 1, 3 * page_size);
+	ASSERT_TRUE(pageout_until_swapped(mapping + page_size, page_size,
+					  self->pagemap_fd));
+
+	ASSERT_TRUE(page_is_present(self->pagemap_fd, mapping));
+	ASSERT_TRUE(page_is_present(self->pagemap_fd, mapping + 2 * page_size));
+	assert_filled(_metadata, mapping, 3 * page_size);
+
+	ASSERT_EQ(munmap(mapping, 3 * page_size), 0);
+}
+
+/*
+ * MADV_PAGEOUT of half of a THP must split it, and reclaim only the advised
+ * half. See pageout_stays_within_range about other unmapping.
+ */
+static void pageout_half_thp(struct __test_metadata *_metadata, char *mapping,
+			     int pagemap_fd)
+{
+	size_t offset;
+
+	ASSERT_TRUE(pageout_until_swapped(mapping, pmd_size / 2, pagemap_fd));
+
+	for (offset = pmd_size / 2; offset < pmd_size; offset += page_size) {
+		ASSERT_TRUE(page_is_present(pagemap_fd, mapping + offset))
+			TH_LOG("page at offset %zu was unmapped", offset);
+	}
+	assert_filled(_metadata, mapping, pmd_size);
+}
+
+TEST_F(madv_cold_pageout, pageout_stays_within_pmd_mapped_thp)
+{
+	char *mapping;
+
+	if (!can_swap)
+		SKIP(return, "MADV_PAGEOUT cannot swap");
+	mapping = map_thp(_metadata);
+	if (!mapping)
+		SKIP(return, "could not allocate a PMD-sized THP");
+
+	pageout_half_thp(_metadata, mapping, self->pagemap_fd);
+
+	ASSERT_EQ(munmap(mapping, pmd_size), 0);
+}
+
+TEST_F(madv_cold_pageout, pageout_stays_within_pte_mapped_thp)
+{
+	char *mapping;
+
+	if (!can_swap)
+		SKIP(return, "MADV_PAGEOUT cannot swap");
+	mapping = map_thp(_metadata);
+	if (!mapping)
+		SKIP(return, "could not allocate a PMD-sized THP");
+	pte_map_thp(_metadata, mapping, self->pagemap_fd, self->kpageflags_fd);
+
+	pageout_half_thp(_metadata, mapping, self->pagemap_fd);
+
+	ASSERT_EQ(munmap(mapping, pmd_size), 0);
+}
+
+/* MADV_COLD on part of a THP may split it, but must not change it. */
+TEST_F(madv_cold_pageout, cold_preserves_half_of_pmd_mapped_thp)
+{
+	char *mapping;
+
+	mapping = map_thp(_metadata);
+	if (!mapping)
+		SKIP(return, "could not allocate a PMD-sized THP");
+
+	ASSERT_EQ(madvise(mapping, pmd_size / 2, MADV_COLD), 0);
+	assert_filled(_metadata, mapping, pmd_size);
+
+	ASSERT_EQ(munmap(mapping, pmd_size), 0);
+}
+
+TEST_F(madv_cold_pageout, cold_preserves_half_of_pte_mapped_thp)
+{
+	char *mapping;
+
+	mapping = map_thp(_metadata);
+	if (!mapping)
+		SKIP(return, "could not allocate a PMD-sized THP");
+	pte_map_thp(_metadata, mapping, self->pagemap_fd, self->kpageflags_fd);
+
+	ASSERT_EQ(madvise(mapping, pmd_size / 2, MADV_COLD), 0);
+	assert_filled(_metadata, mapping, pmd_size);
+
+	ASSERT_EQ(munmap(mapping, pmd_size), 0);
+}
+
 /* Swap out an anonymous page, to see whether swap works at all. */
 static bool probe_swap(int pagemap_fd)
 {
@@ -474,6 +711,7 @@ int main(int argc, char **argv)
 	int pagemap_fd;
 
 	page_size = getpagesize();
+	pmd_size = read_pmd_pagesize();
 	/* The harness forks each test, which inherits this. */
 	pin_to_current_cpu();
 	/* Only root can reserve the page that hugetlb_vma_is_rejected maps. */
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 6/7] selftests/mm: check MADV_PAGEOUT file permissions
  2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
                   ` (4 preceding siblings ...)
  2026-10-19 17:12 ` [PATCH v3 5/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT on partly advised THPs Gregory Price (Meta)
@ 2026-10-19 17:12 ` Gregory Price (Meta)
  2026-10-28 11:25   ` Sarthak Sharma
  2026-10-19 17:12 ` [PATCH v3 7/7] selftests/mm: check that unauthorized MADV_PAGEOUT skips large file folios Gregory Price (Meta)
  6 siblings, 1 reply; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:12 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah

MADV_PAGEOUT only pages out file pages for a caller that owns the file or
could open it for writing, per can_do_file_pageout(). Any other caller
gets nothing from a shared file mapping, and only the anonymous COW pages
from a private one.

main() now also checks once whether MADV_PAGEOUT can unmap a clean page
of a file in /tmp, where the tests make their files. The tests below skip
if it cannot, since none of them would show anything.

Check that the owner of a read-only file can page out a clean page of
it. When started as root, the test hands the file to nobody and drops
privileges, so that capabilities cannot stand in for ownership and
nobody need not be able to write to /tmp.

Also check that an unauthorized caller leaves a shared file page mapped,
and that it pages out the COW page of a private mapping but not the file
page next to it. These need root to change credentials, and skip if that
fails. The private mapping check also skips if MADV_PAGEOUT cannot swap.

Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
 .../testing/selftests/mm/madv_cold_pageout.c  | 127 ++++++++++++++++++
 1 file changed, 127 insertions(+)

diff --git a/tools/testing/selftests/mm/madv_cold_pageout.c b/tools/testing/selftests/mm/madv_cold_pageout.c
index b5eec490fb68..f5a81d7bd08b 100644
--- a/tools/testing/selftests/mm/madv_cold_pageout.c
+++ b/tools/testing/selftests/mm/madv_cold_pageout.c
@@ -5,6 +5,7 @@
 #define _GNU_SOURCE
 #include <errno.h>
 #include <fcntl.h>
+#include <grp.h>
 #include <sched.h>
 #include <stdbool.h>
 #include <stdint.h>
@@ -29,6 +30,8 @@ static size_t page_size;
 static size_t pmd_size;
 /* Whether MADV_PAGEOUT can swap out anonymous memory, see main(). */
 static bool can_swap;
+/* Whether MADV_PAGEOUT can unmap clean file pages in /tmp, see main(). */
+static bool can_unmap_file;
 
 /*
  * Keep this task on one CPU, so that draining that CPU's LRU batches drains
@@ -276,6 +279,15 @@ static char *map_temp_file(struct __test_metadata *_metadata, size_t size,
 	return mapping;
 }
 
+/* The uid and gid of nobody. */
+#define NOBODY 65534
+
+/* Become nobody, an unprivileged user without capabilities. */
+static bool drop_privileges(void)
+{
+	return !setgroups(0, NULL) && !setgid(NOBODY) && !setuid(NOBODY);
+}
+
 /*
  * Both hints are best effort, so these tests check what userspace can rely
  * on: which mappings are accepted, which pages must never be reclaimed, and
@@ -689,6 +701,97 @@ TEST_F(madv_cold_pageout, cold_preserves_half_of_pte_mapped_thp)
 	ASSERT_EQ(munmap(mapping, pmd_size), 0);
 }
 
+/*
+ * The owner of a file may page out its clean page-cache pages. Run as an
+ * unprivileged owner, so that capabilities cannot stand in for ownership.
+ */
+TEST_F(madv_cold_pageout, pageout_unmaps_owned_file_page)
+{
+	char *mapping;
+	int fd;
+
+	if (!can_unmap_file)
+		SKIP(return, "MADV_PAGEOUT cannot unmap file pages");
+	fd = create_temp_file(page_size);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(fchmod(fd, 0400), 0);
+	/* Hand the file over first, as nobody may not be able to write /tmp. */
+	if (!geteuid()) {
+		ASSERT_EQ(fchown(fd, NOBODY, NOBODY), 0);
+		if (!drop_privileges())
+			SKIP(return, "could not drop privileges");
+	}
+	mapping = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE,
+		       fd, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	close(fd);
+
+	ASSERT_EQ(mapping[0], 0);
+	ASSERT_TRUE(pageout_until_unmapped(mapping, page_size,
+					   self->pagemap_fd));
+	ASSERT_EQ(mapping[0], 0);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+/*
+ * A caller that neither owns nor may write a file must not page out its
+ * shared mappings. MADV_PAGEOUT succeeds but leaves the page mapped.
+ */
+TEST_F(madv_cold_pageout, pageout_skips_unauthorized_shared_file)
+{
+	char *mapping;
+
+	/* Otherwise the page would stay mapped regardless. */
+	if (!can_unmap_file)
+		SKIP(return, "MADV_PAGEOUT cannot unmap file pages");
+	if (geteuid())
+		SKIP(return, "requires root to change credentials");
+	mapping = map_temp_file(_metadata, page_size, MAP_SHARED, 0400);
+	if (!drop_privileges())
+		SKIP(return, "could not drop privileges");
+
+	ASSERT_EQ(mapping[0], 0);
+	ASSERT_TRUE(pagemap_is_populated(self->pagemap_fd, mapping));
+	ASSERT_EQ(madvise(mapping, page_size, MADV_PAGEOUT), 0);
+	ASSERT_TRUE(pagemap_is_populated(self->pagemap_fd, mapping));
+	ASSERT_EQ(mapping[0], 0);
+
+	ASSERT_EQ(munmap(mapping, page_size), 0);
+}
+
+/*
+ * A caller that neither owns nor may write a file may page out the anonymous
+ * COW pages of a private mapping of it, but must leave the file pages
+ * mapped.
+ */
+TEST_F(madv_cold_pageout, pageout_private_file_reclaims_only_cow)
+{
+	char *mapping;
+
+	if (!can_swap || !can_unmap_file)
+		SKIP(return, "MADV_PAGEOUT cannot swap or unmap file pages");
+	if (geteuid())
+		SKIP(return, "requires root to change credentials");
+	mapping = map_temp_file(_metadata, 2 * page_size, MAP_PRIVATE,
+				0400);
+	if (!drop_privileges())
+		SKIP(return, "could not drop privileges");
+
+	ASSERT_EQ(mapping[0], 0);
+	ASSERT_EQ(mapping[page_size], 0);
+	mapping[0] = 1;
+
+	ASSERT_TRUE(pageout_until_swapped(mapping, 2 * page_size,
+					  self->pagemap_fd));
+	ASSERT_TRUE(pagemap_is_populated(self->pagemap_fd,
+					 mapping + page_size));
+	ASSERT_EQ(mapping[0], 1);
+	ASSERT_EQ(mapping[page_size], 0);
+
+	ASSERT_EQ(munmap(mapping, 2 * page_size), 0);
+}
+
 /* Swap out an anonymous page, to see whether swap works at all. */
 static bool probe_swap(int pagemap_fd)
 {
@@ -706,6 +809,27 @@ static bool probe_swap(int pagemap_fd)
 	return swapped;
 }
 
+/* Unmap a clean page of a file in /tmp, where the tests make their files. */
+static bool probe_file_unmap(int pagemap_fd)
+{
+	bool unmapped;
+	char *page;
+	int fd;
+
+	fd = create_temp_file(page_size);
+	if (fd < 0)
+		ksft_exit_fail_perror("create_temp_file");
+	page = mmap(NULL, page_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (page == MAP_FAILED)
+		ksft_exit_fail_perror("mmap");
+	close(fd);
+	FORCE_READ(page[0]);
+	unmapped = pageout_until_unmapped(page, page_size, pagemap_fd);
+	munmap(page, page_size);
+
+	return unmapped;
+}
+
 int main(int argc, char **argv)
 {
 	int pagemap_fd;
@@ -724,6 +848,9 @@ int main(int argc, char **argv)
 	can_swap = probe_swap(pagemap_fd);
 	if (!can_swap)
 		ksft_print_msg("MADV_PAGEOUT cannot swap\n");
+	can_unmap_file = probe_file_unmap(pagemap_fd);
+	if (!can_unmap_file)
+		ksft_print_msg("MADV_PAGEOUT cannot unmap file pages\n");
 	close(pagemap_fd);
 
 	return test_harness_run(argc, argv);
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 7/7] selftests/mm: check that unauthorized MADV_PAGEOUT skips large file folios
  2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
                   ` (5 preceding siblings ...)
  2026-10-19 17:12 ` [PATCH v3 6/7] selftests/mm: check MADV_PAGEOUT file permissions Gregory Price (Meta)
@ 2026-10-19 17:12 ` Gregory Price (Meta)
  6 siblings, 0 replies; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-19 17:12 UTC (permalink / raw)
  To: linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah, Sarthak Sharma

A caller that neither owns nor may write a file may page out the COW
pages of a private mapping of it, but not its file pages. When the
advised range only partly covers a large file folio, the folio must be
filtered out before it is split, on separate paths for PMD- and
PTE-mapped folios.

Splitting a file folio unmaps all of it, and a partly covered folio can
only be reclaimed after a split, so a split is the failure to look for.

Check that partial MADV_PAGEOUT by such a caller leaves every mapped page
of a PMD-sized shmem folio mapped, both when a PMD maps it and when a
mapping of half of it maps it with PTEs. Since this checks for a split
rather than for reclaim, it needs no swap.

Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
 .../testing/selftests/mm/madv_cold_pageout.c  | 114 ++++++++++++++++++
 1 file changed, 114 insertions(+)

diff --git a/tools/testing/selftests/mm/madv_cold_pageout.c b/tools/testing/selftests/mm/madv_cold_pageout.c
index f5a81d7bd08b..5a226ccbacb2 100644
--- a/tools/testing/selftests/mm/madv_cold_pageout.c
+++ b/tools/testing/selftests/mm/madv_cold_pageout.c
@@ -195,6 +195,33 @@ static void pte_map_thp(struct __test_metadata *_metadata, char *mapping,
 	ASSERT_TRUE(flags & KPF_COMPOUND_HEAD);
 }
 
+static char *map_aligned_file(struct __test_metadata *_metadata, int fd,
+			      size_t size, int flags)
+{
+	char *mapping = reserve_aligned(_metadata, size);
+
+	ASSERT_EQ(mmap(mapping, size, PROT_READ | PROT_WRITE, flags | MAP_FIXED,
+		       fd, 0), mapping);
+
+	return mapping;
+}
+
+/* Create a PMD-sized shmem file filled with 1s. */
+static int create_shmem_file(struct __test_metadata *_metadata)
+{
+	char *shared;
+	int fd;
+
+	fd = memfd_create("madv_cold_pageout", 0);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(ftruncate(fd, pmd_size), 0);
+	shared = map_aligned_file(_metadata, fd, pmd_size, MAP_SHARED);
+	memset(shared, 1, pmd_size);
+	ASSERT_EQ(munmap(shared, pmd_size), 0);
+
+	return fd;
+}
+
 static void assert_filled(struct __test_metadata *_metadata,
 			  const char *mapping, size_t size)
 {
@@ -792,6 +819,93 @@ TEST_F(madv_cold_pageout, pageout_private_file_reclaims_only_cow)
 	ASSERT_EQ(munmap(mapping, 2 * page_size), 0);
 }
 
+/*
+ * An unauthorized caller must leave a large file folio of a private mapping
+ * alone, even when the advised range only partly covers it. Splitting the
+ * folio would unmap all of it, so every page must stay mapped, which needs
+ * no swap to check. See pageout_stays_within_range about other unmapping.
+ */
+TEST_F(madv_cold_pageout, pageout_private_file_skips_large_file_folio)
+{
+	char *mapping;
+	size_t offset;
+	int fd;
+
+	if (!pmd_size)
+		SKIP(return, "PMD-sized THPs are not supported");
+	if (geteuid())
+		SKIP(return, "requires root to change credentials");
+	fd = create_shmem_file(_metadata);
+	mapping = map_aligned_file(_metadata, fd, pmd_size, MAP_PRIVATE);
+	if (!collapse(mapping, pmd_size, check_huge_file))
+		SKIP(return, "could not allocate a PMD-sized shmem THP");
+	ASSERT_EQ(fchmod(fd, 0400), 0);
+	if (!drop_privileges())
+		SKIP(return, "could not drop privileges");
+
+	ASSERT_EQ(madvise(mapping, pmd_size / 2, MADV_PAGEOUT), 0);
+	for (offset = 0; offset < pmd_size; offset += page_size) {
+		ASSERT_TRUE(page_is_present(self->pagemap_fd,
+					    mapping + offset))
+			TH_LOG("page at offset %zu was unmapped", offset);
+	}
+	assert_filled(_metadata, mapping, pmd_size);
+
+	ASSERT_EQ(munmap(mapping, pmd_size), 0);
+	close(fd);
+}
+
+/*
+ * The same holds for a large file folio that a private mapping maps with
+ * PTEs: every page the mapping maps must stay mapped. See
+ * pageout_stays_within_range about other unmapping.
+ */
+TEST_F(madv_cold_pageout, pageout_private_file_skips_pte_mapped_file_folio)
+{
+	const size_t size = pmd_size / 2;
+	char *mapping, *shared;
+	unsigned long pfn;
+	uint64_t flags;
+	size_t offset;
+	int fd;
+
+	if (!pmd_size)
+		SKIP(return, "PMD-sized THPs are not supported");
+	if (geteuid())
+		SKIP(return, "requires root to change credentials");
+	if (self->kpageflags_fd < 0)
+		SKIP(return, "cannot read page flags");
+	fd = create_shmem_file(_metadata);
+	shared = map_aligned_file(_metadata, fd, pmd_size, MAP_SHARED);
+	if (!collapse(shared, pmd_size, check_huge_file))
+		SKIP(return, "could not allocate a PMD-sized shmem THP");
+	ASSERT_EQ(munmap(shared, pmd_size), 0);
+
+	/* Mapping half of the folio leaves it mapped by PTEs. */
+	mapping = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+	ASSERT_NE(mapping, MAP_FAILED);
+	assert_filled(_metadata, mapping, size);
+	pfn = pagemap_get_pfn(self->pagemap_fd, mapping);
+	ASSERT_NE(pfn, -1UL);
+	ASSERT_EQ(pageflags_get(pfn, self->kpageflags_fd, &flags), 0);
+	if (!(flags & KPF_COMPOUND_HEAD))
+		SKIP(return, "the shmem THP was split");
+	ASSERT_EQ(fchmod(fd, 0400), 0);
+	if (!drop_privileges())
+		SKIP(return, "could not drop privileges");
+
+	ASSERT_EQ(madvise(mapping, size / 2, MADV_PAGEOUT), 0);
+	for (offset = 0; offset < size; offset += page_size) {
+		ASSERT_TRUE(page_is_present(self->pagemap_fd,
+					    mapping + offset))
+			TH_LOG("page at offset %zu was unmapped", offset);
+	}
+	assert_filled(_metadata, mapping, size);
+
+	ASSERT_EQ(munmap(mapping, size), 0);
+	close(fd);
+}
+
 /* Swap out an anonymous page, to see whether swap works at all. */
 static bool probe_swap(int pagemap_fd)
 {
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 1/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests
  2026-10-19 17:12 ` [PATCH v3 1/7] " Gregory Price (Meta)
@ 2026-10-28 11:22   ` Sarthak Sharma
  2026-10-28 13:43     ` Gregory Price (Meta)
  0 siblings, 1 reply; 13+ messages in thread
From: Sarthak Sharma @ 2026-10-28 11:22 UTC (permalink / raw)
  To: Gregory Price (Meta), linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah



On 10/19/26 10:42 PM, Gregory Price (Meta) wrote:
> MADV_COLD and MADV_PAGEOUT share a page-table walker covering ordinary
> PTEs, PTE-mapped large folios and huge PMDs. Existing selftests use
> MADV_PAGEOUT mostly to swap memory out for other tests, and check only
> special mappings (PFNMAP, guard regions, droppable memory), not the
> walker's range, folio and permission handling.
> 
> Add a kselftest harness test for both hints, starting with the most basic
> behavior userspace relies on: neither hint populates a PTE hole or faults
> a swapped page back in, and neither may be applied to a locked VMA.
> 
> Before running the tests, pin the process to the CPU it starts on,
> so that the LRU batch MADV_COLD and MADV_PAGEOUT drain holds the
> folios a test faulted in.
> 
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
> v3: Dropped Sarthak's Reviewed-by from v2 1/8 and 2/8, which are folded
> here, since this patch now adds main() with the CPU pinning and the
> one-time reclaim check, and the swapped-page tests fail rather than skip.
Thanks for changing the name of the selftest. Adding the swap check once
in main() also looks good.

Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT range handling
  2026-10-19 17:12 ` [PATCH v3 2/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT range handling Gregory Price (Meta)
@ 2026-10-28 11:23   ` Sarthak Sharma
  0 siblings, 0 replies; 13+ messages in thread
From: Sarthak Sharma @ 2026-10-28 11:23 UTC (permalink / raw)
  To: Gregory Price (Meta), linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah



On 10/19/26 10:42 PM, Gregory Price (Meta) wrote:
> madvise(2) accepts a zero-length range, rejects an unaligned start, and
> reports an unmapped gap in the range with ENOMEM while still applying the
> advice to the mapped pages on either side.
> 
> Check each of these for both hints. Around the gap, only MADV_PAGEOUT has
> an effect the test can observe, so only MADV_PAGEOUT is checked for
> paging out both neighbors, and only if MADV_PAGEOUT can swap at all.
> Both hints' ENOMEM is checked first, so it is covered without swap.
> 
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
Looks good to me.

Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 4/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT preserve shared memory
  2026-10-19 17:12 ` [PATCH v3 4/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT preserve shared memory Gregory Price (Meta)
@ 2026-10-28 11:24   ` Sarthak Sharma
  0 siblings, 0 replies; 13+ messages in thread
From: Sarthak Sharma @ 2026-10-28 11:24 UTC (permalink / raw)
  To: Gregory Price (Meta), linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah



On 10/19/26 10:42 PM, Gregory Price (Meta) wrote:
> Both hints are non-destructive, but the tests so far only check that for
> private anonymous memory. Shared memory reaches reclaim differently:
> 
> Test the following:
>  - a dirty shared file page must be written back or left resident,
>  - shared anonymous memory is paged out through shmem
>  - a SHM_LOCK'd segment is unevictable without its VMA being VM_LOCKED.
> 
> MADV_PAGEOUT unmaps a shmem page before it tries to swap it out
> so none of these needs swap.
> 
> The SHM_LOCK test confirms the page is unevictable and skips otherwise.
> 
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
> v3: Dropped Sarthak's Reviewed-by from v2 5/8: added the fixture check
>     for hidden PFNs and create_temp_file(), and the shared anonymous
>     test now fails rather than skips when the page is not unmapped.


LGTM, so:

Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 6/7] selftests/mm: check MADV_PAGEOUT file permissions
  2026-10-19 17:12 ` [PATCH v3 6/7] selftests/mm: check MADV_PAGEOUT file permissions Gregory Price (Meta)
@ 2026-10-28 11:25   ` Sarthak Sharma
  0 siblings, 0 replies; 13+ messages in thread
From: Sarthak Sharma @ 2026-10-28 11:25 UTC (permalink / raw)
  To: Gregory Price (Meta), linux-kselftest
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, shuah



On 10/19/26 10:42 PM, Gregory Price (Meta) wrote:
> MADV_PAGEOUT only pages out file pages for a caller that owns the file or
> could open it for writing, per can_do_file_pageout(). Any other caller
> gets nothing from a shared file mapping, and only the anonymous COW pages
> from a private one.
> 
> main() now also checks once whether MADV_PAGEOUT can unmap a clean page
> of a file in /tmp, where the tests make their files. The tests below skip
> if it cannot, since none of them would show anything.
> 
> Check that the owner of a read-only file can page out a clean page of
> it. When started as root, the test hands the file to nobody and drops
> privileges, so that capabilities cannot stand in for ownership and
> nobody need not be able to write to /tmp.
> 
> Also check that an unauthorized caller leaves a shared file page mapped,
> and that it pages out the COW page of a private mapping but not the file
> page next to it. These need root to change credentials, and skip if that
> fails. The private mapping check also skips if MADV_PAGEOUT cannot swap.
> 
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
Thanks for the changes. It looks good to me.

Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 1/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests
  2026-10-28 11:22   ` Sarthak Sharma
@ 2026-10-28 13:43     ` Gregory Price (Meta)
  0 siblings, 0 replies; 13+ messages in thread
From: Gregory Price (Meta) @ 2026-10-28 13:43 UTC (permalink / raw)
  To: Sarthak Sharma
  Cc: Gregory Price (Meta), linux-kselftest, linux-mm, linux-kernel,
	kernel-team, akpm, david, ljs, liam, vbabka, rppt, surenb, mhocko,
	shuah

On 2026-10-28 16:52 +0530, Sarthak Sharma wrote:
> 
> > v3: Dropped Sarthak's Reviewed-by from v2 1/8 and 2/8, which are folded
> > here, since this patch now adds main() with the CPU pinning and the
> > one-time reclaim check, and the swapped-page tests fail rather than skip.
> Thanks for changing the name of the selftest. Adding the swap check once
> in main() also looks good.
> 
> Reviewed-by: Sarthak Sharma <sarthak.sharma@arm.com>
> 

Thank you for the reviews!

-- 
~Gregory



^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-10-28 13:43 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19 17:11 [PATCH v3 0/7] selftests/mm: add MADV_COLD and MADV_PAGEOUT contract tests Gregory Price (Meta)
2026-10-19 17:12 ` [PATCH v3 1/7] " Gregory Price (Meta)
2026-10-28 11:22   ` Sarthak Sharma
2026-10-28 13:43     ` Gregory Price (Meta)
2026-10-19 17:12 ` [PATCH v3 2/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT range handling Gregory Price (Meta)
2026-10-28 11:23   ` Sarthak Sharma
2026-10-19 17:12 ` [PATCH v3 3/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT reject hugetlb Gregory Price (Meta)
2026-10-19 17:12 ` [PATCH v3 4/7] selftests/mm: check that MADV_COLD and MADV_PAGEOUT preserve shared memory Gregory Price (Meta)
2026-10-28 11:24   ` Sarthak Sharma
2026-10-19 17:12 ` [PATCH v3 5/7] selftests/mm: check MADV_COLD and MADV_PAGEOUT on partly advised THPs Gregory Price (Meta)
2026-10-19 17:12 ` [PATCH v3 6/7] selftests/mm: check MADV_PAGEOUT file permissions Gregory Price (Meta)
2026-10-28 11:25   ` Sarthak Sharma
2026-10-19 17:12 ` [PATCH v3 7/7] selftests/mm: check that unauthorized MADV_PAGEOUT skips large file folios Gregory Price (Meta)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox