Thread (23 messages) flat view 23 messages, 4 authors, 2026-08-14

Re: [PATCH v5 00/16] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff

From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Date: 2026-08-14 09:01:55
Also in: amd-gfx, dri-devel, intel-xe, kvm, linux-fsdevel, linux-kselftest, linux-mm, linux-perf-users, linux-s390, lkml

On Thu, Aug 13, 2026 at 11:53:46AM -0700, Andrew Morton wrote:
You'll be mortified to hear that Sashiko wasn't able to find anything
to which to apply this.
:))

Well, when it's right it's useful, when it's wrong or suggesting unrelated
what-nots it's less useful :>)

I do locally put things through claude + Chris Mason's prompts a lot, I
don't always invoke local sashiko as it's very slow and token-heavy or has
been so far, but am planning to do that more also in future.
Sashiko can be guided with a base-commit: tag but I'm not sure how to
tell it what tree/branch to try, or even if that's necessary.  Perhaps
someone can figure this out sometime.
b4 gives a base commit, but I think because the trees are rebased it ends
up being the incorrect one.

Not sure what the solution is!
maybe

hp2:/usr/src/linux-next> git log --oneline | grep "mm/vma: introduce VMA anon page offset field and add helpers"
249646a587dc mm/vma: introduce VMA anon page offset field and add helpers

base-commit: 249646a587dc^

But that requires that Sashiko be able to poke around in linux-next
from previous days.
quoted
v5:
* Accumulated tags (thanks everybody!)
* Removed the final 4 patches to be handled later as there are nuances with
  the /dev/zero stuff we need to figure out, as discussed with David.
* Updated the cover letter to reflect this.
* Added comments to vma_flags_is_cow_mapping(),
  vma_[desc_]is_cow_mapping() as per Suren.
* Correct typo as per Suren.
* Reworded test comment in patch 16 from 'fault in' to 'trigger a CoW
  fault' as per David.
* Fix -> 75 char limit in patch 12's commit msg.
Here's how v5 altered mm.git.  It's rather substantial, but mainly
selftests:
Thanks for the diff, always useful!

The noise it's mostly because of dropping the final 4 commits, and as you
say mostly test stuff that will be sent with whichever approach we decide
on for MAP_PRIVATE-/dev/zero in the next cycle.

The actual changes elsewhere are rather trivial otherwise.

What remains, targeting 2nd week of the merge window, is heavily tested +
fully reviewed, so all is still very sane :)
quoted hunk ↗ jump to hunk

 drivers/char/mem.c                                     |    8
 include/linux/mm.h                                     |   18 -
 include/linux/pagemap.h                                |    3
 mm/internal.h                                          |   17 -
 mm/vma.c                                               |   52 ----
 mm/vma.h                                               |    3
 mm/vma_internal.h                                      |    1
 tools/testing/selftests/mm/merge.c                     |  106 ----------
 tools/testing/selftests/proc/proc-self-map-files-001.c |    2
 tools/testing/selftests/proc/proc-self-map-files-002.c |    2
 tools/testing/vma/include/dup.h                        |   40 ---
 tools/testing/vma/tests/mmap.c                         |   50 ----
 12 files changed, 40 insertions(+), 262 deletions(-)
--- a/drivers/char/mem.c~b
+++ a/drivers/char/mem.c
@@ -506,7 +506,11 @@ static int mmap_zero_prepare(struct vm_a
 	if (vma_desc_test(desc, VMA_SHARED_BIT))
 		return shmem_zero_setup_desc(desc);

-	/* MAP_PRIVATE semantics are taken care for us by core mm. */
+	/*
+	 * This is a highly unique situation where we mark a MAP_PRIVATE mapping
+	 * of /dev/zero anonymous, despite it not being.
+	 */
+	vma_desc_set_anonymous(desc);
 	return 0;
 }
@@ -694,7 +698,7 @@ static const struct memdev {
 #ifdef CONFIG_DEVPORT
 	[4] = { "port", &port_fops, 0, 0 },
 #endif
-	[DEVZERO_MINOR] = { "zero", &zero_fops, FMODE_NOWAIT, 0666 },
+	[5] = { "zero", &zero_fops, FMODE_NOWAIT, 0666 },
 	[7] = { "full", &full_fops, 0, 0666 },
 	[8] = { "random", &random_fops, FMODE_NOWAIT, 0666 },
 	[9] = { "urandom", &urandom_fops, FMODE_NOWAIT, 0666 },
--- a/include/linux/mm.h~b
+++ a/include/linux/mm.h
@@ -740,9 +740,6 @@ static inline bool fault_flag_allow_retr
 	{ FAULT_FLAG_INTERRUPTIBLE,	"INTERRUPTIBLE" }, \
 	{ FAULT_FLAG_VMA_LOCK,		"VMA_LOCK" }

-/* /dev/zero minor device number. Special due to MAP_PRIVATE semantics. */
-#define DEVZERO_MINOR	5
-
 /*
  * vm_fault is filled by the pagefault handler and passed to the vma's
  * ->fault function. The vma's ->fault is responsible for returning a bitmask
@@ -1554,6 +1551,11 @@ static inline void vma_set_anonymous(str
 	vma->vm_ops = NULL;
 }

+static inline void vma_desc_set_anonymous(struct vm_area_desc *desc)
+{
+	desc->vm_ops = NULL;
+}
+
 static inline bool vma_is_anonymous(const struct vm_area_struct *vma)
 {
 	return !vma->vm_ops;
@@ -2279,8 +2281,7 @@ void unpin_folios(struct folio **folios,
  * All mappings backed by anonymous folios (all anonymous mappings and most
  * MAP_PRIVATE-file backed ranges) are CoW mappings.
  *
- * All other mappings (including all writable MAP_SHARED mappings) are
- * non-CoW.
+ * All other mappings (including all MAP_SHARED mappings) are non-CoW.
  *
  * The criteria are !VMA_SHARED_BIT, VMA_MAYWRITE_BIT.
  *
@@ -2317,7 +2318,7 @@ static inline bool vma_flags_is_cow_mapp

 /**
  * vma_is_cow_mapping() - Is this VMA a CoW mapping?
- * @vma: The VMA to check.
+ * @desc: The VMA to check.
  *
  * See vma_flags_is_cow_mapping() for details.
  *
@@ -4407,8 +4408,9 @@ static inline unsigned long vma_pages(co
  * If @vma is a MAP_PRIVATE file-backed mapping, then this returns the
  * page offset within the file.
  *
- * Edge cases: nommu does not abide by these and CoW MAP_PRIVATE-pfnmap regions
- * have their page offset set to the first PFN in the range.
+ * Edge cases: nommu does not abide by these, MAP_PRIVATE-/dev/zero satisfies
+ * vma_is_anonymous() but has file-backed page offset, and MAP_PRIVATE-pfnmap
+ * regions have their page offset set to the first PFN in the range.
  *
  * Returns: The page offset of the start of @vma.
  */
--- a/include/linux/pagemap.h~b
+++ a/include/linux/pagemap.h
@@ -1128,7 +1128,8 @@ static inline pgoff_t linear_anon_page_i
 	const pgoff_t pgoff = __linear_anon_page_index(vma, address);

 	VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
-	if (vma_is_anonymous(vma))
+	/* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
+	if (vma_is_anonymous(vma) && !vma->vm_file)
 		VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));

 	return pgoff;
--- a/mm/internal.h~b
+++ a/mm/internal.h
@@ -240,18 +240,15 @@ static inline int mmap_file(struct file
 {
 	int err = vfs_mmap(file, vma);

+	if (likely(!err))
+		return 0;
+
 	/*
-	 * Either we tried to call the file hook for mmap() and an error arose
-	 * or a driver set vma->vm_ops = NULL intending there to be no VMA
-	 * operations.
-	 *
-	 * In the former case the VMA is in an inconsistent state and we mustn't
-	 * invoke any further hooks on it, in the latter case the hook actually
-	 * wanted no further hooks to be invoked, so fix both by setting dummy
-	 * VMA ops.
+	 * OK, we tried to call the file hook for mmap(), but an error
+	 * arose. The mapping is in an inconsistent state and we must not invoke
+	 * any further hooks on it.
 	 */
-	if (unlikely(err || !vma->vm_ops))
-		vma->vm_ops = &vma_dummy_vm_ops;
+	vma->vm_ops = &vma_dummy_vm_ops;

 	return err;
 }
--- a/mm/vma.c~b
+++ a/mm/vma.c
@@ -2621,36 +2621,6 @@ static int __mmap_new_file_vma(struct mm
 	return 0;
 }

-static bool map_is_dev_zero(const struct mmap_state *map)
-{
-	const struct file *file = map->file;
-	struct inode *inode;
-
-	if (!file)
-		return false;
-	inode = file_inode(file);
-	if (!S_ISCHR(inode->i_mode))
-		return false;
-	return imajor(inode) == MEM_MAJOR && iminor(inode) == DEVZERO_MINOR;
-}
-
-static void map_set_anon(struct mmap_state *map)
-{
-	map->file = NULL;
-	map->vm_ops = NULL;
-	map->pgoff = map->addr >> PAGE_SHIFT;
-}
-
-static bool map_is_private(const struct mmap_state *map)
-{
-	return !vma_flags_test(&map->vma_flags, VMA_SHARED_BIT);
-}
-
-static bool map_is_anon(const struct mmap_state *map)
-{
-	return map_is_private(map) && !map->file;
-}
-
 /*
  * __mmap_new_vma() - Allocate a new VMA for the region, as merging was not
  * possible.
@@ -2664,7 +2634,8 @@ static bool map_is_anon(const struct mma
 static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
 	struct mmap_action *action)
 {
-	const bool is_anon = map_is_anon(map);
+	const bool is_anon = !map->file &&
+		!vma_flags_test(&map->vma_flags, VMA_SHARED_BIT);
 	struct vma_iterator *vmi = map->vmi;
 	int error = 0;
 	struct vm_area_struct *vma;
@@ -2806,10 +2777,6 @@ static int call_mmap_prepare(struct mmap
 	if (err)
 		return err;

-	/* Hooks cannot mark themselves anonymous. */
-	if (!desc->vm_ops)
-		return -EINVAL;
-
 	err = call_action_prepare(map, desc);
 	if (err)
 		return err;
@@ -2826,21 +2793,16 @@ static int call_mmap_prepare(struct mmap
 	map->vm_ops = desc->vm_ops;
 	map->vm_private_data = desc->private_data;

-	/*
-	 * MAP_PRIVATE-/dev/zero mappings are an ancient way of getting
-	 * anonymous mappings. Rather than allowing these mappings to be odd
-	 * outliers, simply make them truly anonymous.
-	 */
-	if (map_is_private(map) && map_is_dev_zero(map))
-		map_set_anon(map);
-
 	return 0;
 }

 static void set_vma_user_defined_fields(struct vm_area_struct *vma,
 		struct mmap_state *map)
 {
-	vma->vm_ops = map->vm_ops;
+	if (map->vm_ops)
+		vma->vm_ops = map->vm_ops;
+	else	/* Only /dev/zero should do this. */
+		vma_set_anonymous(vma);
 	vma->vm_private_data = map->vm_private_data;
 }
@@ -2920,7 +2882,7 @@ static unsigned long __mmap_region(struc
 		allocated_new = true;
 	}

-	if (have_mmap_prepare && !map_is_anon(&map))
+	if (have_mmap_prepare)
 		set_vma_user_defined_fields(vma, &map);

 	__mmap_complete(&map, vma);
--- a/mm/vma.h~b
+++ a/mm/vma.h
@@ -267,6 +267,9 @@ static inline void assert_sane_pgoff(str
 	 */
 	if (!vma_is_anonymous(vma))
 		return;
+	/* MAP_PRIVATE-/dev/zero is anon, non-NULL vm_file, but has file pgoff. */
+	if (vma->vm_file)
+		return;
 	/* If faulted in, could have been remapped. */
 	if (vma->anon_vma)
 		return;
--- a/mm/vma_internal.h~b
+++ a/mm/vma_internal.h
@@ -23,7 +23,6 @@
 #include <linux/ksm.h>
 #include <linux/khugepaged.h>
 #include <linux/list.h>
-#include <linux/major.h>
 #include <linux/maple_tree.h>
 #include <linux/mempolicy.h>
 #include <linux/mm.h>
--- a/tools/testing/selftests/mm/merge.c~b
+++ a/tools/testing/selftests/mm/merge.c
@@ -1324,7 +1324,7 @@ TEST_F(merge, anon_and_page_offset_misma
 	ASSERT_NE(ptr, MAP_FAILED);

 	/*
-	 * Map another separately and trigger a CoW fault, at page offset 5:
+	 * Map another separately and trigger a CoW fault at page offset 5:
 	 *
 	 * |-----------|           |---------|
 	 * | unfaulted |           | faulted |
@@ -1362,110 +1362,6 @@ TEST_F(merge, anon_and_page_offset_misma
 	ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 5 * page_size);
 }

-TEST_F(merge, merge_map_private_dev_zero_unfaulted)
-{
-	struct procmap_fd *procmap = &self->procmap;
-	unsigned int page_size = self->page_size;
-	char *carveout = self->carveout;
-	char *ptr, *ptr2;
-	int fd_zero;
-
-	if (access("/dev/zero", F_OK))
-		SKIP(return, "No /dev/zero.");
-	fd_zero = open("/dev/zero", O_RDWR);
-	ASSERT_NE(fd_zero, -1);
-
-	/*
-	 * Map two MAP_PRIVATE-/dev/zero VMAs next to one another with offset 0
-	 * each.
-	 *
-	 * With these being made truly anonymous upon mapping, they will
-	 * merge. If they were file-backed VMAs the page offsets would prevent
-	 * merge:
-	 *
-	 * |-----||------|    |-------------|
-	 * | ptr || ptr2 | -> |     ptr     |
-	 * |-----||------|    |-------------|
-	 */
-	ptr = mmap(carveout, 5 * page_size, PROT_READ | PROT_WRITE,
-		   MAP_FIXED | MAP_PRIVATE, fd_zero, 0);
-	if (ptr == MAP_FAILED) {
-		close(fd_zero);
-		ASSERT_TRUE(false);
-	}
-	ptr2 = mmap(&carveout[5 * page_size], 5 * page_size,
-		   PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE, fd_zero, 0);
-	if (ptr2 == MAP_FAILED) {
-		close(fd_zero);
-		ASSERT_TRUE(false);
-	}
-	close(fd_zero);
-
-	/* Assert that they merged. */
-	ASSERT_TRUE(find_vma_procmap(procmap, ptr));
-	ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
-	ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size);
-}
-
-TEST_F(merge, merge_map_private_dev_zero_faulted_unfaulted)
-{
-	struct procmap_fd *procmap = &self->procmap;
-	unsigned int page_size = self->page_size;
-	char *carveout = self->carveout;
-	char *ptr, *ptr2;
-	int fd_zero;
-
-	if (access("/dev/zero", F_OK))
-		SKIP(return, "No /dev/zero.");
-	fd_zero = open("/dev/zero", O_RDWR);
-	ASSERT_NE(fd_zero, -1);
-
-	/*
-	 * Map a MAP_PRIVATE mapping of /dev/zero with page offset 0, then fault
-	 * it in:
-	 *
-	 * |-------------------------------|
-	 * |           faulted             |
-	 * |-------------------------------|
-	 */
-	ptr = mmap(carveout, 15 * page_size, PROT_READ | PROT_WRITE,
-		   MAP_FIXED | MAP_PRIVATE, fd_zero, 0);
-	if (ptr == MAP_FAILED) {
-		close(fd_zero);
-		ASSERT_TRUE(false);
-	}
-	memset(ptr, 'x', 15 * page_size);
-
-	/*
-	 * Unmap the middle:
-	 *
-	 * |---------|           |---------|
-	 * | faulted |           | faulted |
-	 * |---------|           |---------|
-	 */
-	ASSERT_EQ(munmap(&ptr[5 * page_size], 5 * page_size), 0);
-
-	/*
-	 * Map in a new unfaulted mapping in the middle with page offset 0 -
-	 * this should merge and would not if it were treated as a file rather
-	 * than pure anon:
-	 *
-	 * |---------|-----------|---------|
-	 * | faulted | unfaulted | faulted |
-	 * |---------|-----------|---------|
-	 */
-	ptr2 = mmap(&carveout[5 * page_size], 5 * page_size,
-		    PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE,
-		    fd_zero, 0);
-	close(fd_zero);
-	ASSERT_NE(ptr2, MAP_FAILED);
-
-	/* Assert that they merged. */
-	ASSERT_TRUE(find_vma_procmap(procmap, ptr));
-	ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
-	ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size);
-}
-
 TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_prev)
 {
 	struct procmap_fd *procmap = &self->procmap;
--- a/tools/testing/selftests/proc/proc-self-map-files-001.c~b
+++ a/tools/testing/selftests/proc/proc-self-map-files-001.c
@@ -51,7 +51,7 @@ int main(void)
 	int fd;
 	unsigned long a, b;

-	fd = open("/proc/self/exe", O_RDONLY);
+	fd = open("/dev/zero", O_RDONLY);
 	if (fd == -1)
 		return 1;

--- a/tools/testing/selftests/proc/proc-self-map-files-002.c~b
+++ a/tools/testing/selftests/proc/proc-self-map-files-002.c
@@ -57,7 +57,7 @@ int main(void)
 	int fd;
 	unsigned long a, b;

-	fd = open("/proc/self/exe", O_RDONLY);
+	fd = open("/dev/zero", O_RDONLY);
 	if (fd == -1)
 		return 1;

--- a/tools/testing/vma/include/dup.h~b
+++ a/tools/testing/vma/include/dup.h
@@ -15,21 +15,6 @@ struct task_struct *get_current(void);
 #define MMF_HAS_MDWE	28
 #define current get_current()

-#define MINORBITS	20
-#define MINORMASK	((1U << MINORBITS) - 1)
-
-#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
-#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
-#define MKDEV(ma, mi)	(((ma) << MINORBITS) | (mi))
-
-#define S_IFMT  00170000
-#define S_IFCHR  0020000
-
-#define S_ISCHR(m)	(((m) & S_IFMT) == S_IFCHR)
-
-#define MEM_MAJOR		1
-#define DEVZERO_MINOR	5
-
 /*
  * Define the task command name length as enum, then it can be visible to
  * BPF programs.
@@ -38,8 +23,6 @@ enum {
 	TASK_COMM_LEN = 16,
 };

-typedef unsigned short		umode_t;
-
 /* PARTIALLY implemented types. */
 struct mm_struct {
 	struct maple_tree mm_mt;
@@ -62,10 +45,6 @@ struct address_space {
 	unsigned long		flags;
 	atomic_t		i_mmap_writable;
 };
-struct inode {
-	umode_t			i_mode;
-	dev_t			i_rdev;
-};
 struct file_operations {
 	int (*mmap)(struct file *, struct vm_area_struct *);
 	int (*mmap_prepare)(struct vm_area_desc *);
@@ -73,7 +52,6 @@ struct file_operations {
 struct file {
 	struct address_space	*f_mapping;
 	const struct file_operations	*f_op;
-	struct inode			*f_inode;
 };
 struct anon_vma_chain {
 	struct anon_vma *anon_vma;
@@ -1660,23 +1638,9 @@ static inline pgoff_t linear_anon_page_i
 	const pgoff_t pgoff = __linear_anon_page_index(vma, address);

 	VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
-	if (vma_is_anonymous(vma))
+	/* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
+	if (vma_is_anonymous(vma) && !vma->vm_file)
 		VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));

 	return pgoff;
 }
-
-static inline struct inode *file_inode(const struct file *f)
-{
-	return f->f_inode;
-}
-
-static inline unsigned iminor(const struct inode *inode)
-{
-	return MINOR(inode->i_rdev);
-}
-
-static inline unsigned imajor(const struct inode *inode)
-{
-	return MAJOR(inode->i_rdev);
-}
--- a/tools/testing/vma/tests/mmap.c~b
+++ a/tools/testing/vma/tests/mmap.c
@@ -45,57 +45,7 @@ static bool test_mmap_region_basic(void)
 	return true;
 }

-static int dummy_mmap_prepare(struct vm_area_desc *desc)
-{
-	return 0;
-}
-
-static bool test_pure_anon_dev_zero(void)
-{
-	const vma_flags_t vma_flags = mk_vma_flags(VMA_READ_BIT, VMA_WRITE_BIT,
-			VMA_MAYREAD_BIT, VMA_MAYWRITE_BIT);
-	const struct file_operations f_op = {
-		.mmap_prepare = dummy_mmap_prepare,
-	};
-	struct inode inode = {
-		.i_mode = S_IFCHR,
-		.i_rdev = MKDEV(MEM_MAJOR, DEVZERO_MINOR),
-	};
-	struct file file = {
-		.f_inode = &inode,
-		.f_op = &f_op,
-	};
-	struct mm_struct mm = {};
-	struct vm_area_struct *vma;
-	unsigned long addr;
-	VMA_ITERATOR(vmi, &mm, 0);
-
-	current->mm = &mm;
-
-	/*
-	 * Map a MAP_PRIVATE-/dev/zero mapping at address 0x300000 with a page
-	 * offset of 0x10, which we expect to be reset to the anonymous page
-	 * offset.
-	 */
-	addr = __mmap_region(&file, 0x300000, 0x3000, vma_flags, 0x10, NULL);
-	ASSERT_EQ(addr, 0x300000);
-
-	/* Assert that it truly is an anonymous mapping. */
-	vma = vma_lookup(&mm, addr);
-	ASSERT_NE(vma, NULL);
-	ASSERT_TRUE(vma_is_anonymous(vma));
-	ASSERT_EQ(vma->vm_file, NULL);
-	ASSERT_EQ(vma->vm_private_data, NULL);
-	/* Expect anonymous page offsets. */
-	ASSERT_EQ(vma->vm_pgoff, 0x300);
-	ASSERT_EQ(vma_start_anon_pgoff(vma), 0x300);
-
-	cleanup_mm(&mm, &vmi);
-	return true;
-}
-
 static void run_mmap_tests(int *num_tests, int *num_fail)
 {
 	TEST(mmap_region_basic);
-	TEST(pure_anon_dev_zero);
 }
_
--
Cheers, Lorenzo
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help