* [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff
@ 2026-08-06 20:21 Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 01/20] mm/vma: introduce VMA anon page offset field and add helpers Lorenzo Stoakes (ARM)
` (20 more replies)
0 siblings, 21 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel, syzbot
In memory management we've managed to manufacture a great deal of confusion
around the concept of anonymous memory. We have:
1. 'Pure anon' memory - anonymous VMAs whose folios are anonymous and
swap-backed (thus for reclaim purposes, treated as anonymous). These are
simple enough.
2. shmem - file-backed VMAs, file-backed folios (from rmap perspective) so
present in the page cache and mapped by an address_space object, but
whose folios are also swap-backed (thus treated as anonymous for reclaim
purposes).
3. MAP_PRIVATE-mapped /dev/zero - a strange beast whose VMAs have
vma->vm_file set, but which clears vma->vm_ops to satisfy
vma_is_anonymous(), resulting in VMAs that were mmap()'d referencing a
file, but are in every other sense anonymous, including the folios.
4. Other MAP_PRIVATE-file backed mappings - These possess file-backed VMAs
and have file-backed folios until CoW'd, at which point those CoW'd
folios are anonymous.
This series fixes issues 3 and 4.
In order for us to traverse VMAs using the reverse mapping, we require two
fields - folio->mapping and folio->index. The first tells the rmap code
where to look for VMAs, and the second tells it at which offset the folio
starts within the referenced object.
For anonymous folios, folio->mapping points at an anon_vma object. For
file-backed folios, it points at an address_space. And:
* For file-backed folios folio->index is simply the page offset of the start
of the folio within the file.
* For anonymous folios belonging to pure anon mappings, folio->index is
equal to the anonymous page offset of the folio.
* For anonymous folios belonging to file-backed mappings (i.e. CoW'd folios
of a MAP_PRIVATE file-backed mapping), folio->index is equal to the file
page offset.
This series establishes a new anonymous page offset property of VMAs to
allow us to map anonymous folios at their anonymous page offset, consistent
with pure anon.
The purpose of doing so is to lay the foundations for the scalable CoW
work. This is necessary because scalable CoW looks in the maple tree for
the VMA located at folio->index << PAGE_SHIFT, before falling back to
looking up tracked remaps if necessary.
The MAP_PRIVATE file-backed case means that folio indices will very often
conflict with one another and this remap tracking becomes substantially
more contended, and of course the fast path can never be used.
This also makes it possible, in future, to unshare anonymously mapped
folios with deep fork hierarchies on remap, eliminating the need for remap
tracking in the vast majority of cases.
Similar to page offset of pure anonymous VMAs, we update the anonymous page
offset of unfaulted file-backed VMAs on remap, but do not once
CoW'd (i.e. vma->anon_vma is non-NULL).
Overall, there is little impact on mergeability, which remains exactly the same
for pure anonymous and shared file-backed mappings, with the only impact being
on MAP_PRIVATE-mapped file-backed mappings, which must now match on anonymous
page offset as well as file page offset to be merged.
To fail to merge like this would require CoW'ing the mapping, then finding
another VMA with identical file and compatible page offset to remap next
to.
This is therefore very much an edge case that should have very little
impact (and which scalable CoW may very well address in any case).
We also address the outlier case of MAP_PRIVATE-/dev/zero mappings which
have file page offset but satisfy vma_is_anonymous() by making them truly
anonymous.
This is low-risk as for anything meaningful these mappings behave
anonymously, but it is very beneficial to do so as we eliminate an odd
corner case, and those are notorious for attracting subtle bugs.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
v4:
- Updated tags (thanks everyone!)
- Adjusted some prose as per David.
- Adjusted whitespace to 2 tabs in 2/15 as per David.
- Improved linear_page_index(), __linear_anon_page_index() to be more
succinct in 2/15 as per David and updated commit message to reflect it.
- Added commit to provide vma_[flags_]is_cow_mapping() - nearly all callers
are calling is_cow_mapping() as is_cow_mapping(vma->vm_flags) so just
provide a helper to do this for them.
- In the same commit update all is_cow_mapping() callers and remove the
now-unused function.
- Change linear_anon_page_index() to assert on !CoW mapping using
vma_is_cow_mapping() in 2/15 as per David.
- Updated 4/15 to output index differently depending on whether the VMA is
a CoW mapping or not or whether the file index differs from the anon
index as per Gregory.
- Updated 6/15 to change the 'update page offset' logic in copy_vma() to
not be gated on CoW or VMA_SHARED_BIT as discussed with David.
- Updated 6/15 to improve the 'faulted in anon vma' assert stuff. It was
very unclear so rename the variable sensibly and update the comment.
- Added a separate commit to fix the mess that is the faulted_in_anon_vma
and the VM_WARN_ON_ONCE_VMA() assert in copy_vma() - make it actually
only update the vmap in cases where the VMA was replaced (backwards
remap), update the checks to reflect this in a way that's actually
understandable and improve the comment.
- Updated needs_adjacent_anon_gpoff() in 9/15 to use
vma_flags_is_cow_mapping() as per David. Updated the comment to reflect
it.
- Broke out changes to vma_address_end() into a separate patch as per David.
- Eliminated pgoff in vma_address_end() as it adds confusion - just use
pgoff_end, which was what pgoff used to be (confusingly).
- Placed more variables in vma_address_end() as constants at the start of
the function.
- Dropped KSM comment in 9/15 as per David.
- Dropped linear_folio_page_index() patch altogether as per
David.
- Realised all off uffd is only anon so use linear_anon_page_index()
throughout there and also the huge memory case for the same reason.
- Added a patch to make remove_migration_pmd() accept a folio instead of a
page.
- Added a patch to calculate large folio index using PFN as suggested by
David, eliminating the need for linear page index lookup at all.
- Added a patch to add self merge VMA userland tests.
v3:
- Renamed virtual page offset to anonymous page offset across the series as
per David and adjusted prose to reflect it.
- As part of the rename, eliminated vma_anon_pgoff_addr() and the
conflicting vma_[start, end]_anon_pgoff() functions by passing anon pgoff
to the merge logic and having that figure out which page offset to use,
significantly simplifying things.
- Introduced needs_adjacent_anon_pgoff() to be really clear about what the
merge logic is doing.
- Passing through the anon pgoff fixes the issue Sashiko raised with shared
file-backed mappings having incorrect anon pgoff (inconsequential but a
wrinkle nonetheless).
- Dropped unnecessary needs_rmap_locks change in 9/15 - this checks to see
if rmap locks need to be taken due to the range being moved
backwards. Since both file-backed and anon page offset are updated on
such a move, it suffices to check only the former. Updated commit message
to reflect it.
- Sashiko complained about a couple missing .is_anon_walk entries in
pvmw's - page_mapped_in_vma() and migrate_vma_collect_huge_pmd() -
however neither impact anything - the field is only meaningful for
vma_address_end() if nr_pages > 1 and neither site is impacted. Moreover,
neither site sets pgoff either. Update the commit message to explain
this.
- Renamed is_anon_walk to pgoff_is_anon and document that pvmw->pgoff is
only meaningful if pvmw->nr_pages > 1.
- Highlighted the user-visible changes to /dev/zero being true-anon in the
relevant commit message as per Yang.
- Fixed a bug in the proc-self-map-files-00[1,2].c procfs selftests - the
code was trying to MAP_PRIVATE 'an arbitrary file' then asserts that
file-backed procfs entries exist, but happened to choose
/dev/zero. Updated to the guaranteed-available /proc/self/exe, as
reported by Mark.
- Fixed an issue with drivers that intentionally mark vma->vm_ops as NULL
(using the legacy ->mmap callback). If they do this set dummy ops, which
is what they meant. The mmap_prepare case is fine as nobody does this
there and this will be fixed when all drivers are finally converted to
mmap_prepare. As reported by Sashiko.
- Added some missed VMA selftest vma_start_[anon]_pgoff() conversions in
the merge test commit as per Sashiko.
- Various small prose/comment fixups.
https://patch.msgid.link/20260729-b4-scalable-cow-virt-pgoff-v3-0-e8ecfefea812@kernel.org
v2:
- Removed incorrect assert on always-NULL folio from 7/15, as per syzbot.
- Updated 2/15 so linear_virt_page_index() checks for vma_is_anonymous() as
well to be cautious about 'special' (VDSO, VVAR, etc.) VMAs accidentally
being asserted when CONFIG_DEBUG_VM is set, as per Sashiko.
- Updated commit message of 9/15 to mention the subtle change in NUMA
interleaving behaviour, as per Sashiko.
- Updated 10/15 to assert virtual page offset for adjacent VMAs for various
VMA userland tests, as per Sashiko.
- Updated 13/15 to remove the !vma->vm_file check altogether after
MAP_PRIVATE-/dev/zero is made pure anon in vma_start_virt_pgoff().
- Updated 12/15 to check that the /dev/zero device is a character device
since it turns out that block and character devices have their own
separate major/minor device number namespaces... :) as per Sashiko.
- Updated 12/15 to fix a bisection hazard where vma->vm_ops would be
overwritten by vma_dummy_vm_ops for MAP_PRIVATE-/dev/zero, as per
Sashiko.
- Updated 12/15 to fix another bisection hazard (...!) due to
ordering of vma_set_anonymous(). Removed in 13/15.
- Added comments to __vm_virt_pgoff[lo, hi] fields referencing
vma_start_virt_pgoff()'s comment to be clearer what these are as per
Xu Xin in 1/15.
- Various typo fixes + cleanups in prose.
- Updated the cover letter to point out that the MAP_PRIVATE-/dev/zero
issue is addressed in this series too.
https://patch.msgid.link/20260720-b4-scalable-cow-virt-pgoff-v2-0-2d549757a76f@kernel.org
v1:
- Rebased onto mm-new.
- Dependent series heavily reviewed and looks highly likely to land, so
un-RFC.
- Added explicit check for /dev/zero and removed ability for arbitrary
mmap/mmap_prepare hooks to make themselves anonymous.
- Made MAP_PRIVATE-/dev/zero mappings truly anonymous.
- Added MAP_PRIVATE file-backed mapping merge test to selftests.
- Added a MAP_PRIVATE-/dev/zero VMA userland test to assert that the VMA
really is made anonymous.
- Added MAP_PRIVATE-/dev/zero merge tests to selftests.
- Fixed missed virtual page index site in try_to_merge_with_ksm_page().
- Updated folio_within_range() to use virtual page offset for anon
folio. This had no impact as it is only called for large folios at the
moment (and MAP_PRIVATE-file backed mappings can't currently be backed by
a large folio) but making the change now protects us for the future.
- Fixed typos etc.
https://patch.msgid.link/20260717-b4-scalable-cow-virt-pgoff-v1-0-cf24910ef094@kernel.org
RFC:
https://patch.msgid.link/cover.1782745153.git.ljs@kernel.org
To: Andrew Morton <akpm@linux-foundation.org>
To: David Hildenbrand <david@kernel.org>
To: "Liam R. Howlett" <liam@infradead.org>
To: Vlastimil Babka <vbabka@kernel.org>
To: Mike Rapoport <rppt@kernel.org>
To: Suren Baghdasaryan <surenb@google.com>
To: Michal Hocko <mhocko@suse.com>
To: Jann Horn <jannh@google.com>
To: Pedro Falcato <pfalcato@suse.de>
To: "Matthew Wilcox (Oracle)" <willy@infradead.org>
To: Jan Kara <jack@suse.cz>
To: Miaohe Lin <linmiaohe@huawei.com>
To: Naoya Horiguchi <nao.horiguchi@gmail.com>
To: Rik van Riel <riel@surriel.com>
To: Harry Yoo <harry@kernel.org>
To: Lance Yang <lance.yang@linux.dev>
To: Kees Cook <kees@kernel.org>
To: Zi Yan <ziy@nvidia.com>
To: Baolin Wang <baolin.wang@linux.alibaba.com>
To: Nico Pache <npache@redhat.com>
To: Ryan Roberts <ryan.roberts@arm.com>
To: Dev Jain <dev.jain@arm.com>
To: Barry Song <baohua@kernel.org>
To: Usama Arif <usama.arif@linux.dev>
To: Matthew Brost <matthew.brost@intel.com>
To: Joshua Hahn <joshua.hahnjy@gmail.com>
To: Rakie Kim <rakie.kim@sk.com>
To: Byungchul Park <byungchul@sk.com>
To: Gregory Price <gourry@gourry.net>
To: Ying Huang <ying.huang@linux.alibaba.com>
To: Alistair Popple <apopple@nvidia.com>
To: Peter Xu <peterx@redhat.com>
To: Xu Xin <xu.xin16@zte.com.cn>
To: Chengming Zhou <chengming.zhou@linux.dev>
To: Arnd Bergmann <arnd@arndb.de>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: Christian Borntraeger <borntraeger@linux.ibm.com>
To: Janosch Frank <frankja@linux.ibm.com>
To: Claudio Imbrenda <imbrenda@linux.ibm.com>
To: Alexander Gordeev <agordeev@linux.ibm.com>
To: Gerald Schaefer <gerald.schaefer@linux.ibm.com>
To: Heiko Carstens <hca@linux.ibm.com>
To: Vasily Gorbik <gor@linux.ibm.com>
To: Sven Schnelle <svens@linux.ibm.com>
To: Alex Deucher <alexander.deucher@amd.com>
To: Christian König <christian.koenig@amd.com>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Maxime Ripard <mripard@kernel.org>
To: Thomas Zimmermann <tzimmermann@suse.de>
To: Boris Brezillon <boris.brezillon@collabora.com>
To: Steven Price <steven.price@arm.com>
To: Liviu Dudau <liviu.dudau@arm.com>
To: Huang Rui <ray.huang@amd.com>
To: Matthew Auld <matthew.auld@intel.com>
To: Thomas Hellström <thomas.hellstrom@linux.intel.com>
To: Rodrigo Vivi <rodrigo.vivi@intel.com>
To: Masami Hiramatsu <mhiramat@kernel.org>
To: Oleg Nesterov <oleg@redhat.com>
To: Peter Zijlstra <peterz@infradead.org>
To: Ingo Molnar <mingo@redhat.com>
To: Arnaldo Carvalho de Melo <acme@kernel.org>
To: Namhyung Kim <namhyung@kernel.org>
To: Mark Rutland <mark.rutland@arm.com>
To: Alexander Shishkin <alexander.shishkin@linux.intel.com>
To: Jiri Olsa <jolsa@kernel.org>
To: Ian Rogers <irogers@google.com>
To: Adrian Hunter <adrian.hunter@intel.com>
To: James Clark <james.clark@linaro.org>
To: Jason Gunthorpe <jgg@ziepe.ca>
To: John Hubbard <jhubbard@nvidia.com>
To: Muchun Song <muchun.song@linux.dev>
To: Oscar Salvador <osalvador@suse.de>
To: Chris Li <chrisl@kernel.org>
To: Kairui Song <kasong@tencent.com>
To: Kemeng Shi <shikemeng@huaweicloud.com>
To: Nhat Pham <nphamcs@gmail.com>
To: Baoquan He <baoquan.he@linux.dev>
To: Youngjun Park <youngjun.park@lge.com>
Cc: ljs@kernel.org
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org
Cc: kvm@vger.kernel.org
Cc: linux-s390@vger.kernel.org
Cc: amd-gfx@lists.freedesktop.org
Cc: dri-devel@lists.freedesktop.org
Cc: intel-xe@lists.freedesktop.org
Cc: linux-perf-users@vger.kernel.org
Cc: linux-trace-kernel@vger.kernel.org
---
Lorenzo Stoakes (ARM) (20):
mm/vma: introduce VMA anon page offset field and add helpers
mm: provide vma_[flags_]is_cow_mapping() and remove is_cow_mapping()
mm: introduce linear_anon_page_index()
mm: abstract vma_address() and introduce vma_anon_address()
mm: update print_bad_page_map() to show anon index if appropriate
mm: introduce and use vma_filebacked_address()
mm/vma: fix self-merge check in copy_vma()
tools/testing/vma: add tests for copy_vma() self-merge
mm: propagate VMA anonymous page offset on map, remap, split + merge
mm/rmap: track whether the page VMA mapped pgoff is anonymous
mm: clean up vma_address_end()
mm/huge_memory: update remove_migration_pmd() to accept a folio
mm/migrate: calculate large folio page index using PFN
mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios
tools/testing/vma: expand VMA merge tests to assert anon pgoff
tools/testing/selftests/mm: test anonymous page offset merge behaviour
mm/vma: only permit MAP_PRIVATE /dev/zero to be mapped anonymous
mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous
tools/testing/vma: add test to assert MAP_PRIVATE-/dev/zero is anon
tools/testing/selftests/mm: add MAP_PRIVATE-/dev/zero merge tests
arch/s390/mm/gmap_helpers.c | 2 +-
drivers/char/mem.c | 8 +-
drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 4 +-
drivers/gpu/drm/drm_gem_shmem_helper.c | 2 +-
drivers/gpu/drm/panthor/panthor_gem.c | 2 +-
drivers/gpu/drm/ttm/ttm_bo_vm.c | 2 +-
drivers/gpu/drm/xe/xe_device.c | 2 +-
fs/proc/task_mmu.c | 2 +-
include/linux/mm.h | 89 +++++++++--
include/linux/mm_types.h | 12 ++
include/linux/pagemap.h | 39 ++++-
include/linux/rmap.h | 4 +-
include/linux/swapops.h | 6 +-
kernel/events/uprobes.c | 2 +-
mm/gup.c | 2 +-
mm/huge_memory.c | 26 ++--
mm/hugetlb.c | 2 +-
mm/internal.h | 89 +++++++----
mm/interval_tree.c | 4 +-
mm/ksm.c | 6 +-
mm/memory-failure.c | 4 +-
mm/memory.c | 38 +++--
mm/mempolicy.c | 2 +-
mm/migrate.c | 21 ++-
mm/mremap.c | 6 +-
mm/page_vma_mapped.c | 6 +-
mm/rmap.c | 22 +--
mm/userfaultfd.c | 4 +-
mm/vma.c | 165 ++++++++++++++++-----
mm/vma.h | 92 +++++++-----
mm/vma_exec.c | 2 +-
mm/vma_init.c | 1 +
mm/vma_internal.h | 1 +
tools/testing/selftests/mm/merge.c | 161 ++++++++++++++++++++
.../selftests/proc/proc-self-map-files-001.c | 2 +-
.../selftests/proc/proc-self-map-files-002.c | 2 +-
tools/testing/vma/include/dup.h | 98 +++++++++++-
tools/testing/vma/shared.c | 3 +-
tools/testing/vma/tests/merge.c | 49 ++++--
tools/testing/vma/tests/mmap.c | 50 +++++++
tools/testing/vma/tests/vma.c | 50 ++++++-
tools/testing/vma/vma_internal.h | 1 +
42 files changed, 865 insertions(+), 220 deletions(-)
---
base-commit: 5093dba1014c1d7f7e247fd118f0fa8f22136046
change-id: 20260711-b4-scalable-cow-virt-pgoff-a0cc0eb14bc6
Cheers,
--
Lorenzo Stoakes (ARM) <ljs@kernel.org>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v4 01/20] mm/vma: introduce VMA anon page offset field and add helpers
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 02/20] mm: provide vma_[flags_]is_cow_mapping() and remove is_cow_mapping() Lorenzo Stoakes (ARM)
` (19 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Establish fields in vm_area_struct to store the anonymous page offset of
VMAs.
Initially, the anonymous page offset of a VMA is vma->vm_start >>
PAGE_SHIFT.
When a VMA is remapped to new_address its anonymous page offset is either
updated to new_address >> PAGE_SHIFT if unfaulted or, if faulted, remains
equal to the anonymous page offset it had when first faulted.
Currently, anonymous folios belonging to CoW'd MAP_PRIVATE-mapped
file-backed VMAs are tracked by their file offsets. By adding anonymous
offset as a property of VMAs, we can now track them by their anonymous page
offset instead.
By tracking this, we provide the means by which to eliminate this
inconsistency, and more importantly lay the foundations for future work for
the scalable CoW anonymous rmap rework.
This patch simply adds the fields and some simple helpers. Subsequent
patches will update mm code to make use of these fields correctly.
The fields chosen are packed in the VMA such that, for 64-bit kernel
builds, no additional space is taken up.
The first field is present on cacheline 0 containing key VMA fields, and
the second on cacheline 3, which contains file-backed reverse mapping
fields.
Given the relative time spent accessing reverse mapping fields as well as
updating them, there shouldn't be any performance impact here from false
sharing.
Update the VMA userland tests to account for this change.
No callsites are updated yet, so no functional change intended.
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
Reviewed-by: Xu Xin <xu.xin16@zte.com.cn>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 59 +++++++++++++++++++++++++++++++++++++++++
include/linux/mm_types.h | 12 +++++++++
mm/vma.h | 14 ++++++++++
mm/vma_init.c | 1 +
tools/testing/vma/include/dup.h | 26 ++++++++++++++++++
5 files changed, 112 insertions(+)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 87feaa5a2b78..df78847f5f07 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -4393,6 +4393,65 @@ static inline pgoff_t vma_last_pgoff(const struct vm_area_struct *vma)
return vma_end_pgoff(vma) - 1;
}
+/**
+ * vma_start_anon_pgoff() - Get the anonymous page offset of the start of @vma
+ * @vma: The VMA whose anonymous page offset is required.
+ *
+ * If unfaulted, then this is vma->vm_start >> PAGE_SHIFT, if faulted then the
+ * anonymous page offset at the time of first fault.
+ *
+ * If the VMA is anonymous, this returns the same value as vma_start_pgoff().
+ *
+ * This value is used for tracking MAP_PRIVATE file-backed mappings by their
+ * anonymous page offset.
+ *
+ * Returns: The anonymous page offset of the start of @vma.
+ */
+static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma)
+{
+ pgoff_t pgoff = 0;
+
+#ifdef CONFIG_64BIT
+ pgoff += vma->__vm_anon_pgoff_hi;
+ pgoff <<= 32;
+#endif
+ pgoff += vma->__vm_anon_pgoff_lo;
+ return pgoff;
+}
+
+/**
+ * vma_end_anon_pgoff() - Get the anonymous page offset of the exclusive end of
+ * @vma.
+ * @vma: The VMA whose end anonymous page offset is required.
+ *
+ * This returns the anonymous exclusive end page offset of @vma, which is useful
+ * for expressing page offset ranges.
+ *
+ * See the description of vma_start_anon_pgoff() for a description of VMA
+ * anonymous page offsets.
+ *
+ * Returns: The exclusive end anonymous page offset of @vma.
+ */
+static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma)
+{
+ return vma_start_anon_pgoff(vma) + vma_pages(vma);
+}
+
+/**
+ * vma_last_anon_pgoff() - Get the anonymous page offset of the last page in
+ * @vma.
+ * @vma: The VMA whose last anonymous page offset is required.
+ *
+ * See the description of vma_start_anon_pgoff() for a description of VMA
+ * anonymous page offsets.
+ *
+ * Returns: The last anonymous page offset of @vma.
+ */
+static inline pgoff_t vma_last_anon_pgoff(const struct vm_area_struct *vma)
+{
+ return vma_end_anon_pgoff(vma) - 1;
+}
+
static inline unsigned long vma_desc_size(const struct vm_area_desc *desc)
{
return desc->end - desc->start;
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 939b5ea8c9e0..ebf0d912be7d 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -967,6 +967,11 @@ struct vm_area_struct {
*/
unsigned int vm_lock_seq;
#endif
+ /*
+ * Low 32-bits of anonymous page offset.
+ * See vma_start_anon_pgoff() comment for details.
+ */
+ unsigned int __vm_anon_pgoff_lo;
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
* list, after a COW of one of the file pages. A MAP_SHARED vma
@@ -1041,6 +1046,13 @@ struct vm_area_struct {
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map vmlock_dep_map;
#endif
+#endif
+#ifdef CONFIG_64BIT
+ /*
+ * High 32-bits of anonymous page offset.
+ * See vma_start_anon_pgoff() comment for details.
+ */
+ unsigned int __vm_anon_pgoff_hi;
#endif
/*
* For areas with an address space and backing store,
diff --git a/mm/vma.h b/mm/vma.h
index 0bc7d521e976..54ed7c744e3b 100644
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -283,6 +283,20 @@ static inline void vma_set_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
vma->vm_pgoff = pgoff;
}
+static inline void __vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
+{
+#ifdef CONFIG_64BIT
+ vma->__vm_anon_pgoff_hi = pgoff >> 32;
+#endif
+ vma->__vm_anon_pgoff_lo = pgoff & GENMASK(31, 0);
+}
+
+static inline void vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
+{
+ vma_assert_can_modify(vma);
+ __vma_set_anon_pgoff(vma, pgoff);
+}
+
static inline void vma_add_pgoff(struct vm_area_struct *vma, pgoff_t delta)
{
vma_assert_can_modify(vma);
diff --git a/mm/vma_init.c b/mm/vma_init.c
index 715feee283f0..baa7e82f47e3 100644
--- a/mm/vma_init.c
+++ b/mm/vma_init.c
@@ -51,6 +51,7 @@ static void vm_area_init_from(const struct vm_area_struct *src,
dest->vm_end = src->vm_end;
dest->anon_vma = src->anon_vma;
dest->vm_pgoff = vma_start_pgoff(src);
+ __vma_set_anon_pgoff(dest, vma_start_anon_pgoff(src));
dest->vm_file = src->vm_file;
dest->vm_private_data = src->vm_private_data;
vm_flags_init(dest, src->vm_flags);
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index c52b23773cd2..26d9210b1e8b 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -577,6 +577,7 @@ struct vm_area_struct {
*/
unsigned int vm_lock_seq;
#endif
+ unsigned int __vm_anon_pgoff_lo;
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
@@ -612,6 +613,9 @@ struct vm_area_struct {
#ifdef CONFIG_PER_VMA_LOCK
/* Unstable RCU readers are allowed to read this. */
refcount_t vm_refcnt;
+#endif
+#ifdef CONFIG_64BIT
+ unsigned int __vm_anon_pgoff_hi;
#endif
/*
* For areas with an address space and backing store,
@@ -1320,6 +1324,28 @@ static inline pgoff_t vma_end_pgoff(const struct vm_area_struct *vma)
return vma_start_pgoff(vma) + vma_pages(vma);
}
+static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma)
+{
+ pgoff_t pgoff = 0;
+
+#ifdef CONFIG_64BIT
+ pgoff += vma->__vm_anon_pgoff_hi;
+ pgoff <<= 32;
+#endif
+ pgoff += vma->__vm_anon_pgoff_lo;
+ return pgoff;
+}
+
+static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma)
+{
+ return vma_start_anon_pgoff(vma) + vma_pages(vma);
+}
+
+static inline pgoff_t vma_last_anon_pgoff(const struct vm_area_struct *vma)
+{
+ return vma_end_anon_pgoff(vma) - 1;
+}
+
static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
{
return file->f_op->mmap_prepare(desc);
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 02/20] mm: provide vma_[flags_]is_cow_mapping() and remove is_cow_mapping()
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 01/20] mm/vma: introduce VMA anon page offset field and add helpers Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 03/20] mm: introduce linear_anon_page_index() Lorenzo Stoakes (ARM)
` (18 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
All remaining callers of is_cow_mapping() are invoking it in the form of
is_cow_mapping(vma->vm_flags) or an indirected version of this.
Therefore, provide a helper - vma_is_cow_mapping() to directly test the
VMA.
Additionally provide a new helper vma_flags_is_cow_mapping() which performs
the check using the new vma_flags_t type, and share this logic between
vma_is_cow_mapping() and vma_desc_is_cow_mapping().
With these changes, no callers of is_cow_mapping() remain, so remove it.
Also update the userland VMA tests to reflect the change.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/s390/mm/gmap_helpers.c | 2 +-
drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 4 ++--
drivers/gpu/drm/drm_gem_shmem_helper.c | 2 +-
drivers/gpu/drm/panthor/panthor_gem.c | 2 +-
drivers/gpu/drm/ttm/ttm_bo_vm.c | 2 +-
drivers/gpu/drm/xe/xe_device.c | 2 +-
fs/proc/task_mmu.c | 2 +-
include/linux/mm.h | 15 +++++++++------
kernel/events/uprobes.c | 2 +-
mm/gup.c | 2 +-
mm/huge_memory.c | 8 ++++----
mm/hugetlb.c | 2 +-
mm/internal.h | 2 +-
mm/memory.c | 25 ++++++++++++-------------
mm/mempolicy.c | 2 +-
tools/testing/vma/include/dup.h | 11 +++++++++++
16 files changed, 49 insertions(+), 36 deletions(-)
diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c
index 4bf7c9012feb..cd5fded159c0 100644
--- a/arch/s390/mm/gmap_helpers.c
+++ b/arch/s390/mm/gmap_helpers.c
@@ -200,7 +200,7 @@ static int find_zeropage_pte_entry(pte_t *pte, unsigned long addr,
* currently only works in COW mappings, which is also where
* mm_forbids_zeropage() is checked.
*/
- if (!is_cow_mapping(walk->vma->vm_flags))
+ if (!vma_is_cow_mapping(walk->vma))
return -EFAULT;
*found_addr = addr;
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c
index 6a0699746fbc..0c7309080a7a 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c
@@ -377,9 +377,9 @@ static int amdgpu_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_str
/* Workaround for Thunk bug creating PROT_NONE,MAP_PRIVATE mappings
* for debugger access to invisible VRAM. Should have used MAP_SHARED
* instead. Clearing VM_MAYWRITE prevents the mapping from ever
- * becoming writable and makes is_cow_mapping(vm_flags) false.
+ * becoming writable and makes vma_is_cow_mapping(vma) false.
*/
- if (is_cow_mapping(vma->vm_flags) &&
+ if (vma_is_cow_mapping(vma) &&
!(vma->vm_flags & VM_ACCESS_FLAGS))
vm_flags_clear(vma, VM_MAYWRITE);
diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
index 06d019d51d3e..177d0e0b9334 100644
--- a/drivers/gpu/drm/drm_gem_shmem_helper.c
+++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
@@ -753,7 +753,7 @@ int drm_gem_shmem_mmap(struct drm_gem_shmem_object *shmem, struct vm_area_struct
return ret;
}
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
dma_resv_lock(shmem->base.resv, NULL);
diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c
index 770556353968..d2eec46f7abe 100644
--- a/drivers/gpu/drm/panthor/panthor_gem.c
+++ b/drivers/gpu/drm/panthor/panthor_gem.c
@@ -761,7 +761,7 @@ static int panthor_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *v
return ret;
}
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c
index 88babf435ac2..872bf444b1f0 100644
--- a/drivers/gpu/drm/ttm/ttm_bo_vm.c
+++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c
@@ -489,7 +489,7 @@ static const struct vm_operations_struct ttm_bo_vm_ops = {
int ttm_bo_mmap_obj(struct vm_area_struct *vma, struct ttm_buffer_object *bo)
{
/* Enforce no COW since would have really strange behavior with it. */
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
drm_gem_object_get(&bo->base);
diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
index 838797cc65d7..ab16937ebbe0 100644
--- a/drivers/gpu/drm/xe/xe_device.c
+++ b/drivers/gpu/drm/xe/xe_device.c
@@ -330,7 +330,7 @@ static int xe_pci_barrier_mmap(struct file *filp,
if (vma->vm_end - vma->vm_start > SZ_4K)
return -EINVAL;
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
if (vma->vm_flags & (VM_READ | VM_EXEC))
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 817e3e0f9194..5c54aebe2118 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -1693,7 +1693,7 @@ static inline bool pte_is_pinned(struct vm_area_struct *vma, unsigned long addr,
if (!pte_write(pte))
return false;
- if (!is_cow_mapping(vma->vm_flags))
+ if (!vma_is_cow_mapping(vma))
return false;
if (likely(!mm_flags_test(MMF_HAS_PINNED, vma->vm_mm)))
return false;
diff --git a/include/linux/mm.h b/include/linux/mm.h
index df78847f5f07..a3368c542947 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2271,17 +2271,20 @@ void unpin_user_pages(struct page **pages, unsigned long npages);
void unpin_user_folio(struct folio *folio, unsigned long npages);
void unpin_folios(struct folio **folios, unsigned long nfolios);
-static inline bool is_cow_mapping(vm_flags_t flags)
+static inline bool vma_flags_is_cow_mapping(const vma_flags_t *flags)
{
- return (flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
+ return vma_flags_test(flags, VMA_MAYWRITE_BIT) &&
+ !vma_flags_test(flags, VMA_SHARED_BIT);
}
-static inline bool vma_desc_is_cow_mapping(struct vm_area_desc *desc)
+static inline bool vma_is_cow_mapping(const struct vm_area_struct *vma)
{
- const vma_flags_t *flags = &desc->vma_flags;
+ return vma_flags_is_cow_mapping(&vma->flags);
+}
- return vma_flags_test(flags, VMA_MAYWRITE_BIT) &&
- !vma_flags_test(flags, VMA_SHARED_BIT);
+static inline bool vma_desc_is_cow_mapping(struct vm_area_desc *desc)
+{
+ return vma_flags_is_cow_mapping(&desc->vma_flags);
}
#ifndef CONFIG_MMU
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index ae2f3b9f8d50..eb0d11092fb3 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -513,7 +513,7 @@ int uprobe_write(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
uprobe = container_of(auprobe, struct uprobe, arch);
- if (WARN_ON_ONCE(!is_cow_mapping(vma->vm_flags)))
+ if (WARN_ON_ONCE(!vma_is_cow_mapping(vma)))
return -EINVAL;
/*
diff --git a/mm/gup.c b/mm/gup.c
index 1d32e9a3dc79..b534ef58c46a 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -1236,7 +1236,7 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)
* Anon pages in shared mappings are surprising: now
* just reject it.
*/
- if (!is_cow_mapping(vm_flags))
+ if (!vma_is_cow_mapping(vma))
return -EFAULT;
}
} else if (!(vm_flags & VM_READ)) {
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 9b1f3b24f7e0..6b0cabd45b2d 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -1638,7 +1638,7 @@ vm_fault_t vmf_insert_pfn_pmd(struct vm_fault *vmf, unsigned long pfn,
BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
(VM_PFNMAP|VM_MIXEDMAP));
- BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));
+ BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma));
pfnmap_setup_cachemode_pfn(pfn, &pgprot);
@@ -1746,7 +1746,7 @@ vm_fault_t vmf_insert_pfn_pud(struct vm_fault *vmf, unsigned long pfn,
BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
(VM_PFNMAP|VM_MIXEDMAP));
- BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));
+ BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma));
pfnmap_setup_cachemode_pfn(pfn, &pgprot);
@@ -1888,7 +1888,7 @@ int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm,
* applied special bit, or we made the PRIVATE mapping be
* able to wrongly write to the backend MMIO.
*/
- VM_WARN_ON_ONCE(is_cow_mapping(src_vma->vm_flags) && pmd_write(pmd));
+ VM_WARN_ON_ONCE(vma_is_cow_mapping(src_vma) && pmd_write(pmd));
goto set_pmd;
}
@@ -2009,7 +2009,7 @@ int copy_huge_pud(struct mm_struct *dst_mm, struct mm_struct *src_mm,
* TODO: once we support anonymous pages, use
* folio_try_dup_anon_rmap_*() and split if duplicating fails.
*/
- if (is_cow_mapping(vma->vm_flags) && pud_write(pud)) {
+ if (vma_is_cow_mapping(vma) && pud_write(pud)) {
pudp_set_wrprotect(src_mm, addr, src_pud);
pud = pud_wrprotect(pud);
}
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index d86a27c8819c..560e85ca1d1c 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -4905,7 +4905,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src,
pte_t *src_pte, *dst_pte, entry;
struct folio *pte_folio;
unsigned long addr;
- bool cow = is_cow_mapping(src_vma->vm_flags);
+ bool cow = vma_is_cow_mapping(src_vma);
struct hstate *h = hstate_vma(src_vma);
unsigned long sz = huge_page_size(h);
unsigned long npages = pages_per_huge_page(h);
diff --git a/mm/internal.h b/mm/internal.h
index f26423de4ca2..a75a1344e9ba 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1341,7 +1341,7 @@ static inline bool gup_must_unshare(struct vm_area_struct *vma,
* ... because we only care about writable private ("COW")
* mappings where we have to break COW early.
*/
- return is_cow_mapping(vma->vm_flags);
+ return vma_is_cow_mapping(vma);
}
/* Paired with a memory barrier in folio_try_share_anon_rmap_*(). */
diff --git a/mm/memory.c b/mm/memory.c
index d5e87624f692..e1349e18f046 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -780,7 +780,7 @@ static inline struct page *__vm_normal_page(struct vm_area_struct *vma,
/* Only CoW'ed anon folios are "normal". */
if (pfn == index)
return NULL;
- if (!is_cow_mapping(vma->vm_flags))
+ if (!vma_is_cow_mapping(vma))
return NULL;
}
}
@@ -1002,7 +1002,6 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *dst_vma,
struct vm_area_struct *src_vma, unsigned long addr, int *rss)
{
- vm_flags_t vm_flags = dst_vma->vm_flags;
pte_t orig_pte = ptep_get(src_pte);
softleaf_t entry = softleaf_from_pte(orig_pte);
pte_t pte = orig_pte;
@@ -1026,7 +1025,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
rss[mm_counter(folio)]++;
if (!softleaf_is_migration_read(entry) &&
- is_cow_mapping(vm_flags)) {
+ vma_is_cow_mapping(dst_vma)) {
/*
* COW mappings require pages in both parent and child
* to be set to read. A previously exclusive entry is
@@ -1067,7 +1066,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
* save and restore device driver state).
*/
if (softleaf_is_device_private_write(entry) &&
- is_cow_mapping(vm_flags)) {
+ vma_is_cow_mapping(dst_vma)) {
entry = make_readable_device_private_entry(
swp_offset(entry));
pte = swp_entry_to_pte(entry);
@@ -1082,7 +1081,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
* exclusive entries currently only support private writable
* (ie. COW) mappings.
*/
- VM_BUG_ON(!is_cow_mapping(src_vma->vm_flags));
+ VM_BUG_ON(!vma_is_cow_mapping(src_vma));
if (try_restore_exclusive_pte(src_vma, addr, src_pte, orig_pte))
return -EBUSY;
return -ENOENT;
@@ -1181,7 +1180,7 @@ static __always_inline void __copy_present_ptes(struct vm_area_struct *dst_vma,
}
/* If it's a COW mapping, write protect it both processes. */
- if (is_cow_mapping(src_vma->vm_flags) && writable) {
+ if (vma_is_cow_mapping(src_vma) && writable) {
wrprotect_ptes(src_mm, addr, src_pte, nr);
pte = pte_wrprotect(pte);
}
@@ -1602,9 +1601,9 @@ copy_page_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)
* We need to invalidate the secondary MMU mappings only when
* there could be a permission downgrade on the ptes of the
* parent mm. And a permission downgrade will only happen if
- * is_cow_mapping() returns true.
+ * vma_is_cow_mapping() returns true.
*/
- is_cow = is_cow_mapping(src_vma->vm_flags);
+ is_cow = vma_is_cow_mapping(src_vma);
if (is_cow) {
mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE,
@@ -2388,7 +2387,7 @@ static bool vm_mixed_zeropage_allowed(struct vm_area_struct *vma)
if (mm_forbids_zeropage(vma->vm_mm))
return false;
/* zeropages in COW mappings are common and unproblematic. */
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return true;
/* Mappings that do not allow for writable PTEs are unproblematic. */
if (!(vma->vm_flags & (VM_WRITE | VM_MAYWRITE)))
@@ -2839,7 +2838,7 @@ vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,
BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
(VM_PFNMAP|VM_MIXEDMAP));
- BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));
+ BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma));
BUG_ON((vma->vm_flags & VM_MIXEDMAP) && pfn_valid(pfn));
if (addr < vma->vm_start || addr >= vma->vm_end)
@@ -3251,7 +3250,7 @@ static int remap_pfn_range_prepare_vma(struct vm_area_struct *vma,
unsigned long size)
{
const unsigned long end = addr + PAGE_ALIGN(size);
- const bool is_cow = is_cow_mapping(vma->vm_flags);
+ const bool is_cow = vma_is_cow_mapping(vma);
int err;
err = get_remap_pgoff(is_cow, addr, end, vma->vm_start, vma->vm_end,
@@ -6751,7 +6750,7 @@ static vm_fault_t sanitize_fault_flags(struct vm_area_struct *vma,
* FAULT_FLAG_UNSHARE only applies to COW mappings. Let's
* just treat it like an ordinary read-fault otherwise.
*/
- if (!is_cow_mapping(vma->vm_flags))
+ if (!vma_is_cow_mapping(vma))
*flags &= ~FAULT_FLAG_UNSHARE;
} else if (*flags & FAULT_FLAG_WRITE) {
/* Write faults on read-only mappings are impossible ... */
@@ -6759,7 +6758,7 @@ static vm_fault_t sanitize_fault_flags(struct vm_area_struct *vma,
return VM_FAULT_SIGSEGV;
/* ... and FOLL_FORCE only applies to COW mappings. */
if (WARN_ON_ONCE(!(vma->vm_flags & VM_WRITE) &&
- !is_cow_mapping(vma->vm_flags)))
+ !vma_is_cow_mapping(vma)))
return VM_FAULT_SIGSEGV;
}
#ifdef CONFIG_PER_VMA_LOCK
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 5720f7f54d94..3498a5651d50 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -844,7 +844,7 @@ bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma,
return false;
/* Also skip shared copy-on-write folios */
- if (is_cow_mapping(vma->vm_flags) && folio_maybe_mapped_shared(folio))
+ if (vma_is_cow_mapping(vma) && folio_maybe_mapped_shared(folio))
return false;
/* Folios are pinned and can't be migrated */
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 26d9210b1e8b..40ad83936b28 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -1162,6 +1162,17 @@ static inline bool vma_is_shared_maywrite(struct vm_area_struct *vma)
return is_shared_maywrite(&vma->flags);
}
+static inline bool vma_flags_is_cow_mapping(const vma_flags_t *flags)
+{
+ return vma_flags_test(flags, VMA_MAYWRITE_BIT) &&
+ !vma_flags_test(flags, VMA_SHARED_BIT);
+}
+
+static inline bool vma_is_cow_mapping(const struct vm_area_struct *vma)
+{
+ return vma_flags_is_cow_mapping(&vma->flags);
+}
+
static inline struct vm_area_struct *vma_next(struct vma_iterator *vmi)
{
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 03/20] mm: introduce linear_anon_page_index()
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 01/20] mm/vma: introduce VMA anon page offset field and add helpers Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 02/20] mm: provide vma_[flags_]is_cow_mapping() and remove is_cow_mapping() Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 04/20] mm: abstract vma_address() and introduce vma_anon_address() Lorenzo Stoakes (ARM)
` (17 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
This function provides the anonymous equivalent of linear_page_index(),
instead offsetting based on the anonymous page offset of the VMA.
It is valid only for anonymous or MAP_PRIVATE file-backed mappings, in
other words CoW mappings.
For pure anon VMAs, this will be equal to linear_page_index().
Assert that both of these invariants are true In linear_anon_page_index()
and implement the algorithm in __linear_anon_page_index().
Note that MAP_PRIVATE-/dev/zero mappings will satisfy vma_is_anonymous()
but not fulfill this invariant, so when asserting this we check
vma->vm_file to account for this.
We do not update callsites yet, so no functional change intended.
Also const-ify vma_is_anonymous() to make it compatible with the
const-ified linear_anon_page_index().
While we're here, update linear_page_index() to be more succinct.
VMA userland tests are also updated accordingly.
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 2 +-
include/linux/pagemap.h | 40 +++++++++++++++++++++++++++++++++++++---
tools/testing/vma/include/dup.h | 25 ++++++++++++++++++++++++-
3 files changed, 62 insertions(+), 5 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index a3368c542947..da36a6cc907c 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1556,7 +1556,7 @@ static inline void vma_desc_set_anonymous(struct vm_area_desc *desc)
desc->vm_ops = NULL;
}
-static inline bool vma_is_anonymous(struct vm_area_struct *vma)
+static inline bool vma_is_anonymous(const struct vm_area_struct *vma)
{
return !vma->vm_ops;
}
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index c6fc783aaee5..0adfa6605653 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -1094,10 +1094,44 @@ static inline pgoff_t linear_page_delta(const struct vm_area_struct *vma,
static inline pgoff_t linear_page_index(const struct vm_area_struct *vma,
const unsigned long address)
{
- pgoff_t pgoff;
+ return linear_page_delta(vma, address) + vma_start_pgoff(vma);
+}
+
+static inline pgoff_t __linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ return linear_page_delta(vma, address) + vma_start_anon_pgoff(vma);
+}
+
+/**
+ * linear_anon_page_index() - Determine the absolute anonymous page offset of
+ * @address within @vma.
+ * @vma: An anonymous or MAP_PRIVATE file-backed VMA in which @address resides.
+ * @address: The address whose absolute page offset is required.
+ *
+ * This returns the anonymous page offset of @address, which is the page offset
+ * the address possessed at the time the VMA was first faulted.
+ *
+ * For anonymous mappings, this returns the same value as linear_page_index().
+ *
+ * For MAP_PRIVATE file-backed mappings, this returns the anonymous page offset
+ * of @address, which is the page offset the address possessed at the time the
+ * VMA was first faulted.
+ *
+ * It is not valid to call this function for shared file-backed mappings.
+ *
+ * Returns: The absolute anonymous page offset of @address within @vma.
+ */
+static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ const pgoff_t pgoff = __linear_anon_page_index(vma, address);
+
+ VM_WARN_ON_ONCE(!vma_is_cow_mapping(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));
- pgoff = linear_page_delta(vma, address);
- pgoff += vma_start_pgoff(vma);
return pgoff;
}
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 40ad83936b28..4c58487b764e 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -1428,7 +1428,7 @@ static inline void vma_iter_set(struct vma_iterator *vmi, unsigned long addr)
mas_set(&vmi->mas, addr);
}
-static inline bool vma_is_anonymous(struct vm_area_struct *vma)
+static inline bool vma_is_anonymous(const struct vm_area_struct *vma)
{
return !vma->vm_ops;
}
@@ -1621,3 +1621,26 @@ static inline pgprot_t vma_get_page_prot(const struct vm_area_struct *vma)
{
return vma_flags_to_page_prot(vma->flags);
}
+
+static inline pgoff_t __linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ pgoff_t pgoff;
+
+ pgoff = linear_page_delta(vma, address);
+ pgoff += vma_start_anon_pgoff(vma);
+ return pgoff;
+}
+
+static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ const pgoff_t pgoff = __linear_anon_page_index(vma, address);
+
+ VM_WARN_ON_ONCE(!vma_is_cow_mapping(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;
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 04/20] mm: abstract vma_address() and introduce vma_anon_address()
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (2 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 03/20] mm: introduce linear_anon_page_index() Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 05/20] mm: update print_bad_page_map() to show anon index if appropriate Lorenzo Stoakes (ARM)
` (16 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Introduce __vma_address() which abstracts the VMA start page offset field
as pgoff_start, then update vma_address() to use it.
Then introduce vma_anon_address() which does the equivalent of
vma_address(), only using the anonymous page offset of the VMA rather than
the file-backed one.
Also add an assert to ensure that the function is not called for mappings
which are file-backed but not MAP_PRIVATE to ensure it is only used in the
correct places.
This will be necessary for determining the address of a folio's index
within a VMA when the folio belongs to a MAP_PRIVATE file-backed VMA but
has been CoW'd, and thus is anonymous, once the anonymous VMA page offset
field is used for the reverse mapping.
No callers are updated, so no functional change intended.
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 49 +++++++++++++++++++++++++++++++++++++------------
1 file changed, 37 insertions(+), 12 deletions(-)
diff --git a/mm/internal.h b/mm/internal.h
index a75a1344e9ba..10b6662be8ed 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1005,19 +1005,9 @@ void mlock_drain_remote(int cpu);
extern pmd_t maybe_pmd_mkwrite(pmd_t pmd, struct vm_area_struct *vma);
-/**
- * vma_address - Find the virtual address a page range is mapped at
- * @vma: The vma which maps this object.
- * @pgoff: The page offset within its object.
- * @nr_pages: The number of pages to consider.
- *
- * If any page in this range is mapped by this VMA, return the first address
- * where any of these pages appear. Otherwise, return -EFAULT.
- */
-static inline unsigned long vma_address(const struct vm_area_struct *vma,
- pgoff_t pgoff, unsigned long nr_pages)
+static inline unsigned long __vma_address(const struct vm_area_struct *vma,
+ pgoff_t pgoff, pgoff_t pgoff_start, unsigned long nr_pages)
{
- const pgoff_t pgoff_start = vma_start_pgoff(vma);
unsigned long address;
if (pgoff >= pgoff_start) {
@@ -1035,6 +1025,41 @@ static inline unsigned long vma_address(const struct vm_area_struct *vma,
return address;
}
+/**
+ * vma_address - Find the virtual address a page range is mapped at.
+ * @vma: The vma which maps this object.
+ * @pgoff: The page offset within its object.
+ * @nr_pages: The number of pages to consider.
+ *
+ * If any page in this range is mapped by this VMA, return the first address
+ * where any of these pages appear. Otherwise, return -EFAULT.
+ */
+static inline unsigned long vma_address(const struct vm_area_struct *vma,
+ pgoff_t pgoff, unsigned long nr_pages)
+{
+ return __vma_address(vma, pgoff, vma_start_pgoff(vma), nr_pages);
+}
+
+/**
+ * vma_anon_address - Find the address an anonymous folio with index @pgoff_anon
+ * is mapped at.
+ * @vma: The vma which maps this object.
+ * @pgoff_anon: The anonymous page index belonging to the folio.
+ * @nr_pages: The number of pages to consider.
+ *
+ * This is only valid for anonymous or MAP_PRIVATE-mapped file-backed VMAs.
+ *
+ * Returns: If any page in this range is mapped by this VMA, return the first
+ * address where any of these pages appear. Otherwise, return -EFAULT.
+ */
+static inline unsigned long vma_anon_address(const struct vm_area_struct *vma,
+ pgoff_t pgoff_anon, unsigned long nr_pages)
+{
+ VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
+
+ return __vma_address(vma, pgoff_anon, vma_start_anon_pgoff(vma), nr_pages);
+}
+
/*
* Then at what user virtual address will none of the range be found in vma?
* Assumes that vma_address() already returned a good starting address.
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 05/20] mm: update print_bad_page_map() to show anon index if appropriate
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (3 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 04/20] mm: abstract vma_address() and introduce vma_anon_address() Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 06/20] mm: introduce and use vma_filebacked_address() Lorenzo Stoakes (ARM)
` (15 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
If the VMA is a CoW mapping page offset may differ from anon page offset,
indicating different positions in the relevant rmap trees.
Update print_bad_page_map() to reflect that - if the mapping is non-CoW or
the indexes match, then output only one index as before, otherwise output
both with (file) or (anon) suffixes to reflect which is which.
It's not possible to give only one index as there is no folio available to
perform folio_test_anon() upon (the page table entry is bad so this is
unavailable).
This is potentially useful debugging information and matches the existing
page offset provided.
Use the raw __linear_anon_page_index() function so as to always output this
value regardless of whether the mapping is file-backed or not and to avoid
asserts that shouldn't apply here.
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/memory.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/mm/memory.c b/mm/memory.c
index e1349e18f046..3bd3616b27d2 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -631,13 +631,14 @@ static void print_bad_page_map(struct vm_area_struct *vma,
{
struct address_space *mapping;
char entry_str[PTVAL_STR_MAX];
- pgoff_t index;
+ pgoff_t index, anon_index;
if (is_bad_page_map_ratelimited())
return;
mapping = vma->vm_file ? vma->vm_file->f_mapping : NULL;
index = linear_page_index(vma, addr);
+ anon_index = __linear_anon_page_index(vma, addr);
ptval_bytes_to_hex_str(entry_str, sizeof(entry_str), entry, entry_size);
pr_alert("BUG: Bad page map in process %s %s:%s", current->comm,
@@ -645,8 +646,14 @@ static void print_bad_page_map(struct vm_area_struct *vma,
__print_bad_page_map_pgtable(vma->vm_mm, addr);
if (page)
dump_page(page, "bad page map");
- pr_alert("addr:%px vm_flags:%08lx anon_vma:%px mapping:%px index:%lx\n",
- (void *)addr, vma->vm_flags, vma->anon_vma, mapping, index);
+ pr_alert("addr:%px vm_flags:%08lx anon_vma:%px mapping:%px",
+ (void *)addr, vma->vm_flags, vma->anon_vma, mapping);
+ if (!vma_is_cow_mapping(vma) || index == anon_index) {
+ pr_cont(" index:%lx\n", index);
+ } else {
+ pr_cont(" index:%lx (file) %lx (anon)\n", index, anon_index);
+ }
+
pr_alert("file:%pD fault:%ps mmap:%ps mmap_prepare: %ps read_folio:%ps\n",
vma->vm_file,
vma->vm_ops ? vma->vm_ops->fault : NULL,
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 06/20] mm: introduce and use vma_filebacked_address()
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (4 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 05/20] mm: update print_bad_page_map() to show anon index if appropriate Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 07/20] mm/vma: fix self-merge check in copy_vma() Lorenzo Stoakes (ARM)
` (14 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel, syzbot
In cases where we know that the VMA is file-backed, use
vma_filebacked_address() rather than vma_address().
This lays the foundation for using the anonymous page offset via
vma_anon_address().
Also add an assert to ensure that the VMA whose address is required is not
anonymous.
No functional change intended.
Tested-by: syzbot@syzkaller.appspotmail.com
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 18 ++++++++++++++++++
mm/memory-failure.c | 4 ++--
mm/page_vma_mapped.c | 6 +++++-
mm/rmap.c | 10 ++++++----
4 files changed, 31 insertions(+), 7 deletions(-)
diff --git a/mm/internal.h b/mm/internal.h
index 10b6662be8ed..03145b8d0d56 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1025,6 +1025,24 @@ static inline unsigned long __vma_address(const struct vm_area_struct *vma,
return address;
}
+/**
+ * vma_filebacked_address - Find the virtual address a file-backed page range is
+ * mapped at.
+ * @vma: The vma which maps this object.
+ * @pgoff: The page offset within its object.
+ * @nr_pages: The number of pages to consider.
+ *
+ * Returns: If any page in this range is mapped by this VMA, return the first
+ * address where any of these pages appear. Otherwise, return -EFAULT.
+ */
+static inline unsigned long vma_filebacked_address(const struct vm_area_struct *vma,
+ pgoff_t pgoff, unsigned long nr_pages)
+{
+ VM_WARN_ON_ONCE(vma_is_anonymous(vma));
+
+ return __vma_address(vma, pgoff, vma_start_pgoff(vma), nr_pages);
+}
+
/**
* vma_address - Find the virtual address a page range is mapped at.
* @vma: The vma which maps this object.
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index aaf14608b30e..a8b03e2920ba 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -620,7 +620,7 @@ static void add_to_kill_fsdax(struct task_struct *tsk, const struct page *p,
struct vm_area_struct *vma,
struct list_head *to_kill, pgoff_t pgoff)
{
- unsigned long addr = vma_address(vma, pgoff, 1);
+ unsigned long addr = vma_filebacked_address(vma, pgoff, 1);
__add_to_kill(tsk, p, vma, to_kill, addr);
}
@@ -2265,7 +2265,7 @@ static void add_to_kill_pgoff(struct task_struct *tsk,
}
/* Check for pgoff not backed by struct page */
- tk->addr = vma_address(vma, pgoff, 1);
+ tk->addr = vma_filebacked_address(vma, pgoff, 1);
tk->size_shift = PAGE_SHIFT;
if (tk->addr == -EFAULT)
diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c
index d7670ba4147b..081e483cc7bf 100644
--- a/mm/page_vma_mapped.c
+++ b/mm/page_vma_mapped.c
@@ -356,6 +356,7 @@ unsigned long page_mapped_in_vma(const struct page *page,
struct vm_area_struct *vma)
{
const struct folio *folio = page_folio(page);
+ const pgoff_t pgoff = page_pgoff(folio, page);
struct page_vma_mapped_walk pvmw = {
.pfn = page_to_pfn(page),
.nr_pages = 1,
@@ -363,7 +364,10 @@ unsigned long page_mapped_in_vma(const struct page *page,
.flags = PVMW_SYNC,
};
- pvmw.address = vma_address(vma, page_pgoff(folio, page), 1);
+ if (folio_test_anon(folio))
+ pvmw.address = vma_address(vma, pgoff, 1);
+ else
+ pvmw.address = vma_filebacked_address(vma, pgoff, 1);
if (pvmw.address == -EFAULT)
goto out;
if (!page_vma_mapped_walk(&pvmw))
diff --git a/mm/rmap.c b/mm/rmap.c
index ad820fe86f7d..5798427d007f 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -865,14 +865,15 @@ unsigned long page_address_in_vma(const struct folio *folio,
if (!vma->anon_vma || !anon_vma ||
vma->anon_vma->root != anon_vma->root)
return -EFAULT;
+ /* KSM folios don't reach here because of the !anon_vma check */
+ return vma_address(vma, page_pgoff(folio, page), 1);
} else if (!vma->vm_file) {
return -EFAULT;
} else if (vma->vm_file->f_mapping != folio->mapping) {
return -EFAULT;
}
- /* KSM folios don't reach here because of the !anon_vma check */
- return vma_address(vma, page_pgoff(folio, page), 1);
+ return vma_filebacked_address(vma, page_pgoff(folio, page), 1);
}
/*
@@ -1321,7 +1322,7 @@ int pfn_mkclean_range(unsigned long pfn, unsigned long nr_pages, pgoff_t pgoff,
if (invalid_mkclean_vma(vma, NULL))
return 0;
- pvmw.address = vma_address(vma, pgoff, nr_pages);
+ pvmw.address = vma_filebacked_address(vma, pgoff, nr_pages);
VM_BUG_ON_VMA(pvmw.address == -EFAULT, vma);
return page_vma_mkclean_one(&pvmw);
@@ -3100,7 +3101,8 @@ static void __rmap_walk_file(struct folio *folio, struct address_space *mapping,
}
lookup:
mapping_rmap_tree_foreach(vma, mapping, pgoff_start, pgoff_end) {
- unsigned long address = vma_address(vma, pgoff_start, nr_pages);
+ unsigned long address = vma_filebacked_address(vma, pgoff_start,
+ nr_pages);
VM_BUG_ON_VMA(address == -EFAULT, vma);
cond_resched();
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 07/20] mm/vma: fix self-merge check in copy_vma()
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (5 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 06/20] mm: introduce and use vma_filebacked_address() Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 08/20] tools/testing/vma: add tests for copy_vma() self-merge Lorenzo Stoakes (ARM)
` (13 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
The existing logic is very confusing so improve things. Firstly rename the
confusing faulted_in_anon_vma variable to can_self_merge and update this
when the page offset is updated.
What is being checked for is a 'self-merge' - that is between the VMA being
remapped and its prior VMA (remember that this is copy_vma() - if a
non-MREMAP_DONTUNMAP remap the original VMA is only removed afterwards).
This can happen if the VMA is moved immediately adjacent to
itself, either before or after it:
|----------------|----------------|
| | |
v | v
|...............||---------------||...............|
| new || old || new |
|...............||---------------||---------------|
In these cases the old VMA is simply expanded to cover the new range.
It is also possible for the move to both self-merge and merge with a prior
VMA if it is placed between a preceding VMA and its old self:
|---------------|
| |
v |
|---------------||...............||---------------|
| prev || new || old |
|---------------||...............||---------------|
In this case, the old VMA is removed and 'prev' is expanded and replaces
it.
Since copy_vma_and_data() which calls copy_vma() intends to reference the
old VMA after the merge, it must have this pointer updated.
This kind of self-merge is not possible with a succeeding merge, as the
merge always prefers to expand the preceding VMA if possible.
copy_vma() accounts for this by explicitly checking to see if a self-merge
occurred and updating the vmap pointer if so. However it incorrect did so
even for a subsequent merge (this is simply a noop so it had no impact).
So change this to only check for the case which matters - a backwards
merge - and rearrange the parameters to make it clearer we're doing that -
i.e. check new_vma->vm_start < old_vma_start (having already renamed
vma_start to old_vma_start to make it clear this is the previous VMA).
Also update the existing wall-of-text comment to be a lot clearer.
While we're here, replace the VM_BUG_ON_VMA() with a VM_WARN_ON_ONCE_VMA()
and update the VMA userland tests accordingly.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/vma.c | 35 ++++++++++++++++-------------------
tools/testing/vma/vma_internal.h | 1 +
2 files changed, 17 insertions(+), 19 deletions(-)
diff --git a/mm/vma.c b/mm/vma.c
index b5bc3eec961c..18c8f2546765 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -1911,10 +1911,10 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
bool *need_rmap_locks)
{
struct vm_area_struct *vma = *vmap;
- unsigned long vma_start = vma->vm_start;
+ unsigned long old_vma_start = vma->vm_start;
struct mm_struct *mm = vma->vm_mm;
struct vm_area_struct *new_vma;
- bool faulted_in_anon_vma = true;
+ bool can_self_merge = false;
VMA_ITERATOR(vmi, mm, addr);
VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len);
@@ -1924,7 +1924,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
*/
if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) {
pgoff = addr >> PAGE_SHIFT;
- faulted_in_anon_vma = false;
+ can_self_merge = true;
}
/*
@@ -1944,24 +1944,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
new_vma = vma_merge_copied_range(&vmg);
if (new_vma) {
- /*
- * Source vma may have been merged into new_vma
- */
- if (unlikely(vma_start >= new_vma->vm_start &&
- vma_start < new_vma->vm_end)) {
+ /* Self-merged and VMA replaced. */
+ if (unlikely(new_vma->vm_start < old_vma_start &&
+ new_vma->vm_end > old_vma_start)) {
/*
- * The only way we can get a vma_merge with
- * self during an mremap is if the vma hasn't
- * been faulted in yet and we were allowed to
- * reset the dst vma->vm_pgoff to the
- * destination address of the mremap to allow
- * the merge to happen. mremap must change the
- * vm_pgoff linearity between src and dst vmas
- * (in turn preventing a vma_merge) to be
- * safe. It is only safe to keep the vm_pgoff
- * linear if there are no pages mapped yet.
+ * The only way a VMA can both self-merge and be
+ * replaced is if the remap places the new VMA
+ * immediately prior to its old self ('next') and
+ * immediately after another VMA ('prev') causing the
+ * next to be removed and prev to be expanded to cover
+ * the entire range.
+ *
+ * This should only be possible if the page offset was
+ * updated, i.e. the VMA is unfaulted.
*/
- VM_BUG_ON_VMA(faulted_in_anon_vma, new_vma);
+ VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma);
*vmap = vma = new_vma;
}
*need_rmap_locks =
diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h
index 4f6c5666ac07..8a48b231aa7a 100644
--- a/tools/testing/vma/vma_internal.h
+++ b/tools/testing/vma/vma_internal.h
@@ -53,6 +53,7 @@ typedef __bitwise unsigned int vm_fault_t;
#define VM_WARN_ON(_expr) (WARN_ON(_expr))
#define VM_WARN_ON_ONCE(_expr) (WARN_ON_ONCE(_expr))
+#define VM_WARN_ON_ONCE_VMA(_expr, _vma) (WARN_ON_ONCE(_expr))
#define VM_WARN_ON_VMG(_expr, _vmg) (WARN_ON(_expr))
#define VM_BUG_ON(_expr) (BUG_ON(_expr))
#define VM_BUG_ON_VMA(_expr, _vma) (BUG_ON(_expr))
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 08/20] tools/testing/vma: add tests for copy_vma() self-merge
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (6 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 07/20] mm/vma: fix self-merge check in copy_vma() Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 09/20] mm: propagate VMA anonymous page offset on map, remap, split + merge Lorenzo Stoakes (ARM)
` (12 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Assert that a VMA can be moved backwards, forwards and between a preceding
VMA and its old self.
In the cases in which the VMA merges only with itself expect that to be
achieved by expanding its old self, so assert that these function
correctly.
However in the case of a merge between a preceding VMA and itself the
original VMA is removed, so assert that the preceding VMA replaces the one
passed in as vmap and the merge is as expected.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
tools/testing/vma/tests/vma.c | 46 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c
index 754a2da06321..0d40d7ba2181 100644
--- a/tools/testing/vma/tests/vma.c
+++ b/tools/testing/vma/tests/vma.c
@@ -33,7 +33,51 @@ static bool test_copy_vma(void)
struct mm_struct mm = {};
bool need_locks = false;
VMA_ITERATOR(vmi, &mm, 0);
- struct vm_area_struct *vma, *vma_new, *vma_next;
+ struct vm_area_struct *vma, *vma_prev, *vma_new, *vma_next, *vma_orig;
+
+ /* Move forwards, adjacent to old self - self-merge. */
+
+ vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, &need_locks);
+ ASSERT_EQ(vma_new, vma_orig);
+ ASSERT_EQ(vma, vma_orig);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x3000);
+
+ cleanup_mm(&mm, &vmi);
+
+ /* Move backwards, adjacent to old self - self-merge. */
+
+ vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, &need_locks);
+ ASSERT_EQ(vma_new, vma_orig);
+ ASSERT_EQ(vma, vma_orig);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x3000);
+
+ cleanup_mm(&mm, &vmi);
+
+ /*
+ * Move backwards between prior VMA and old self - self-merge and vma
+ * updated to a new VMA.
+ */
+
+ vma_prev = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags);
+ vma_set_anonymous(vma_prev);
+ vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, &need_locks);
+ ASSERT_NE(vma_new, vma_orig);
+ ASSERT_EQ(vma_new, vma);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x4000);
+
+ cleanup_mm(&mm, &vmi);
/* Move backwards and do not merge. */
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 09/20] mm: propagate VMA anonymous page offset on map, remap, split + merge
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (7 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 08/20] tools/testing/vma: add tests for copy_vma() self-merge Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 10/20] mm/rmap: track whether the page VMA mapped pgoff is anonymous Lorenzo Stoakes (ARM)
` (11 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
We must correctly update VMA anonymous page offset state on all VMA
operations that would result in it changing, with special attention given
to remapping.
We cover most cases by simply updating vma_set_range() to do so (with a new
anonymous page offset parameter), but also notably must update the merging
and mapping logic to propagate this parameter correctly.
The remap logic remains the same - we may update the anonymous page offset
if the VMA is unfaulted, but now this applies to MAP_PRIVATE file-backed
mappings too, so we update the code to reflect this.
Note that we use __linear_anon_page_index() upon remap as the VMA may be
shared, in order that we update the field consistently regardless of VMA
type.
Similarly, pass through anon page offset to the merge logic, updating the
vma_merge_struct struct to propagate it, and also use
__linear_anon_page_index() to obtain the anonymous page index so it can be
safely used for both shared and MAP_PRIVATE file-backed mappings.
In copy_vma(), the anonymous page offset is updated regardless of whether
the mapping is a CoW mapping or not. This is both to keep the anonymous
page offset consistent even for non-CoW mappings (it is set so should at
least remain correct) and makes the logic cleaner.
A self-merge however remains permitted only for mappings which can have a
populated vma->anon_vma and do not require alignment on a separate file
offset - that is pure anonymous VMAs, so only set can_self_merge if
vma_is_anonymous().
Finally, we update insert_vm_struct() to correctly set the anonymous page
offset on insertion of a VMA.
We simply ensure state is correctly propagated here, so no functional
changes are intended.
Also update VMA userland tests to reflect this change.
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/mremap.c | 6 ++--
mm/vma.c | 52 ++++++++++++++++++----------
mm/vma.h | 75 ++++++++++++++++++++++++-----------------
mm/vma_exec.c | 2 +-
tools/testing/vma/shared.c | 3 +-
tools/testing/vma/tests/merge.c | 4 ++-
tools/testing/vma/tests/vma.c | 10 +++---
7 files changed, 95 insertions(+), 57 deletions(-)
diff --git a/mm/mremap.c b/mm/mremap.c
index b64aa1f6e07e..9ea1707eafa5 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -1265,7 +1265,9 @@ static void unmap_source_vma(struct vma_remap_struct *vrm)
static int copy_vma_and_data(struct vma_remap_struct *vrm,
struct vm_area_struct **new_vma_ptr)
{
- const unsigned long new_pgoff = linear_page_index(vrm->vma, vrm->addr);
+ const pgoff_t new_pgoff = linear_page_index(vrm->vma, vrm->addr);
+ const pgoff_t new_anon_pgoff =
+ __linear_anon_page_index(vrm->vma, vrm->addr);
struct vm_area_struct *vma = vrm->vma;
struct vm_area_struct *new_vma;
unsigned long moved_len;
@@ -1273,7 +1275,7 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm,
PAGETABLE_MOVE(pmc, NULL, NULL, vrm->addr, vrm->new_addr, vrm->old_len);
new_vma = copy_vma(&vma, vrm->new_addr, vrm->new_len, new_pgoff,
- &pmc.need_rmap_locks);
+ new_anon_pgoff, &pmc.need_rmap_locks);
if (!new_vma) {
vrm_uncharge(vrm);
*new_vma_ptr = NULL;
diff --git a/mm/vma.c b/mm/vma.c
index 18c8f2546765..b55015952f0d 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -18,6 +18,7 @@ struct mmap_state {
unsigned long addr;
unsigned long end;
pgoff_t pgoff;
+ pgoff_t anon_pgoff;
unsigned long pglen;
union {
vm_flags_t vm_flags;
@@ -46,13 +47,14 @@ struct mmap_state {
bool file_doesnt_need_get :1;
};
-#define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, vma_flags_, file_) \
+#define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
struct mmap_state name = { \
.mm = mm_, \
.vmi = vmi_, \
.addr = addr_, \
.end = (addr_) + (len_), \
.pgoff = pgoff_, \
+ .anon_pgoff = anon_pgoff_, \
.pglen = PHYS_PFN(len_), \
.vma_flags = vma_flags_, \
.file = file_, \
@@ -67,6 +69,7 @@ struct mmap_state {
.end = (map_)->end, \
.vma_flags = (map_)->vma_flags, \
.pgoff = (map_)->pgoff, \
+ .anon_pgoff = (map_)->anon_pgoff, \
.file = (map_)->file, \
.prev = (map_)->prev, \
.middle = vma_, \
@@ -82,10 +85,11 @@ static void __vma_set_range(struct vm_area_struct *vma, unsigned long start,
}
static void vma_set_range(struct vm_area_struct *vma, unsigned long start,
- unsigned long end, pgoff_t pgoff)
+ unsigned long end, pgoff_t pgoff, pgoff_t anon_pgoff)
{
__vma_set_range(vma, start, end);
vma_set_pgoff(vma, pgoff);
+ vma_set_anon_pgoff(vma, anon_pgoff);
}
/* Was this VMA ever forked from a parent, i.e. maybe contains CoW mappings? */
@@ -812,7 +816,8 @@ static int commit_merge(struct vma_merge_struct *vmg)
*/
vma_adjust_trans_huge(vma, vmg->start, vmg->end,
vmg->__adjust_middle_start ? vmg->middle : NULL);
- vma_set_range(vma, vmg->start, vmg->end, vmg_start_pgoff(vmg));
+ vma_set_range(vma, vmg->start, vmg->end, vmg_start_pgoff(vmg),
+ vmg_start_anon_pgoff(vmg));
vmg_adjust_set_range(vmg);
vma_iter_store_overwrite(vmg->vmi, vmg->target);
@@ -982,6 +987,7 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
vmg->start = prev->vm_start;
vmg->end = next->vm_end;
vmg->pgoff = vma_start_pgoff(prev);
+ vmg->anon_pgoff = vma_start_anon_pgoff(prev);
/*
* We already ensured anon_vma compatibility above, so now it's
@@ -1000,6 +1006,7 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
*/
vmg->start = prev->vm_start;
vmg->pgoff = vma_start_pgoff(prev);
+ vmg->anon_pgoff = vma_start_anon_pgoff(prev);
if (!vmg->__remove_middle)
vmg->__adjust_middle_start = true;
@@ -1022,12 +1029,14 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
if (vmg->__remove_middle) {
vmg->end = next->vm_end;
vmg->pgoff = vma_start_pgoff(next) - pglen;
+ vmg->anon_pgoff = vma_start_anon_pgoff(next) - pglen;
} else {
/* We shrink middle and expand next. */
vmg->__adjust_next_start = true;
vmg->start = middle->vm_start;
vmg->end = start;
vmg->pgoff = vma_start_pgoff(middle);
+ vmg->anon_pgoff = vma_start_anon_pgoff(middle);
}
err = dup_anon_vma(next, middle, &anon_dup);
@@ -1137,6 +1146,7 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg)
vmg->start = prev->vm_start;
vmg->target = prev;
vmg->pgoff = vma_start_pgoff(prev);
+ vmg->anon_pgoff = vma_start_anon_pgoff(prev);
/*
* If this merge would result in removal of the next VMA but we
@@ -1908,7 +1918,7 @@ static int vma_link(struct mm_struct *mm, struct vm_area_struct *vma)
*/
struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
unsigned long addr, unsigned long len, pgoff_t pgoff,
- bool *need_rmap_locks)
+ pgoff_t anon_pgoff, bool *need_rmap_locks)
{
struct vm_area_struct *vma = *vmap;
unsigned long old_vma_start = vma->vm_start;
@@ -1919,12 +1929,16 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len);
/*
- * If anonymous vma has not yet been faulted, update new pgoff
- * to match new location, to increase its chance of merging.
+ * If a vma has not yet been faulted, update its anonymous pgoff to
+ * match the new location to increase its chance of merging.
*/
- if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) {
- pgoff = addr >> PAGE_SHIFT;
- can_self_merge = true;
+ if (!vma->anon_vma) {
+ anon_pgoff = addr >> PAGE_SHIFT;
+
+ if (vma_is_anonymous(vma)) {
+ pgoff = anon_pgoff;
+ can_self_merge = true;
+ }
}
/*
@@ -1940,6 +1954,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
return NULL; /* should never get here */
vmg.pgoff = pgoff;
+ vmg.anon_pgoff = anon_pgoff;
vmg.next = vma_iter_next_rewind(&vmi, NULL);
new_vma = vma_merge_copied_range(&vmg);
@@ -1955,8 +1970,8 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
* next to be removed and prev to be expanded to cover
* the entire range.
*
- * This should only be possible if the page offset was
- * updated, i.e. the VMA is unfaulted.
+ * This should only be possible if the anonymous page
+ * offset was updated, i.e. the VMA is unfaulted.
*/
VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma);
*vmap = vma = new_vma;
@@ -1967,7 +1982,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
new_vma = vm_area_dup(vma);
if (!new_vma)
goto out;
- vma_set_range(new_vma, addr, addr + len, pgoff);
+ vma_set_range(new_vma, addr, addr + len, pgoff, anon_pgoff);
if (vma_dup_policy(vma, new_vma))
goto out_free_vma;
if (anon_vma_clone(new_vma, vma, VMA_OP_REMAP))
@@ -2609,7 +2624,7 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
if (is_anon)
vma_set_anonymous(vma);
- vma_set_range(vma, map->addr, map->end, map->pgoff);
+ vma_set_range(vma, map->addr, map->end, map->pgoff, map->anon_pgoff);
vma->flags = map->vma_flags;
vma->vm_page_prot = map->page_prot;
@@ -2798,7 +2813,8 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,
struct vm_area_struct *vma = NULL;
bool have_mmap_prepare = file && file->f_op->mmap_prepare;
VMA_ITERATOR(vmi, mm, addr);
- MMAP_STATE(map, mm, &vmi, addr, len, pgoff, vma_flags, file);
+ const pgoff_t anon_pgoff = addr >> PAGE_SHIFT;
+ MMAP_STATE(map, mm, &vmi, addr, len, pgoff, anon_pgoff, vma_flags, file);
struct vm_area_desc desc = {
.mm = mm,
.file = file,
@@ -2943,6 +2959,7 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *vma,
unsigned long addr, unsigned long len, vma_flags_t vma_flags)
{
struct mm_struct *mm = current->mm;
+ const pgoff_t pgoff = addr >> PAGE_SHIFT;
/*
* Check against address space limits by the changed size
@@ -2967,7 +2984,7 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *vma,
* occur after forking, so the expand will only happen on new VMAs.
*/
if (vma && vma->vm_end == addr) {
- VMG_STATE(vmg, mm, vmi, addr, addr + len, vma_flags, PHYS_PFN(addr));
+ VMG_STATE(vmg, mm, vmi, addr, addr + len, vma_flags, pgoff, pgoff);
vmg.prev = vma;
/* vmi is positioned at prev, which this mode expects. */
@@ -2987,7 +3004,7 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *vma,
goto unacct_fail;
vma_set_anonymous(vma);
- vma_set_range(vma, addr, addr + len, addr >> PAGE_SHIFT);
+ vma_set_range(vma, addr, addr + len, pgoff, pgoff);
vma->flags = vma_flags;
vma->vm_page_prot = vm_get_page_prot(vma_flags_to_legacy(vma_flags));
vma_start_write(vma);
@@ -3379,6 +3396,7 @@ int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)
WARN_ON_ONCE(vma->anon_vma);
vma_set_pgoff(vma, vma->vm_start >> PAGE_SHIFT);
}
+ vma_set_anon_pgoff(vma, vma->vm_start >> PAGE_SHIFT);
if (vma_link(mm, vma)) {
if (vma_test(vma, VMA_ACCOUNT_BIT))
@@ -3434,7 +3452,7 @@ struct vm_area_struct *__install_special_mapping(
vma->vm_ops = ops;
vma->vm_private_data = priv;
- vma_set_range(vma, addr, addr + len, 0);
+ vma_set_range(vma, addr, addr + len, 0, addr >> PAGE_SHIFT);
ret = insert_vm_struct(mm, vma);
if (ret)
diff --git a/mm/vma.h b/mm/vma.h
index 54ed7c744e3b..024fabe63560 100644
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -104,6 +104,7 @@ struct vma_merge_struct {
unsigned long start;
unsigned long end;
pgoff_t pgoff;
+ pgoff_t anon_pgoff;
union {
/* Temporary while VMA flags are being converted. */
@@ -237,11 +238,6 @@ static inline bool vmg_nomem(struct vma_merge_struct *vmg)
return vmg->state == VMA_MERGE_ERROR_NOMEM;
}
-static inline pgoff_t vmg_start_pgoff(const struct vma_merge_struct *vmg)
-{
- return vmg->pgoff;
-}
-
static inline pgoff_t vmg_pages(const struct vma_merge_struct *vmg)
{
const unsigned long size = vmg->end - vmg->start;
@@ -249,6 +245,11 @@ static inline pgoff_t vmg_pages(const struct vma_merge_struct *vmg)
return size >> PAGE_SHIFT;
}
+static inline pgoff_t vmg_start_pgoff(const struct vma_merge_struct *vmg)
+{
+ return vmg->pgoff;
+}
+
static inline pgoff_t vmg_end_pgoff(const struct vma_merge_struct *vmg)
{
return vmg_start_pgoff(vmg) + vmg_pages(vmg);
@@ -283,6 +284,16 @@ static inline void vma_set_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
vma->vm_pgoff = pgoff;
}
+static inline pgoff_t vmg_start_anon_pgoff(const struct vma_merge_struct *vmg)
+{
+ return vmg->anon_pgoff;
+}
+
+static inline pgoff_t vmg_end_anon_pgoff(const struct vma_merge_struct *vmg)
+{
+ return vmg_start_anon_pgoff(vmg) + vmg_pages(vmg);
+}
+
static inline void __vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
{
#ifdef CONFIG_64BIT
@@ -301,44 +312,48 @@ static inline void vma_add_pgoff(struct vm_area_struct *vma, pgoff_t delta)
{
vma_assert_can_modify(vma);
vma_set_pgoff(vma, vma_start_pgoff(vma) + delta);
+ vma_set_anon_pgoff(vma, vma_start_anon_pgoff(vma) + delta);
}
static inline void vma_sub_pgoff(struct vm_area_struct *vma, pgoff_t delta)
{
vma_assert_can_modify(vma);
vma_set_pgoff(vma, vma_start_pgoff(vma) - delta);
-}
+ vma_set_anon_pgoff(vma, vma_start_anon_pgoff(vma) - delta);
+}
+
+#define VMG_STATE(name, mm_, vmi_, start_, end_, vma_flags_, pgoff_, anon_pgoff_) \
+ struct vma_merge_struct name = { \
+ .mm = mm_, \
+ .vmi = vmi_, \
+ .start = start_, \
+ .end = end_, \
+ .vma_flags = vma_flags_, \
+ .pgoff = pgoff_, \
+ .anon_pgoff = anon_pgoff_, \
+ .state = VMA_MERGE_START, \
+ }
-#define VMG_STATE(name, mm_, vmi_, start_, end_, vma_flags_, pgoff_) \
+#define VMG_VMA_STATE(name, vmi_, prev_, vma_, start_, end_) \
struct vma_merge_struct name = { \
- .mm = mm_, \
+ .mm = vma_->vm_mm, \
.vmi = vmi_, \
+ .prev = prev_, \
+ .middle = vma_, \
+ .next = NULL, \
.start = start_, \
.end = end_, \
- .vma_flags = vma_flags_, \
- .pgoff = pgoff_, \
+ .vm_flags = vma_->vm_flags, \
+ .pgoff = linear_page_index(vma_, start_), \
+ .anon_pgoff = __linear_anon_page_index(vma_, start_), \
+ .file = vma_->vm_file, \
+ .anon_vma = vma_->anon_vma, \
+ .policy = vma_policy(vma_), \
+ .uffd_ctx = vma_->vm_userfaultfd_ctx, \
+ .anon_name = anon_vma_name(vma_), \
.state = VMA_MERGE_START, \
}
-#define VMG_VMA_STATE(name, vmi_, prev_, vma_, start_, end_) \
- struct vma_merge_struct name = { \
- .mm = vma_->vm_mm, \
- .vmi = vmi_, \
- .prev = prev_, \
- .middle = vma_, \
- .next = NULL, \
- .start = start_, \
- .end = end_, \
- .vm_flags = vma_->vm_flags, \
- .pgoff = linear_page_index(vma_, start_), \
- .file = vma_->vm_file, \
- .anon_vma = vma_->anon_vma, \
- .policy = vma_policy(vma_), \
- .uffd_ctx = vma_->vm_userfaultfd_ctx, \
- .anon_name = anon_vma_name(vma_), \
- .state = VMA_MERGE_START, \
- }
-
#ifdef CONFIG_DEBUG_VM_MAPLE_TREE
void validate_mm(struct mm_struct *mm);
#else
@@ -520,7 +535,7 @@ void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb,
struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
unsigned long addr, unsigned long len, pgoff_t pgoff,
- bool *need_rmap_locks);
+ pgoff_t anon_pgoff, bool *need_rmap_locks);
struct anon_vma *find_mergeable_anon_vma(struct vm_area_struct *vma);
diff --git a/mm/vma_exec.c b/mm/vma_exec.c
index 7af1260689b9..586c52155942 100644
--- a/mm/vma_exec.c
+++ b/mm/vma_exec.c
@@ -41,7 +41,7 @@ int relocate_vma_down(struct vm_area_struct *vma, unsigned long shift)
unsigned long new_end = old_end - shift;
VMA_ITERATOR(vmi, mm, new_start);
VMG_STATE(vmg, mm, &vmi, new_start, old_end, EMPTY_VMA_FLAGS,
- vma_start_pgoff(vma));
+ vma_start_pgoff(vma), vma_start_anon_pgoff(vma));
struct vm_area_struct *next;
struct mmu_gather tlb;
PAGETABLE_MOVE(pmc, vma, vma, old_start, new_start, length);
diff --git a/tools/testing/vma/shared.c b/tools/testing/vma/shared.c
index bea9ea6db02a..4a39c9d50489 100644
--- a/tools/testing/vma/shared.c
+++ b/tools/testing/vma/shared.c
@@ -23,7 +23,8 @@ struct vm_area_struct *alloc_vma(struct mm_struct *mm,
vma->vm_start = start;
vma->vm_end = end;
- vma->vm_pgoff = pgoff;
+ vma_set_pgoff(vma, pgoff);
+ vma_set_anon_pgoff(vma, start >> PAGE_SHIFT);
vma->flags = vma_flags;
vma_assert_detached(vma);
diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c
index e357accc8499..48418b82b01d 100644
--- a/tools/testing/vma/tests/merge.c
+++ b/tools/testing/vma/tests/merge.c
@@ -45,6 +45,7 @@ void vmg_set_range(struct vma_merge_struct *vmg, unsigned long start,
vmg->start = start;
vmg->end = end;
vmg->pgoff = pgoff;
+ vmg->anon_pgoff = start >> PAGE_SHIFT;
vmg->vma_flags = vma_flags;
vmg->just_expand = false;
@@ -108,6 +109,7 @@ static bool test_simple_merge(void)
.end = 0x2000,
.vma_flags = vma_flags,
.pgoff = 1,
+ .anon_pgoff = 1,
};
ASSERT_FALSE(attach_vma(&mm, vma_left));
@@ -1431,7 +1433,7 @@ static bool test_expand_only_mode(void)
struct mm_struct mm = {};
VMA_ITERATOR(vmi, &mm, 0);
struct vm_area_struct *vma_prev, *vma;
- VMG_STATE(vmg, &mm, &vmi, 0x5000, 0x9000, vma_flags, 5);
+ VMG_STATE(vmg, &mm, &vmi, 0x5000, 0x9000, vma_flags, 5, 5);
/*
* Place a VMA prior to the one we're expanding so we assert that we do
diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c
index 0d40d7ba2181..c8ef7b8cd46b 100644
--- a/tools/testing/vma/tests/vma.c
+++ b/tools/testing/vma/tests/vma.c
@@ -40,7 +40,7 @@ static bool test_copy_vma(void)
vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags);
vma_set_anonymous(vma);
vma_orig = vma;
- vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, &need_locks);
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks);
ASSERT_EQ(vma_new, vma_orig);
ASSERT_EQ(vma, vma_orig);
ASSERT_EQ(vma_new->vm_start, 0x1000);
@@ -53,7 +53,7 @@ static bool test_copy_vma(void)
vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags);
vma_set_anonymous(vma);
vma_orig = vma;
- vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, &need_locks);
+ vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks);
ASSERT_EQ(vma_new, vma_orig);
ASSERT_EQ(vma, vma_orig);
ASSERT_EQ(vma_new->vm_start, 0x1000);
@@ -71,7 +71,7 @@ static bool test_copy_vma(void)
vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags);
vma_set_anonymous(vma);
vma_orig = vma;
- vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, &need_locks);
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks);
ASSERT_NE(vma_new, vma_orig);
ASSERT_EQ(vma_new, vma);
ASSERT_EQ(vma_new->vm_start, 0x1000);
@@ -82,7 +82,7 @@ static bool test_copy_vma(void)
/* Move backwards and do not merge. */
vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, vma_flags);
- vma_new = copy_vma(&vma, 0, 0x2000, 0, &need_locks);
+ vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks);
ASSERT_NE(vma_new, vma);
ASSERT_EQ(vma_new->vm_start, 0);
ASSERT_EQ(vma_new->vm_end, 0x2000);
@@ -95,7 +95,7 @@ static bool test_copy_vma(void)
vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, vma_flags);
vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, vma_flags);
- vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, &need_locks);
+ vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks);
vma_assert_attached(vma_new);
ASSERT_EQ(vma_new, vma_next);
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 10/20] mm/rmap: track whether the page VMA mapped pgoff is anonymous
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (8 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 09/20] mm: propagate VMA anonymous page offset on map, remap, split + merge Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 11/20] mm: clean up vma_address_end() Lorenzo Stoakes (ARM)
` (10 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Update the page_vma_mapped_walk structure to track whether the pgoff being
tracked is an anonymous pgoff or not and update the comments to reflect
this.
This is necessary in order to determine the correct VMA page
offset in vma_address_end() when pvmw->nr_pages > 1.
Also document that pvmw->pgoff is meaningless for pvmw->nr_pages == 1 and
for KSM.
Do not set this field where pgoff is not specified.
This is laying the groundwork for eventually using anonymous page offsets
as the index for all anonymous folios.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/rmap.h | 4 +++-
mm/rmap.c | 2 ++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/include/linux/rmap.h b/include/linux/rmap.h
index 8dc0871e5f00..0574537a355c 100644
--- a/include/linux/rmap.h
+++ b/include/linux/rmap.h
@@ -864,13 +864,14 @@ struct page *make_device_exclusive(struct mm_struct *mm, unsigned long addr,
struct page_vma_mapped_walk {
unsigned long pfn;
unsigned long nr_pages;
- pgoff_t pgoff;
+ pgoff_t pgoff; /* Only meaningful if nr_pages > 1 and not a KSM walk */
struct vm_area_struct *vma;
unsigned long address;
pmd_t *pmd;
pte_t *pte;
spinlock_t *ptl;
unsigned int flags;
+ bool pgoff_is_anon : 1;
};
#define DEFINE_FOLIO_VMA_WALK(name, _folio, _vma, _address, _flags) \
@@ -881,6 +882,7 @@ struct page_vma_mapped_walk {
.vma = _vma, \
.address = _address, \
.flags = _flags, \
+ .pgoff_is_anon = folio_test_anon(_folio), \
}
static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)
diff --git a/mm/rmap.c b/mm/rmap.c
index 5798427d007f..ab3f879454de 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -1240,6 +1240,7 @@ static bool mapping_wrprotect_range_one(struct folio *folio,
.vma = vma,
.address = address,
.flags = PVMW_SYNC,
+ .pgoff_is_anon = false,
};
state->cleaned += page_vma_mkclean_one(&pvmw);
@@ -1317,6 +1318,7 @@ int pfn_mkclean_range(unsigned long pfn, unsigned long nr_pages, pgoff_t pgoff,
.pgoff = pgoff,
.vma = vma,
.flags = PVMW_SYNC,
+ .pgoff_is_anon = false,
};
if (invalid_mkclean_vma(vma, NULL))
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 11/20] mm: clean up vma_address_end()
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (9 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 10/20] mm/rmap: track whether the page VMA mapped pgoff is anonymous Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 12/20] mm/huge_memory: update remove_migration_pmd() to accept a folio Lorenzo Stoakes (ARM)
` (9 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
vma_address_end() is a confusing function with a lot of moving parts so
clean it up prior to extending it for anon page indexed mappings.
Const-ify some variables and establish pgoff_vma_start and pgoff_end
variables to clearly identify the page offset for the start of the VMA and
the end of the page offset range specified by the page walk.
This simplifies the function significantly and lays the groundwork for a
future change to update this function to account for anonymously page
indexed folios.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/internal.h | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/mm/internal.h b/mm/internal.h
index 03145b8d0d56..98b067472ceb 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1079,22 +1079,24 @@ static inline unsigned long vma_anon_address(const struct vm_area_struct *vma,
}
/*
- * Then at what user virtual address will none of the range be found in vma?
+ * At what user virtual address will none of the range be found in vma?
* Assumes that vma_address() already returned a good starting address.
*/
static inline unsigned long vma_address_end(struct page_vma_mapped_walk *pvmw)
{
- struct vm_area_struct *vma = pvmw->vma;
- pgoff_t pgoff;
+ const pgoff_t pgoff_end = pvmw->pgoff + pvmw->nr_pages;
+ const struct vm_area_struct *vma = pvmw->vma;
+ pgoff_t pgoff_vma_start;
unsigned long address;
/* Common case, plus ->pgoff is invalid for KSM */
if (pvmw->nr_pages == 1)
return pvmw->address + PAGE_SIZE;
- pgoff = pvmw->pgoff + pvmw->nr_pages;
+ pgoff_vma_start = vma_start_pgoff(vma);
+
address = vma->vm_start +
- ((pgoff - vma_start_pgoff(vma)) << PAGE_SHIFT);
+ ((pgoff_end - pgoff_vma_start) << PAGE_SHIFT);
/* Check for address beyond vma (or wrapped through 0?) */
if (address < vma->vm_start || address > vma->vm_end)
address = vma->vm_end;
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 12/20] mm/huge_memory: update remove_migration_pmd() to accept a folio
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (10 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 11/20] mm: clean up vma_address_end() Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 13/20] mm/migrate: calculate large folio page index using PFN Lorenzo Stoakes (ARM)
` (8 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
This function does not need to accept a page and requiring it to is unnecessary
and misleading.
make_[writable, readable]_device_private_entry() must be passed a
PMD-aligned PFN as they immediately used to obtain a softleaf PMD entry and
the same argument applies to folio_add_[anon, file]_rmap_pmd().
While we are here, update a VM_BUG_ON() to a VM_WARN_ON_ONCE().
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/swapops.h | 6 +++---
mm/huge_memory.c | 16 +++++++---------
mm/migrate.c | 2 +-
3 files changed, 11 insertions(+), 13 deletions(-)
diff --git a/include/linux/swapops.h b/include/linux/swapops.h
index c956bc445ee0..1f3ff3b93e16 100644
--- a/include/linux/swapops.h
+++ b/include/linux/swapops.h
@@ -325,8 +325,8 @@ struct page_vma_mapped_walk;
extern int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw,
struct page *page);
-extern void remove_migration_pmd(struct page_vma_mapped_walk *pvmw,
- struct page *new);
+void remove_migration_pmd(struct page_vma_mapped_walk *pvmw,
+ struct folio *folio);
extern void pmd_migration_entry_wait(struct mm_struct *mm, pmd_t *pmd);
@@ -346,7 +346,7 @@ static inline int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw,
}
static inline void remove_migration_pmd(struct page_vma_mapped_walk *pvmw,
- struct page *new)
+ struct folio *folio)
{
BUILD_BUG();
}
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 6b0cabd45b2d..47c9c6e32eba 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -5020,9 +5020,8 @@ int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw,
return 0;
}
-void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new)
+void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct folio *folio)
{
- struct folio *folio = page_folio(new);
struct vm_area_struct *vma = pvmw->vma;
struct mm_struct *mm = vma->vm_mm;
unsigned long address = pvmw->address;
@@ -5058,11 +5057,9 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new)
swp_entry_t entry;
if (pmd_write(pmde))
- entry = make_writable_device_private_entry(
- page_to_pfn(new));
+ entry = make_writable_device_private_entry(folio_pfn(folio));
else
- entry = make_readable_device_private_entry(
- page_to_pfn(new));
+ entry = make_readable_device_private_entry(folio_pfn(folio));
pmde = softleaf_to_pmd(entry);
if (pmd_swp_soft_dirty(*pvmw->pmd))
@@ -5077,11 +5074,12 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new)
if (!softleaf_is_migration_read(entry))
rmap_flags |= RMAP_EXCLUSIVE;
- folio_add_anon_rmap_pmd(folio, new, vma, haddr, rmap_flags);
+ folio_add_anon_rmap_pmd(folio, &folio->page, vma, haddr, rmap_flags);
} else {
- folio_add_file_rmap_pmd(folio, new, vma);
+ folio_add_file_rmap_pmd(folio, &folio->page, vma);
}
- VM_BUG_ON(pmd_write(pmde) && folio_test_anon(folio) && !PageAnonExclusive(new));
+ VM_WARN_ON_ONCE(pmd_write(pmde) && folio_test_anon(folio) &&
+ !PageAnonExclusive(&folio->page));
set_pmd_at(mm, haddr, pvmw->pmd, pmde);
/* No need to invalidate - it was non-present before */
diff --git a/mm/migrate.c b/mm/migrate.c
index 222c8c15f782..d08eff028483 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -372,7 +372,7 @@ static bool remove_migration_pte(struct folio *folio,
if (!pvmw.pte) {
VM_BUG_ON_FOLIO(folio_test_hugetlb(folio) ||
!folio_test_pmd_mappable(folio), folio);
- remove_migration_pmd(&pvmw, new);
+ remove_migration_pmd(&pvmw, folio);
continue;
}
#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 13/20] mm/migrate: calculate large folio page index using PFN
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (11 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 12/20] mm/huge_memory: update remove_migration_pmd() to accept a folio Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 14/20] mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios Lorenzo Stoakes (ARM)
` (7 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Rather than having to figure out the page index to use using
linear_page_index(), calculate it using PFN.
This is a more natural fit as the linear page index is immaterial to
determining the folio page index.
Derive the page index from the offset between migration entry PFN and folio
PFN - pvmw.pfn (set via DEFINE_FOLIO_VMA_WALK() which uses folio_pfn() to
obtain it).
Additionally remove a not so useful comment and clean the code layout up.
No functional change intended.
Suggested-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/migrate.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/mm/migrate.c b/mm/migrate.c
index d08eff028483..50d0b547bca3 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -356,16 +356,11 @@ static bool remove_migration_pte(struct folio *folio,
while (page_vma_mapped_walk(&pvmw)) {
rmap_t rmap_flags = RMAP_NONE;
- pte_t old_pte;
- pte_t pte;
+ unsigned long idx = 0;
softleaf_t entry;
struct page *new;
- unsigned long idx = 0;
-
- /* pgoff is invalid for ksm pages, but they are never large */
- if (folio_test_large(folio) && !folio_test_hugetlb(folio))
- idx = linear_page_index(vma, pvmw.address) - pvmw.pgoff;
- new = folio_page(folio, idx);
+ pte_t old_pte;
+ pte_t pte;
#ifdef CONFIG_ARCH_HAS_PMD_SOFTLEAVES
/* PMD-mapped THP migration entry */
@@ -381,14 +376,18 @@ static bool remove_migration_pte(struct folio *folio,
pvmw.pte);
else
old_pte = ptep_get(pvmw.pte);
+
+ entry = softleaf_from_pte(old_pte);
+ if (folio_test_large(folio) && !folio_test_hugetlb(folio))
+ idx = softleaf_to_pfn(entry) - pvmw.pfn;
+
if (rmap_walk_arg->map_unused_to_zeropage &&
try_to_map_unused_to_zeropage(&pvmw, folio, old_pte, idx))
continue;
folio_get(folio);
+ new = folio_page(folio, idx);
pte = mk_pte(new, READ_ONCE(vma->vm_page_prot));
-
- entry = softleaf_from_pte(old_pte);
if (!softleaf_is_migration_young(entry))
pte = pte_mkold(pte);
if (folio_test_dirty(folio) && softleaf_is_migration_dirty(entry))
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 14/20] mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (12 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 13/20] mm/migrate: calculate large folio page index using PFN Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 15/20] tools/testing/vma: expand VMA merge tests to assert anon pgoff Lorenzo Stoakes (ARM)
` (6 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Currently anonymous folios belonging to CoW'd MAP_PRIVATE file-backed
mappings are indexed by their page offset within the file in which they
were originally mapped.
This differs from anonymous folios belonging to pure anon mappings which
are indexed by their anonymous page offset (the address at which they'd
belong in the VMA when first faulted).
This change fixes this inconsistency, always indexing anonymous folios by
their anonymous page offset regardless of the VMA to which they belong.
The foundations have been laid such that we need only switch this
functionality on such by:
* Using linear_anon_page_index() in __folio_set_anon() to assign the
folio's index to the anonymous linear index rather than the file-backed
one.
* Otherwise using linear_anon_page_index() in all instances where
anonymous folios are being referenced or manipulated.
* Replacing vma_address() with vma_filebacked_address() or
vma_anon_address() as appropriate.
* Updating the merging logic to check that anonymous page offsets are
aligned as well as filebacked ones for MAP_PRIVATE file-backed VMAs,
introducing needs_adjacent_anon_pgoff() to figure out when this is
required.
* Updating linear_folio_page_index() to invoke linear_anon_page_index()
if the folio is anonymous.
* Updating vma_address_end() to use the VMA's anonymous page offset when
pvmw->pgoff is anonymous.
* Correcting folio_within_range() to use anonymous page offset for
anonymous folios.
This will have no impact on merging of anonymous VMAs, whose page offset
and anonymous page offset are identical, nor will it impact shared
file-backed VMAs, which will continue to be merged based on the file-backed
page offset.
However, MAP_PRIVATE file-backed mappings must now be aligned on anonymous
page offset as well.
In most instances this should have no impact on merging of file-backed
mappings, which are usually not merged all that often, let alone
MAP_PRIVATE mapped ones, and rarely remapped and faulted before being moved
back in place (the case in which a merge may now fail).
One subtle impact of this change is in NUMA interleaving - since commit
88c91dc58582 ("mempolicy: migration attempt to match interleave nodes"),
migration heuristically tries to maintain interleaving behaviour matching
the policy using folio indices.
When doing migration of CoW'd MAP_PRIVATE-file backed ranges, the 'base'
upon which the interleaving behaviour is performed will vary for these
ranges. However the commit notes that ranges spanning multiple VMAs will
already cause varying bases, and that this is an acceptable approximation.
It is very unlikely real world use-cases will be impacted by
this (MAP_PRIVATE file-backed mappings are already an edge case), and all
that will happen is that such ranges will cause interleaving to be rotated
over the CoW'd range, with little to no impact.
This commit lays the foundations for future scalable CoW work which needs
to track some remaps, meaning that most remap tracking can be avoided, and
in nearly all cases the anonymous page offset will be able to be used to
quickly find the VMA in an mm.
Note that the need_rmap_locks check doesn't need to be updated, as any
remapping will offset both the anonymous and file-backed page offset, so it
suffices to check only one.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/huge_memory.c | 2 +-
mm/internal.h | 27 ++++++++-------------------
mm/interval_tree.c | 4 ++--
mm/ksm.c | 6 +++---
mm/page_vma_mapped.c | 2 +-
mm/rmap.c | 12 ++++++------
mm/userfaultfd.c | 4 ++--
mm/vma.c | 32 +++++++++++++++++++++++++++++++-
8 files changed, 54 insertions(+), 35 deletions(-)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 47c9c6e32eba..e46b3a4750ee 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2887,7 +2887,7 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm
}
folio_move_anon_rmap(src_folio, dst_vma);
- src_folio->index = linear_page_index(dst_vma, dst_addr);
+ src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
_dst_pmd = folio_mk_pmd(src_folio, dst_vma->vm_page_prot);
/* Follow mremap() behavior and treat the entry dirty after the move */
diff --git a/mm/internal.h b/mm/internal.h
index 98b067472ceb..5892b7453b54 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -933,7 +933,8 @@ folio_within_range(struct folio *folio, struct vm_area_struct *vma,
return false;
pgoff_folio = folio_pgoff(folio);
- pgoff_vma_start = vma_start_pgoff(vma);
+ pgoff_vma_start = folio_test_anon(folio) ?
+ vma_start_anon_pgoff(vma) : vma_start_pgoff(vma);
if (start < vma->vm_start)
start = vma->vm_start;
@@ -1044,23 +1045,8 @@ static inline unsigned long vma_filebacked_address(const struct vm_area_struct *
}
/**
- * vma_address - Find the virtual address a page range is mapped at.
- * @vma: The vma which maps this object.
- * @pgoff: The page offset within its object.
- * @nr_pages: The number of pages to consider.
- *
- * If any page in this range is mapped by this VMA, return the first address
- * where any of these pages appear. Otherwise, return -EFAULT.
- */
-static inline unsigned long vma_address(const struct vm_area_struct *vma,
- pgoff_t pgoff, unsigned long nr_pages)
-{
- return __vma_address(vma, pgoff, vma_start_pgoff(vma), nr_pages);
-}
-
-/**
- * vma_anon_address - Find the address an anonymous folio with index @pgoff_anon
- * is mapped at.
+ * vma_anon_address - Find the virtual address an anonymous page range is mapped
+ * at.
* @vma: The vma which maps this object.
* @pgoff_anon: The anonymous page index belonging to the folio.
* @nr_pages: The number of pages to consider.
@@ -1093,7 +1079,10 @@ static inline unsigned long vma_address_end(struct page_vma_mapped_walk *pvmw)
if (pvmw->nr_pages == 1)
return pvmw->address + PAGE_SIZE;
- pgoff_vma_start = vma_start_pgoff(vma);
+ if (pvmw->pgoff_is_anon)
+ pgoff_vma_start = vma_start_anon_pgoff(vma);
+ else
+ pgoff_vma_start = vma_start_pgoff(vma);
address = vma->vm_start +
((pgoff_end - pgoff_vma_start) << PAGE_SHIFT);
diff --git a/mm/interval_tree.c b/mm/interval_tree.c
index 3ae9e106d3af..7bbbf15cfbf0 100644
--- a/mm/interval_tree.c
+++ b/mm/interval_tree.c
@@ -83,12 +83,12 @@ mapping_rmap_tree_iter_next(struct vm_area_struct *vma,
static pgoff_t avc_start_pgoff(struct anon_vma_chain *avc)
{
- return vma_start_pgoff(avc->vma);
+ return vma_start_anon_pgoff(avc->vma);
}
static pgoff_t avc_last_pgoff(struct anon_vma_chain *avc)
{
- return vma_last_pgoff(avc->vma);
+ return vma_last_anon_pgoff(avc->vma);
}
INTERVAL_TREE_DEFINE(struct anon_vma_chain, rb, pgoff_t, rb_subtree_last,
diff --git a/mm/ksm.c b/mm/ksm.c
index 47006f494fcb..99738a213243 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -1625,7 +1625,7 @@ static int try_to_merge_with_ksm_page(struct ksm_rmap_item *rmap_item,
* stable_tree, break_cow() will clean it up.
*/
rmap_item->anon_vma = vma->anon_vma;
- rmap_item->linear_page_index = linear_page_index(vma, rmap_item->address);
+ rmap_item->linear_page_index = linear_anon_page_index(vma, rmap_item->address);
get_anon_vma(vma->anon_vma);
out:
mmap_read_unlock(mm);
@@ -3152,7 +3152,7 @@ struct folio *ksm_might_need_to_copy(struct folio *folio,
return folio; /* no need to copy it */
} else if (!anon_vma) {
return folio; /* no need to copy it */
- } else if (folio->index == linear_page_index(vma, addr) &&
+ } else if (folio->index == linear_anon_page_index(vma, addr) &&
anon_vma->root == vma->anon_vma->root) {
return folio; /* still no need to copy it */
}
@@ -3222,7 +3222,7 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc)
/*
* Currently, KSM folios are always small folios, so it's
* sufficient to search for a single page. We can simply use
- * the linear_page_index of the original de-duplicate
+ * the linear_anon_page_index of the original de-duplicate
* anonymous page that we remembered in the rmap_item while
* de-duplicating. Note that mremap() always de-duplicates KSM
* folios: so if there was mremap() in our parent or our child,
diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c
index 081e483cc7bf..4e964545e5e8 100644
--- a/mm/page_vma_mapped.c
+++ b/mm/page_vma_mapped.c
@@ -365,7 +365,7 @@ unsigned long page_mapped_in_vma(const struct page *page,
};
if (folio_test_anon(folio))
- pvmw.address = vma_address(vma, pgoff, 1);
+ pvmw.address = vma_anon_address(vma, pgoff, 1);
else
pvmw.address = vma_filebacked_address(vma, pgoff, 1);
if (pvmw.address == -EFAULT)
diff --git a/mm/rmap.c b/mm/rmap.c
index ab3f879454de..2a9ae25ae72d 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -866,7 +866,7 @@ unsigned long page_address_in_vma(const struct folio *folio,
vma->anon_vma->root != anon_vma->root)
return -EFAULT;
/* KSM folios don't reach here because of the !anon_vma check */
- return vma_address(vma, page_pgoff(folio, page), 1);
+ return vma_anon_address(vma, page_pgoff(folio, page), 1);
} else if (!vma->vm_file) {
return -EFAULT;
} else if (vma->vm_file->f_mapping != folio->mapping) {
@@ -1485,7 +1485,7 @@ static void __folio_set_anon(struct folio *folio, struct vm_area_struct *vma,
*/
anon_vma = (void *) anon_vma + FOLIO_MAPPING_ANON;
WRITE_ONCE(folio->mapping, (struct address_space *) anon_vma);
- folio->index = linear_page_index(vma, address);
+ folio->index = linear_anon_page_index(vma, address);
}
/**
@@ -1512,8 +1512,8 @@ static void __page_check_anon_rmap(const struct folio *folio,
*/
VM_BUG_ON_FOLIO(folio_anon_vma(folio)->root != vma->anon_vma->root,
folio);
- VM_BUG_ON_PAGE(page_pgoff(folio, page) != linear_page_index(vma, address),
- page);
+ VM_BUG_ON_PAGE(page_pgoff(folio, page) !=
+ linear_anon_page_index(vma, address), page);
}
static __always_inline void __folio_add_anon_rmap(struct folio *folio,
@@ -3040,10 +3040,10 @@ static void rmap_walk_anon(struct folio *folio,
pgoff_end = pgoff_start + folio_nr_pages(folio) - 1;
anon_rmap_tree_foreach(avc, anon_vma, pgoff_start, pgoff_end) {
struct vm_area_struct *vma = avc->vma;
- unsigned long address = vma_address(vma, pgoff_start,
+ const unsigned long address = vma_anon_address(vma, pgoff_start,
folio_nr_pages(folio));
- VM_BUG_ON_VMA(address == -EFAULT, vma);
+ VM_WARN_ON_ONCE_VMA(address == -EFAULT, vma);
cond_resched();
if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg))
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 8fd24c8b428e..24a4d92ffa3c 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -1352,7 +1352,7 @@ static long move_present_ptes(struct mm_struct *mm,
}
folio_move_anon_rmap(src_folio, dst_vma);
- src_folio->index = linear_page_index(dst_vma, dst_addr);
+ src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
orig_dst_pte = folio_mk_pte(src_folio, dst_vma->vm_page_prot);
/* Set soft dirty bit so userspace can notice the pte was moved */
@@ -1428,7 +1428,7 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma,
*/
if (src_folio) {
folio_move_anon_rmap(src_folio, dst_vma);
- src_folio->index = linear_page_index(dst_vma, dst_addr);
+ src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
} else {
/*
* Check if the swap entry is cached after acquiring the src_pte
diff --git a/mm/vma.c b/mm/vma.c
index b55015952f0d..117ca94fc907 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -204,6 +204,25 @@ static void init_multi_vma_prep(struct vma_prepare *vp,
vp->skip_vma_uprobe = true;
}
+/*
+ * Does this merge require that adjacent VMAs must have adjacent anonymous page
+ * offsets in addition to having adjacent vma->vm_pgoff?
+ *
+ * This is only required for MAP_PRIVATE-file backed mappings as the page offset
+ * for pure anonymous VMAs is equal to the anonymous page offset.
+ *
+ * Read-only shared mappings (with VMA_SHARED_BIT cleared) are always unfaulted
+ * so automatically have correct anonymous page offset (as it is always updated
+ * on remap).
+ *
+ * 'Special' mappings in the sense of VDSO, VVAR etc. have !file but would in
+ * any case not be candidates for merge nor be mergeable.
+ */
+static bool needs_adjacent_anon_pgoff(const struct vma_merge_struct *vmg)
+{
+ return vmg->file && vma_flags_is_cow_mapping(&vmg->vma_flags);
+}
+
/*
* Return true if we can merge this (vma_flags,anon_vma,file,vm_pgoff)
* in front of (at a lower virtual address and file offset than) the vma.
@@ -225,6 +244,9 @@ static bool can_vma_merge_before(struct vma_merge_struct *vmg)
return false;
if (vmg_end_pgoff(vmg) != vma_start_pgoff(vmg->next))
return false;
+ if (needs_adjacent_anon_pgoff(vmg) &&
+ vmg_end_anon_pgoff(vmg) != vma_start_anon_pgoff(vmg->next))
+ return false;
return true;
}
@@ -245,6 +267,9 @@ static bool can_vma_merge_after(struct vma_merge_struct *vmg)
return false;
if (vma_end_pgoff(vmg->prev) != vmg_start_pgoff(vmg))
return false;
+ if (needs_adjacent_anon_pgoff(vmg) &&
+ vma_end_anon_pgoff(vmg->prev) != vmg_start_anon_pgoff(vmg))
+ return false;
return true;
}
@@ -2048,7 +2073,12 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct *
if (!vma_flags_empty(&diff))
return false;
/* Page offset must align. */
- return vma_end_pgoff(a) == vma_start_pgoff(b);
+ if (vma_end_pgoff(a) != vma_start_pgoff(b))
+ return false;
+ /* Only reached from anon path, so either MAP_PRIVATE file or anon. */
+ if (vma_end_anon_pgoff(a) != vma_start_anon_pgoff(b))
+ return false;
+ return true;
}
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 15/20] tools/testing/vma: expand VMA merge tests to assert anon pgoff
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (13 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 14/20] mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 16/20] tools/testing/selftests/mm: test anonymous page offset merge behaviour Lorenzo Stoakes (ARM)
` (5 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Now we have introduced the VMA anonymous page offset attribute and update
it when VMAs are manipulated, update VMA merge tests to assert that the
anonymous page offset is as expected.
Also update instances where we could use vma_start_pgoff() to do so.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
tools/testing/vma/tests/merge.c | 45 ++++++++++++++++++++++++++++++++---------
1 file changed, 36 insertions(+), 9 deletions(-)
diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c
index 48418b82b01d..acaab282939c 100644
--- a/tools/testing/vma/tests/merge.c
+++ b/tools/testing/vma/tests/merge.c
@@ -121,6 +121,7 @@ static bool test_simple_merge(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_FLAGS_SAME_MASK(&vma->flags, vma_flags);
detach_free_vma(vma);
@@ -153,6 +154,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
ASSERT_EQ(vma_start_pgoff(vma), 1);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 1);
/*
* Now walk through the three split VMAs and make sure they are as
@@ -165,6 +167,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -174,6 +177,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
ASSERT_EQ(vma_start_pgoff(vma), 1);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 1);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -183,6 +187,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 2);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -212,6 +217,7 @@ static bool test_simple_expand(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -234,6 +240,7 @@ static bool test_simple_shrink(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -346,6 +353,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -367,6 +375,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 6);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 6);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -387,6 +396,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -407,6 +417,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0xa000);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0xa);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0xa);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -426,6 +437,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -446,6 +458,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
detach_free_vma(vma);
@@ -642,7 +655,8 @@ static bool test_vma_merge_with_close(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x5000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(cleanup_mm(&mm, &vmi), 2);
@@ -753,7 +767,8 @@ static bool test_vma_merge_with_close(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x5000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(cleanup_mm(&mm, &vmi), 2);
@@ -808,6 +823,7 @@ static bool test_vma_merge_new_with_close(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->vm_ops, &vm_ops);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -863,11 +879,13 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_next->vm_start, 0x3000);
ASSERT_EQ(vma_next->vm_end, 0x9000);
- ASSERT_EQ(vma_next->vm_pgoff, 3);
+ ASSERT_EQ(vma_start_pgoff(vma_next), 3);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_next), 3);
ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 2);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_TRUE(vma_write_started(vma_next));
ASSERT_EQ(mm.map_count, 2);
@@ -897,7 +915,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_next->vm_start, 0x2000);
ASSERT_EQ(vma_next->vm_end, 0x9000);
- ASSERT_EQ(vma_next->vm_pgoff, 2);
+ ASSERT_EQ(vma_start_pgoff(vma_next), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_next), 2);
ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_next));
ASSERT_EQ(mm.map_count, 1);
@@ -929,11 +948,13 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x6000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x7000);
ASSERT_EQ(vma_start_pgoff(vma), 6);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 6);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -964,7 +985,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_EQ(mm.map_count, 1);
@@ -996,7 +1018,8 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x9000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_EQ(mm.map_count, 1);
@@ -1126,7 +1149,8 @@ static bool test_anon_vma_non_mergeable(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_FALSE(vma_write_started(vma_next));
@@ -1157,7 +1181,8 @@ static bool test_anon_vma_non_mergeable(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_FALSE(vma_write_started(vma_next));
@@ -1419,6 +1444,7 @@ static bool test_merge_extend(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x4000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -1459,6 +1485,7 @@ static bool test_expand_only_mode(void)
ASSERT_EQ(vma->vm_start, 0x3000);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 3);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 3);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(vma_iter_addr(&vmi), 0x3000);
vma_assert_attached(vma);
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 16/20] tools/testing/selftests/mm: test anonymous page offset merge behaviour
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (14 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 15/20] tools/testing/vma: expand VMA merge tests to assert anon pgoff Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 17/20] mm/vma: only permit MAP_PRIVATE /dev/zero to be mapped anonymous Lorenzo Stoakes (ARM)
` (4 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
While maintaining anonymous page offsets for VMAs has no impact for most
merge cases, it does impact MAP_PRIVATE-mapped file-backed mappings which
happen to have matching page offset but not matching anonymous page offset.
Assert this behaviour by attempting to map an unfaulted MAP_PRIVATE-memfd
region with a faulted one with compatible file page offsets but
incompatible anonymous page offsets.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
tools/testing/selftests/mm/merge.c | 57 ++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/tools/testing/selftests/mm/merge.c b/tools/testing/selftests/mm/merge.c
index 519e5ac02db7..cb7cdb6b7ada 100644
--- a/tools/testing/selftests/mm/merge.c
+++ b/tools/testing/selftests/mm/merge.c
@@ -1305,6 +1305,63 @@ TEST_F(merge, merge_vmas_with_mseal)
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 2 * page_size);
}
+TEST_F(merge, anon_and_page_offset_mismatch_memfd)
+{
+ struct procmap_fd *procmap = &self->procmap;
+ unsigned int page_size = self->page_size;
+ char *carveout = self->carveout;
+ char *ptr, *ptr2;
+ int fd;
+
+ /* Create a 10 page memfd descriptor. */
+ fd = memfd_create("anon_page_offset_test", MFD_CLOEXEC);
+ ASSERT_NE(fd, -1);
+ ASSERT_EQ(ftruncate(fd, 10 * page_size), 0);
+
+ /* Map a region using the memfd at page offset 0. */
+ ptr = mmap(carveout, 5 * page_size, PROT_READ | PROT_WRITE,
+ MAP_FIXED | MAP_PRIVATE, fd, 0);
+ ASSERT_NE(ptr, MAP_FAILED);
+
+ /*
+ * Map another separately, and fault in, at page offset 5:
+ *
+ * |-----------| |---------|
+ * | unfaulted | | faulted |
+ * |-----------| |---------|
+ */
+ ptr2 = mmap(&carveout[10 * page_size], 5 * page_size,
+ PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE,
+ fd, 5 * page_size);
+ ASSERT_NE(ptr2, MAP_FAILED);
+ ptr2[0] = 'x';
+
+ /*
+ * Now move it in place:
+ *
+ * |----------|
+ * | |
+ * v |
+ * |-----------| |---------|
+ * | unfaulted | | faulted |
+ * |-----------| |---------|
+ *
+ * Because the anonymous page offset of the faulted region is now
+ * &carveout[10 * page_size], despite the two regions being mergeable
+ * due to file page offset, they are NOT mergeable due to anonymous
+ * page offset.
+ */
+ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size,
+ MREMAP_MAYMOVE | MREMAP_FIXED,
+ &carveout[5 * page_size]);
+ ASSERT_NE(ptr2, MAP_FAILED);
+
+ /* Assert that they did not merge. */
+ 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 + 5 * page_size);
+}
+
TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_prev)
{
struct procmap_fd *procmap = &self->procmap;
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 17/20] mm/vma: only permit MAP_PRIVATE /dev/zero to be mapped anonymous
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (15 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 16/20] tools/testing/selftests/mm: test anonymous page offset merge behaviour Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 18/20] mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous Lorenzo Stoakes (ARM)
` (3 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
In order to use mmap_prepare() with MAP_PRIVATE mappings of /dev/zero
without the success_hook hack we explicitly permitted mmap_prepare handlers
to set NULL vm_ops.
However this is dangerous and we really only want to allow this for
MAP_PRIVATE-mapped /dev/zero.
Make it possible to explicitly identify /dev/zero by setting a global
DEVZERO_MINOR device minor number then explicitly check for this in mmap
code for a MAP_PRIVATE mapping and only set the VMA anonymous if we have
positively identified it.
Then remove all ability for mmap_prepare or mmap hooks to set a VMA
anonymous and update mmap_zero_prepare() to leave it to the core mmap code
to mark the VMA anonymous.
Note that this disallows nested MAP_PRIVATE-mappings of /dev/zero
regions. Doing this would be broken in any case.
We therefore do not need to update the mmap_prepare() compatibility layer
to reflect these changes, as the mmap hook check suffices to disallow this
behaviour.
Now we're setting vma->vm_ops to NULL for an mmap_prepare-initialised
MAP_PRIVATE-/dev/zero mapping, we have to avoid a subtle issue when
updating user-defined fields via set_vma_user_defined_fields().
The default for vma->vm_ops for all mmap_prepare-initialised mappings is
vma_dummy_vm_ops, so map->vm_ops will be set to this and setting
vma->vm_ops to this will render the VMA mistakenly non-anon.
In general, we should never be setting user-defined fields for an anonymous
VMA, so explicitly check for this to avoid doing so for the one case where
a mapping can be both mmap_prepare and anonymous.
In the case of legacy ->mmap hooks some drivers may set vma->vm_ops NULL
believing this is the equivalent of setting no VMA operations. Therefore
update mmap_file() to correct this by setting dummy VMA operations if this
occurs.
An example of this is drm_gem_shmem_mmap() which deliberately clears
vma->vm_ops before handing the VMA to dma-buf. Cases such as this will be
updated when they are converted to mmap_prepare.
Also, in order to avoid a single commit bisection hazard, add a temporary
workaround to set the VMA anonymous only after vma->vm_file is assigned in
__mmap_new_file_vma().
This is because vma_set_range() calls vma_set_pgoff() and
assert_sane_pgoff() in turn, prior to the vma->vm_file being assigned. If
we set the VMA anonymous early then this assert will fail.
This is removed in the subsequent commit.
Also update the VMA userland tests to reflect the change.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
drivers/char/mem.c | 8 ++-----
include/linux/mm.h | 3 +++
mm/internal.h | 17 ++++++++-------
mm/vma.c | 46 ++++++++++++++++++++++++++++++++++-------
mm/vma_internal.h | 1 +
tools/testing/vma/include/dup.h | 36 ++++++++++++++++++++++++++++++++
6 files changed, 90 insertions(+), 21 deletions(-)
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
index 63253d1de5d7..dcfd896b733d 100644
--- a/drivers/char/mem.c
+++ b/drivers/char/mem.c
@@ -506,11 +506,7 @@ static int mmap_zero_prepare(struct vm_area_desc *desc)
if (vma_desc_test(desc, VMA_SHARED_BIT))
return shmem_zero_setup_desc(desc);
- /*
- * 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);
+ /* MAP_PRIVATE semantics are taken care for us by core mm. */
return 0;
}
@@ -698,7 +694,7 @@ static const struct memdev {
#ifdef CONFIG_DEVPORT
[4] = { "port", &port_fops, 0, 0 },
#endif
- [5] = { "zero", &zero_fops, FMODE_NOWAIT, 0666 },
+ [DEVZERO_MINOR] = { "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 },
diff --git a/include/linux/mm.h b/include/linux/mm.h
index da36a6cc907c..b54bdbbf3906 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -740,6 +740,9 @@ static inline bool fault_flag_allow_retry_first(enum fault_flag flags)
{ 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
diff --git a/mm/internal.h b/mm/internal.h
index 5892b7453b54..1048d8e053fc 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -240,15 +240,18 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma)
{
int err = vfs_mmap(file, vma);
- if (likely(!err))
- return 0;
-
/*
- * 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.
+ * 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.
*/
- vma->vm_ops = &vma_dummy_vm_ops;
+ if (unlikely(err || !vma->vm_ops))
+ vma->vm_ops = &vma_dummy_vm_ops;
return err;
}
diff --git a/mm/vma.c b/mm/vma.c
index 117ca94fc907..4271b5ff8137 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -2621,6 +2621,32 @@ static int __mmap_new_file_vma(struct mmap_state *map,
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 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)
+{
+ if (!map_is_private(map))
+ return false;
+
+ return !map->file || map_is_dev_zero(map);
+}
+
/*
* __mmap_new_vma() - Allocate a new VMA for the region, as merging was not
* possible.
@@ -2634,8 +2660,7 @@ static int __mmap_new_file_vma(struct mmap_state *map,
static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
struct mmap_action *action)
{
- const bool is_anon = !map->file &&
- !vma_flags_test(&map->vma_flags, VMA_SHARED_BIT);
+ const bool is_anon = map_is_anon(map);
struct vma_iterator *vmi = map->vmi;
int error = 0;
struct vm_area_struct *vma;
@@ -2651,7 +2676,7 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
vma_iter_config(vmi, map->addr, map->end);
- if (is_anon)
+ if (is_anon && !map->file)
vma_set_anonymous(vma);
vma_set_range(vma, map->addr, map->end, map->pgoff, map->anon_pgoff);
@@ -2669,6 +2694,10 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
else if (!is_anon)
error = shmem_zero_setup(vma);
+ /* Temporary MAP_PRIVATE-/dev/zero workaround. */
+ if (is_anon && map->file)
+ vma_set_anonymous(vma);
+
if (error)
goto free_iter_vma;
@@ -2777,6 +2806,10 @@ static int call_mmap_prepare(struct mmap_state *map,
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;
@@ -2799,10 +2832,7 @@ static int call_mmap_prepare(struct mmap_state *map,
static void set_vma_user_defined_fields(struct vm_area_struct *vma,
struct mmap_state *map)
{
- if (map->vm_ops)
- vma->vm_ops = map->vm_ops;
- else /* Only /dev/zero should do this. */
- vma_set_anonymous(vma);
+ vma->vm_ops = map->vm_ops;
vma->vm_private_data = map->vm_private_data;
}
@@ -2882,7 +2912,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,
allocated_new = true;
}
- if (have_mmap_prepare)
+ if (have_mmap_prepare && !map_is_anon(&map))
set_vma_user_defined_fields(vma, &map);
__mmap_complete(&map, vma);
diff --git a/mm/vma_internal.h b/mm/vma_internal.h
index 4d300e7bbaf4..385c0ab13777 100644
--- a/mm/vma_internal.h
+++ b/mm/vma_internal.h
@@ -23,6 +23,7 @@
#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>
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 4c58487b764e..7490e33f9316 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -15,6 +15,20 @@ 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 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.
@@ -23,6 +37,8 @@ enum {
TASK_COMM_LEN = 16,
};
+typedef unsigned short umode_t;
+
/* PARTIALLY implemented types. */
struct mm_struct {
struct maple_tree mm_mt;
@@ -45,6 +61,10 @@ 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 *);
@@ -52,6 +72,7 @@ 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;
@@ -1644,3 +1665,18 @@ static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
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);
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 18/20] mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (16 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 17/20] mm/vma: only permit MAP_PRIVATE /dev/zero to be mapped anonymous Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 19/20] tools/testing/vma: add test to assert MAP_PRIVATE-/dev/zero is anon Lorenzo Stoakes (ARM)
` (2 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
When mapping /dev/zero with MAP_PRIVATE, one ends up with strange VMAs
originating from Linux's distant past.
These have vma->vm_file set but NULL vma->vm_ops, meaning they satisfy
vma_is_anonymous() but otherwise resemble a file-backed VMA.
The introduction of anonymous page offsets and their subsequent use as
indexes for MAP_PRIVATE-file-backed mappings mean the rmap does the right
thing with these but we are left with inconsistencies.
The vma_start_pgoff(vma) == vma_start_anon_pgoff(vma) invariant is true for
all other anonymous VMAs, but not these.
These VMAs are also observable as files in /proc/<pid>/[maps, smaps,
map_files] but otherwise behave like anonymous mappings.
Therefore let's make these VMAs actually anonymous at mapping time which
will activate the anonymous code path for mappings.
This means we no longer have to account for this discrepancy anywhere and
no longer have to think about these at all.
This is user-observable, as MAP_PRIVATE-/dev/zero will no longer appear in
procfs as a file-backed mapping, but the impact of this change should be
low as likely nobody is relying upon this.
However in any case, in using MAP_PRIVATE-/dev/zero they are explicitly
asking anonymous memory, so no longer seeing these as file mappings is in
fact correct.
A previous commit gave us map_is_dev_zero() to positively identify these
mappings, so we expressly only do so for these alone.
Update assert_sane_pgoff(), the comment for vma_start_pgoff() and
linear_anon_page_index() to reflect the change.
We make this change in call_mmap_prepare() alone as /dev/zero has been
converted to an mmap_prepare hook and we do not permit nested MAP_PRIVATE
mapping of /dev/zero.
We also remove the now defunct vma_desc_set_anonymous() and eliminate the
temporary bisection hazard fix from the previous commit.
Also update the VMA userland tests to reflect the change.
Finally, update the procfs self tests proc-self-map-files-001 and
proc-self-map-files-002 which both intend to map an arbitrary file
MAP_PRIVATE then assert procfs state, but happen to choose /dev/zero.
Fix them by updating these to /proc/self/exe which is guaranteed to be
present if procfs is mounted.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 10 ++-------
include/linux/pagemap.h | 3 +--
mm/vma.c | 26 ++++++++++++++--------
mm/vma.h | 3 ---
.../selftests/proc/proc-self-map-files-001.c | 2 +-
.../selftests/proc/proc-self-map-files-002.c | 2 +-
tools/testing/vma/include/dup.h | 3 +--
7 files changed, 23 insertions(+), 26 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index b54bdbbf3906..0d21112a8b63 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1554,11 +1554,6 @@ static inline void vma_set_anonymous(struct vm_area_struct *vma)
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;
@@ -4355,9 +4350,8 @@ static inline unsigned long vma_pages(const struct vm_area_struct *vma)
* 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, 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.
+ * 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.
*
* Returns: The page offset of the start of @vma.
*/
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 0adfa6605653..939f3a5e973f 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -1128,8 +1128,7 @@ static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
const pgoff_t pgoff = __linear_anon_page_index(vma, address);
VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
- /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
- if (vma_is_anonymous(vma) && !vma->vm_file)
+ if (vma_is_anonymous(vma))
VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
return pgoff;
diff --git a/mm/vma.c b/mm/vma.c
index 4271b5ff8137..6db84830e88f 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -2634,6 +2634,13 @@ static bool map_is_dev_zero(const struct mmap_state *map)
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);
@@ -2641,10 +2648,7 @@ static bool map_is_private(const struct mmap_state *map)
static bool map_is_anon(const struct mmap_state *map)
{
- if (!map_is_private(map))
- return false;
-
- return !map->file || map_is_dev_zero(map);
+ return map_is_private(map) && !map->file;
}
/*
@@ -2676,7 +2680,7 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
vma_iter_config(vmi, map->addr, map->end);
- if (is_anon && !map->file)
+ if (is_anon)
vma_set_anonymous(vma);
vma_set_range(vma, map->addr, map->end, map->pgoff, map->anon_pgoff);
@@ -2694,10 +2698,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
else if (!is_anon)
error = shmem_zero_setup(vma);
- /* Temporary MAP_PRIVATE-/dev/zero workaround. */
- if (is_anon && map->file)
- vma_set_anonymous(vma);
-
if (error)
goto free_iter_vma;
@@ -2826,6 +2826,14 @@ static int call_mmap_prepare(struct mmap_state *map,
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;
}
diff --git a/mm/vma.h b/mm/vma.h
index 024fabe63560..e97bd2dfa786 100644
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -267,9 +267,6 @@ static inline void assert_sane_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
*/
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;
diff --git a/tools/testing/selftests/proc/proc-self-map-files-001.c b/tools/testing/selftests/proc/proc-self-map-files-001.c
index 4209c64283d6..bbca9f9e2743 100644
--- a/tools/testing/selftests/proc/proc-self-map-files-001.c
+++ b/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("/dev/zero", O_RDONLY);
+ fd = open("/proc/self/exe", O_RDONLY);
if (fd == -1)
return 1;
diff --git a/tools/testing/selftests/proc/proc-self-map-files-002.c b/tools/testing/selftests/proc/proc-self-map-files-002.c
index e6aa00a183bc..5786cdffbbf6 100644
--- a/tools/testing/selftests/proc/proc-self-map-files-002.c
+++ b/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("/dev/zero", O_RDONLY);
+ fd = open("/proc/self/exe", O_RDONLY);
if (fd == -1)
return 1;
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 7490e33f9316..aee36707abdd 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -1659,8 +1659,7 @@ static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
const pgoff_t pgoff = __linear_anon_page_index(vma, address);
VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
- /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
- if (vma_is_anonymous(vma) && !vma->vm_file)
+ if (vma_is_anonymous(vma))
VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
return pgoff;
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 19/20] tools/testing/vma: add test to assert MAP_PRIVATE-/dev/zero is anon
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (17 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 18/20] mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 20/20] tools/testing/selftests/mm: add MAP_PRIVATE-/dev/zero merge tests Lorenzo Stoakes (ARM)
2026-08-06 23:27 ` [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Andrew Morton
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel
Now we've made MAP_PRIVATE-mapped /dev/zero mappings truly anonymous, add a
VMA userland test to assert that this is the case and everything is as
we would expect for an anonymous mapping.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
tools/testing/vma/include/dup.h | 1 +
tools/testing/vma/tests/mmap.c | 50 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index aee36707abdd..41203b1c2323 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -20,6 +20,7 @@ struct task_struct *get_current(void);
#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
diff --git a/tools/testing/vma/tests/mmap.c b/tools/testing/vma/tests/mmap.c
index c85bc000d1cb..ebe01362e530 100644
--- a/tools/testing/vma/tests/mmap.c
+++ b/tools/testing/vma/tests/mmap.c
@@ -45,7 +45,57 @@ 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);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v4 20/20] tools/testing/selftests/mm: add MAP_PRIVATE-/dev/zero merge tests
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (18 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 19/20] tools/testing/vma: add test to assert MAP_PRIVATE-/dev/zero is anon Lorenzo Stoakes (ARM)
@ 2026-08-06 20:21 ` Lorenzo Stoakes (ARM)
2026-08-06 23:27 ` [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Andrew Morton
20 siblings, 0 replies; 22+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-06 20:21 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara,
Miaohe Lin, Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang,
Kees Cook, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Usama Arif, Matthew Brost, Joshua Hahn,
Rakie Kim, Byungchul Park, Gregory Price, Ying Huang,
Alistair Popple, Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park
Cc: Lorenzo Stoakes (ARM), linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel, syzbot
Assert that MAP_PRIVATE-mapped /dev/zero mappings behave like they are
anonymous.
We test both unfaulted and faulted/unfaulted merges - each with the regions
having page offset of 0, which would not merge if the mappings were treated
as if they were file-backed.
With the recent change that makes them behave as pure anonymous mappings,
the merges should succeed as their page offsets are equal to their
anonymous page offsets.
Tested-by: syzbot@syzkaller.appspotmail.com
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
tools/testing/selftests/mm/merge.c | 104 +++++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/tools/testing/selftests/mm/merge.c b/tools/testing/selftests/mm/merge.c
index cb7cdb6b7ada..19c9bf9eb9ce 100644
--- a/tools/testing/selftests/mm/merge.c
+++ b/tools/testing/selftests/mm/merge.c
@@ -1362,6 +1362,110 @@ TEST_F(merge, anon_and_page_offset_mismatch_memfd)
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;
--
2.55.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
` (19 preceding siblings ...)
2026-08-06 20:21 ` [PATCH v4 20/20] tools/testing/selftests/mm: add MAP_PRIVATE-/dev/zero merge tests Lorenzo Stoakes (ARM)
@ 2026-08-06 23:27 ` Andrew Morton
20 siblings, 0 replies; 22+ messages in thread
From: Andrew Morton @ 2026-08-06 23:27 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, Matthew Wilcox (Oracle), Jan Kara, Miaohe Lin,
Naoya Horiguchi, Rik van Riel, Harry Yoo, Lance Yang, Kees Cook,
Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain,
Barry Song, Usama Arif, Matthew Brost, Joshua Hahn, Rakie Kim,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Xu Xin, Chengming Zhou, Arnd Bergmann,
Greg Kroah-Hartman, Christian Borntraeger, Janosch Frank,
Claudio Imbrenda, Alexander Gordeev, Gerald Schaefer,
Heiko Carstens, Vasily Gorbik, Sven Schnelle, Alex Deucher,
Christian König, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Boris Brezillon, Steven Price, Liviu Dudau, Huang Rui,
Matthew Auld, Thomas Hellström, Rodrigo Vivi,
Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Jason Gunthorpe, John Hubbard, Muchun Song,
Oscar Salvador, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Youngjun Park, linux-mm, linux-kernel, linux-fsdevel,
linux-kselftest, kvm, linux-s390, amd-gfx, dri-devel, intel-xe,
linux-perf-users, linux-trace-kernel, syzbot
On Thu, 06 Aug 2026 21:21:25 +0100 "Lorenzo Stoakes (ARM)" <ljs@kernel.org> wrote:
> In memory management we've managed to manufacture a great deal of confusion
> around the concept of anonymous memory. We have:
>
> 1. 'Pure anon' memory - anonymous VMAs whose folios are anonymous and
> swap-backed (thus for reclaim purposes, treated as anonymous). These are
> simple enough.
>
> 2. shmem - file-backed VMAs, file-backed folios (from rmap perspective) so
> present in the page cache and mapped by an address_space object, but
> whose folios are also swap-backed (thus treated as anonymous for reclaim
> purposes).
>
> 3. MAP_PRIVATE-mapped /dev/zero - a strange beast whose VMAs have
> vma->vm_file set, but which clears vma->vm_ops to satisfy
> vma_is_anonymous(), resulting in VMAs that were mmap()'d referencing a
> file, but are in every other sense anonymous, including the folios.
>
> 4. Other MAP_PRIVATE-file backed mappings - These possess file-backed VMAs
> and have file-backed folios until CoW'd, at which point those CoW'd
> folios are anonymous.
>
> This series fixes issues 3 and 4.
Thanks, I've updated mm.git's mm-unstable branch to this version.
It's about 450 patches away from head-of-queue, so it will be a
second-week-of-merge-window thing. Three weeks to go.
> v4:
> - Updated tags (thanks everyone!)
> - Adjusted some prose as per David.
> - Adjusted whitespace to 2 tabs in 2/15 as per David.
> - Improved linear_page_index(), __linear_anon_page_index() to be more
> succinct in 2/15 as per David and updated commit message to reflect it.
> - Added commit to provide vma_[flags_]is_cow_mapping() - nearly all callers
> are calling is_cow_mapping() as is_cow_mapping(vma->vm_flags) so just
> provide a helper to do this for them.
> - In the same commit update all is_cow_mapping() callers and remove the
> now-unused function.
> - Change linear_anon_page_index() to assert on !CoW mapping using
> vma_is_cow_mapping() in 2/15 as per David.
> - Updated 4/15 to output index differently depending on whether the VMA is
> a CoW mapping or not or whether the file index differs from the anon
> index as per Gregory.
> - Updated 6/15 to change the 'update page offset' logic in copy_vma() to
> not be gated on CoW or VMA_SHARED_BIT as discussed with David.
> - Updated 6/15 to improve the 'faulted in anon vma' assert stuff. It was
> very unclear so rename the variable sensibly and update the comment.
> - Added a separate commit to fix the mess that is the faulted_in_anon_vma
> and the VM_WARN_ON_ONCE_VMA() assert in copy_vma() - make it actually
> only update the vmap in cases where the VMA was replaced (backwards
> remap), update the checks to reflect this in a way that's actually
> understandable and improve the comment.
> - Updated needs_adjacent_anon_gpoff() in 9/15 to use
> vma_flags_is_cow_mapping() as per David. Updated the comment to reflect
> it.
> - Broke out changes to vma_address_end() into a separate patch as per David.
> - Eliminated pgoff in vma_address_end() as it adds confusion - just use
> pgoff_end, which was what pgoff used to be (confusingly).
> - Placed more variables in vma_address_end() as constants at the start of
> the function.
> - Dropped KSM comment in 9/15 as per David.
> - Dropped linear_folio_page_index() patch altogether as per
> David.
> - Realised all off uffd is only anon so use linear_anon_page_index()
> throughout there and also the huge memory case for the same reason.
> - Added a patch to make remove_migration_pmd() accept a folio instead of a
> page.
> - Added a patch to calculate large folio index using PFN as suggested by
> David, eliminating the need for linear page index lookup at all.
> - Added a patch to add self merge VMA userland tests.
Well this grew up. Here's how v4 altered mm.git:
arch/s390/mm/gmap_helpers.c | 2
drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 4
drivers/gpu/drm/drm_gem_shmem_helper.c | 2
drivers/gpu/drm/panthor/panthor_gem.c | 2
drivers/gpu/drm/ttm/ttm_bo_vm.c | 2
drivers/gpu/drm/xe/xe_device.c | 2
fs/proc/task_mmu.c | 2
include/linux/mm.h | 61 ++---
include/linux/mm_types.h | 12 -
include/linux/pagemap.h | 61 +----
include/linux/rmap.h | 6
include/linux/swapops.h | 6
kernel/events/uprobes.c | 2
mm/gup.c | 2
mm/huge_memory.c | 27 +-
mm/hugetlb.c | 2
mm/internal.h | 45 +--
mm/interval_tree.c | 4
mm/ksm.c | 7
mm/memory.c | 40 +--
mm/mempolicy.c | 2
mm/migrate.c | 23 -
mm/mremap.c | 6
mm/rmap.c | 8
mm/userfaultfd.c | 6
mm/vma.c | 118 +++++-----
mm/vma.h | 70 -----
mm/vma_init.c | 2
tools/testing/selftests/mm/merge.c | 10
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 | 41 ++-
tools/testing/vma/shared.c | 2
tools/testing/vma/tests/merge.c | 59 ++---
tools/testing/vma/tests/mmap.c | 6
tools/testing/vma/tests/vma.c | 46 +++
36 files changed, 337 insertions(+), 357 deletions(-)
--- a/arch/s390/mm/gmap_helpers.c~b
+++ a/arch/s390/mm/gmap_helpers.c
@@ -200,7 +200,7 @@ static int find_zeropage_pte_entry(pte_t
* currently only works in COW mappings, which is also where
* mm_forbids_zeropage() is checked.
*/
- if (!is_cow_mapping(walk->vma->vm_flags))
+ if (!vma_is_cow_mapping(walk->vma))
return -EFAULT;
*found_addr = addr;
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c~b
+++ a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c
@@ -377,9 +377,9 @@ static int amdgpu_gem_object_mmap(struct
/* Workaround for Thunk bug creating PROT_NONE,MAP_PRIVATE mappings
* for debugger access to invisible VRAM. Should have used MAP_SHARED
* instead. Clearing VM_MAYWRITE prevents the mapping from ever
- * becoming writable and makes is_cow_mapping(vm_flags) false.
+ * becoming writable and makes vma_is_cow_mapping(vma) false.
*/
- if (is_cow_mapping(vma->vm_flags) &&
+ if (vma_is_cow_mapping(vma) &&
!(vma->vm_flags & VM_ACCESS_FLAGS))
vm_flags_clear(vma, VM_MAYWRITE);
--- a/drivers/gpu/drm/drm_gem_shmem_helper.c~b
+++ a/drivers/gpu/drm/drm_gem_shmem_helper.c
@@ -753,7 +753,7 @@ int drm_gem_shmem_mmap(struct drm_gem_sh
return ret;
}
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
dma_resv_lock(shmem->base.resv, NULL);
--- a/drivers/gpu/drm/panthor/panthor_gem.c~b
+++ a/drivers/gpu/drm/panthor/panthor_gem.c
@@ -761,7 +761,7 @@ static int panthor_gem_mmap(struct drm_g
return ret;
}
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
if (!refcount_inc_not_zero(&bo->cmap.mmap_count)) {
--- a/drivers/gpu/drm/ttm/ttm_bo_vm.c~b
+++ a/drivers/gpu/drm/ttm/ttm_bo_vm.c
@@ -489,7 +489,7 @@ static const struct vm_operations_struct
int ttm_bo_mmap_obj(struct vm_area_struct *vma, struct ttm_buffer_object *bo)
{
/* Enforce no COW since would have really strange behavior with it. */
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
drm_gem_object_get(&bo->base);
--- a/drivers/gpu/drm/xe/xe_device.c~b
+++ a/drivers/gpu/drm/xe/xe_device.c
@@ -330,7 +330,7 @@ static int xe_pci_barrier_mmap(struct fi
if (vma->vm_end - vma->vm_start > SZ_4K)
return -EINVAL;
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return -EINVAL;
if (vma->vm_flags & (VM_READ | VM_EXEC))
--- a/fs/proc/task_mmu.c~b
+++ a/fs/proc/task_mmu.c
@@ -1693,7 +1693,7 @@ static inline bool pte_is_pinned(struct
if (!pte_write(pte))
return false;
- if (!is_cow_mapping(vma->vm_flags))
+ if (!vma_is_cow_mapping(vma))
return false;
if (likely(!mm_flags_test(MMF_HAS_PINNED, vma->vm_mm)))
return false;
--- a/include/linux/mm.h~b
+++ a/include/linux/mm.h
@@ -2269,17 +2269,20 @@ void unpin_user_pages(struct page **page
void unpin_user_folio(struct folio *folio, unsigned long npages);
void unpin_folios(struct folio **folios, unsigned long nfolios);
-static inline bool is_cow_mapping(vm_flags_t flags)
+static inline bool vma_flags_is_cow_mapping(const vma_flags_t *flags)
{
- return (flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
+ return vma_flags_test(flags, VMA_MAYWRITE_BIT) &&
+ !vma_flags_test(flags, VMA_SHARED_BIT);
}
-static inline bool vma_desc_is_cow_mapping(struct vm_area_desc *desc)
+static inline bool vma_is_cow_mapping(const struct vm_area_struct *vma)
{
- const vma_flags_t *flags = &desc->vma_flags;
+ return vma_flags_is_cow_mapping(&vma->flags);
+}
- return vma_flags_test(flags, VMA_MAYWRITE_BIT) &&
- !vma_flags_test(flags, VMA_SHARED_BIT);
+static inline bool vma_desc_is_cow_mapping(struct vm_area_desc *desc)
+{
+ return vma_flags_is_cow_mapping(&desc->vma_flags);
}
#ifndef CONFIG_MMU
@@ -4391,62 +4394,62 @@ static inline pgoff_t vma_last_pgoff(con
}
/**
- * vma_start_virt_pgoff() - Get the virtual page offset of the start of @vma
- * @vma: The VMA whose virtual page offset is required.
+ * vma_start_anon_pgoff() - Get the anonymous page offset of the start of @vma
+ * @vma: The VMA whose anonymous page offset is required.
*
* If unfaulted, then this is vma->vm_start >> PAGE_SHIFT, if faulted then the
- * virtual page offset at the time of first fault.
+ * anonymous page offset at the time of first fault.
*
* If the VMA is anonymous, this returns the same value as vma_start_pgoff().
*
* This value is used for tracking MAP_PRIVATE file-backed mappings by their
- * virtual page offset.
+ * anonymous page offset.
*
- * Returns: The virtual page offset of the start of @vma.
+ * Returns: The anonymous page offset of the start of @vma.
*/
-static inline pgoff_t vma_start_virt_pgoff(const struct vm_area_struct *vma)
+static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma)
{
pgoff_t pgoff = 0;
#ifdef CONFIG_64BIT
- pgoff += vma->__vm_virt_pgoff_hi;
+ pgoff += vma->__vm_anon_pgoff_hi;
pgoff <<= 32;
#endif
- pgoff += vma->__vm_virt_pgoff_lo;
+ pgoff += vma->__vm_anon_pgoff_lo;
return pgoff;
}
/**
- * vma_end_virt_pgoff() - Get the virtual page offset of the exclusive end of
+ * vma_end_anon_pgoff() - Get the anonymous page offset of the exclusive end of
* @vma.
- * @vma: The VMA whose end virtual page offset is required.
+ * @vma: The VMA whose end anonymous page offset is required.
*
- * This returns the virtual exclusive end page offset of @vma, which is useful
+ * This returns the anonymous exclusive end page offset of @vma, which is useful
* for expressing page offset ranges.
*
- * See the description of vma_start_virt_pgoff() for a description of VMA
- * virtual page offsets.
+ * See the description of vma_start_anon_pgoff() for a description of VMA
+ * anonymous page offsets.
*
- * Returns: The exclusive end virtual page offset of @vma.
+ * Returns: The exclusive end anonymous page offset of @vma.
*/
-static inline pgoff_t vma_end_virt_pgoff(const struct vm_area_struct *vma)
+static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma)
{
- return vma_start_virt_pgoff(vma) + vma_pages(vma);
+ return vma_start_anon_pgoff(vma) + vma_pages(vma);
}
/**
- * vma_last_virt_pgoff() - Get the virtual page offset of the last page in
+ * vma_last_anon_pgoff() - Get the anonymous page offset of the last page in
* @vma.
- * @vma: The VMA whose last virtual page offset is required.
+ * @vma: The VMA whose last anonymous page offset is required.
*
- * See the description of vma_start_virt_pgoff() for a description of VMA
- * virtual page offsets.
+ * See the description of vma_start_anon_pgoff() for a description of VMA
+ * anonymous page offsets.
*
- * Returns: The last virtual page offset of @vma.
+ * Returns: The last anonymous page offset of @vma.
*/
-static inline pgoff_t vma_last_virt_pgoff(const struct vm_area_struct *vma)
+static inline pgoff_t vma_last_anon_pgoff(const struct vm_area_struct *vma)
{
- return vma_end_virt_pgoff(vma) - 1;
+ return vma_end_anon_pgoff(vma) - 1;
}
static inline unsigned long vma_desc_size(const struct vm_area_desc *desc)
--- a/include/linux/mm_types.h~b
+++ a/include/linux/mm_types.h
@@ -968,10 +968,10 @@ struct vm_area_struct {
unsigned int vm_lock_seq;
#endif
/*
- * Low 32-bits of virtual page offset.
- * See vma_start_virt_pgoff() comment for details.
+ * Low 32-bits of anonymous page offset.
+ * See vma_start_anon_pgoff() comment for details.
*/
- unsigned int __vm_virt_pgoff_lo;
+ unsigned int __vm_anon_pgoff_lo;
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
* list, after a COW of one of the file pages. A MAP_SHARED vma
@@ -1049,10 +1049,10 @@ struct vm_area_struct {
#endif
#ifdef CONFIG_64BIT
/*
- * High 32-bits of virtual page offset.
- * See vma_start_virt_pgoff() comment for details.
+ * High 32-bits of anonymous page offset.
+ * See vma_start_anon_pgoff() comment for details.
*/
- unsigned int __vm_virt_pgoff_hi;
+ unsigned int __vm_anon_pgoff_hi;
#endif
/*
* For areas with an address space and backing store,
--- a/include/linux/pagemap.h~b
+++ a/include/linux/pagemap.h
@@ -1094,79 +1094,46 @@ static inline pgoff_t linear_page_delta(
static inline pgoff_t linear_page_index(const struct vm_area_struct *vma,
const unsigned long address)
{
- pgoff_t pgoff;
-
- pgoff = linear_page_delta(vma, address);
- pgoff += vma_start_pgoff(vma);
- return pgoff;
+ return linear_page_delta(vma, address) + vma_start_pgoff(vma);
}
-static inline pgoff_t __linear_virt_page_index(const struct vm_area_struct *vma,
- const unsigned long address)
+static inline pgoff_t __linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
{
- pgoff_t pgoff;
-
- pgoff = linear_page_delta(vma, address);
- pgoff += vma_start_virt_pgoff(vma);
- return pgoff;
+ return linear_page_delta(vma, address) + vma_start_anon_pgoff(vma);
}
/**
- * linear_virt_page_index() - Determine the absolute virtual page offset of
+ * linear_anon_page_index() - Determine the absolute anonymous page offset of
* @address within @vma.
* @vma: An anonymous or MAP_PRIVATE file-backed VMA in which @address resides.
* @address: The address whose absolute page offset is required.
*
- * This returns the virtual page offset of @address, which is the page offset
+ * This returns the anonymous page offset of @address, which is the page offset
* the address possessed at the time the VMA was first faulted.
*
* For anonymous mappings, this returns the same value as linear_page_index().
*
- * For MAP_PRIVATE file-backed mappings, this returns the virtual page offset of
- * @address, which is the page offset the address possessed at the time the VMA
- * was first faulted.
+ * For MAP_PRIVATE file-backed mappings, this returns the anonymous page offset
+ * of @address, which is the page offset the address possessed at the time the
+ * VMA was first faulted.
*
* It is not valid to call this function for shared file-backed mappings.
*
- * Returns: The absolute virtual page offset of @address within @vma.
+ * Returns: The absolute anonymous page offset of @address within @vma.
*/
-static inline pgoff_t linear_virt_page_index(const struct vm_area_struct *vma,
- const unsigned long address)
+static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
{
- const pgoff_t pgoff = __linear_virt_page_index(vma, address);
+ const pgoff_t pgoff = __linear_anon_page_index(vma, address);
- VM_WARN_ON_ONCE(vma_test(vma, VMA_SHARED_BIT));
+ VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
if (vma_is_anonymous(vma))
VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
return pgoff;
}
-/**
- * linear_folio_page_index() - Determine the absolute page offset of
- * @address within @vma from @folio.
- * @folio: The folio whose linear page index is sought.
- * @vma: The VMA in which @address resides.
- * @address: The address whose absolute page offset is required.
- *
- * Determines whether to obtain the virtual linear page index based on whether
- * @folio is anonymous or not.
- *
- * See the descriptions of linear_virt_page_index() and linear_page_index() for
- * details of each.
- *
- * Returns: The absolute page offset of @address within @vma.
- */
-static inline pgoff_t linear_folio_page_index(const struct folio *folio,
- const struct vm_area_struct *vma,
- const unsigned long address)
-{
- if (folio_test_anon(folio))
- return linear_virt_page_index(vma, address);
-
- return linear_page_index(vma, address);
-}
-
struct wait_page_key {
struct folio *folio;
int bit_nr;
--- a/include/linux/rmap.h~b
+++ a/include/linux/rmap.h
@@ -864,14 +864,14 @@ struct page *make_device_exclusive(struc
struct page_vma_mapped_walk {
unsigned long pfn;
unsigned long nr_pages;
- pgoff_t pgoff;
+ pgoff_t pgoff; /* Only meaningful if nr_pages > 1 and not a KSM walk */
struct vm_area_struct *vma;
unsigned long address;
pmd_t *pmd;
pte_t *pte;
spinlock_t *ptl;
unsigned int flags;
- bool is_anon_walk;
+ bool pgoff_is_anon : 1;
};
#define DEFINE_FOLIO_VMA_WALK(name, _folio, _vma, _address, _flags) \
@@ -882,7 +882,7 @@ struct page_vma_mapped_walk {
.vma = _vma, \
.address = _address, \
.flags = _flags, \
- .is_anon_walk = folio_test_anon(_folio), \
+ .pgoff_is_anon = folio_test_anon(_folio), \
}
static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)
--- a/include/linux/swapops.h~b
+++ a/include/linux/swapops.h
@@ -325,8 +325,8 @@ struct page_vma_mapped_walk;
extern int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw,
struct page *page);
-extern void remove_migration_pmd(struct page_vma_mapped_walk *pvmw,
- struct page *new);
+void remove_migration_pmd(struct page_vma_mapped_walk *pvmw,
+ struct folio *folio);
extern void pmd_migration_entry_wait(struct mm_struct *mm, pmd_t *pmd);
@@ -346,7 +346,7 @@ static inline int set_pmd_migration_entr
}
static inline void remove_migration_pmd(struct page_vma_mapped_walk *pvmw,
- struct page *new)
+ struct folio *folio)
{
BUILD_BUG();
}
--- a/kernel/events/uprobes.c~b
+++ a/kernel/events/uprobes.c
@@ -513,7 +513,7 @@ int uprobe_write(struct arch_uprobe *aup
uprobe = container_of(auprobe, struct uprobe, arch);
- if (WARN_ON_ONCE(!is_cow_mapping(vma->vm_flags)))
+ if (WARN_ON_ONCE(!vma_is_cow_mapping(vma)))
return -EINVAL;
/*
--- a/mm/gup.c~b
+++ a/mm/gup.c
@@ -1236,7 +1236,7 @@ static int check_vma_flags(struct vm_are
* Anon pages in shared mappings are surprising: now
* just reject it.
*/
- if (!is_cow_mapping(vm_flags))
+ if (!vma_is_cow_mapping(vma))
return -EFAULT;
}
} else if (!(vm_flags & VM_READ)) {
--- a/mm/huge_memory.c~b
+++ a/mm/huge_memory.c
@@ -1681,7 +1681,7 @@ vm_fault_t vmf_insert_pfn_pmd(struct vm_
BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
(VM_PFNMAP|VM_MIXEDMAP));
- BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));
+ BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma));
pfnmap_setup_cachemode_pfn(pfn, &pgprot);
@@ -1789,7 +1789,7 @@ vm_fault_t vmf_insert_pfn_pud(struct vm_
BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
(VM_PFNMAP|VM_MIXEDMAP));
- BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));
+ BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma));
pfnmap_setup_cachemode_pfn(pfn, &pgprot);
@@ -1931,7 +1931,7 @@ int copy_huge_pmd(struct mm_struct *dst_
* applied special bit, or we made the PRIVATE mapping be
* able to wrongly write to the backend MMIO.
*/
- VM_WARN_ON_ONCE(is_cow_mapping(src_vma->vm_flags) && pmd_write(pmd));
+ VM_WARN_ON_ONCE(vma_is_cow_mapping(src_vma) && pmd_write(pmd));
goto set_pmd;
}
@@ -2052,7 +2052,7 @@ int copy_huge_pud(struct mm_struct *dst_
* TODO: once we support anonymous pages, use
* folio_try_dup_anon_rmap_*() and split if duplicating fails.
*/
- if (is_cow_mapping(vma->vm_flags) && pud_write(pud)) {
+ if (vma_is_cow_mapping(vma) && pud_write(pud)) {
pudp_set_wrprotect(src_mm, addr, src_pud);
pud = pud_wrprotect(pud);
}
@@ -2930,8 +2930,7 @@ int move_pages_huge_pmd(struct mm_struct
}
folio_move_anon_rmap(src_folio, dst_vma);
- src_folio->index = linear_folio_page_index(src_folio, dst_vma,
- dst_addr);
+ src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
_dst_pmd = folio_mk_pmd(src_folio, dst_vma->vm_page_prot);
/* Follow mremap() behavior and treat the entry dirty after the move */
@@ -5078,9 +5077,8 @@ int set_pmd_migration_entry(struct page_
return 0;
}
-void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new)
+void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct folio *folio)
{
- struct folio *folio = page_folio(new);
struct vm_area_struct *vma = pvmw->vma;
struct mm_struct *mm = vma->vm_mm;
unsigned long address = pvmw->address;
@@ -5116,11 +5114,9 @@ void remove_migration_pmd(struct page_vm
swp_entry_t entry;
if (pmd_write(pmde))
- entry = make_writable_device_private_entry(
- page_to_pfn(new));
+ entry = make_writable_device_private_entry(folio_pfn(folio));
else
- entry = make_readable_device_private_entry(
- page_to_pfn(new));
+ entry = make_readable_device_private_entry(folio_pfn(folio));
pmde = softleaf_to_pmd(entry);
if (pmd_swp_soft_dirty(*pvmw->pmd))
@@ -5135,11 +5131,12 @@ void remove_migration_pmd(struct page_vm
if (!softleaf_is_migration_read(entry))
rmap_flags |= RMAP_EXCLUSIVE;
- folio_add_anon_rmap_pmd(folio, new, vma, haddr, rmap_flags);
+ folio_add_anon_rmap_pmd(folio, &folio->page, vma, haddr, rmap_flags);
} else {
- folio_add_file_rmap_pmd(folio, new, vma);
+ folio_add_file_rmap_pmd(folio, &folio->page, vma);
}
- VM_BUG_ON(pmd_write(pmde) && folio_test_anon(folio) && !PageAnonExclusive(new));
+ VM_WARN_ON_ONCE(pmd_write(pmde) && folio_test_anon(folio) &&
+ !PageAnonExclusive(&folio->page));
set_pmd_at(mm, haddr, pvmw->pmd, pmde);
/* No need to invalidate - it was non-present before */
--- a/mm/hugetlb.c~b
+++ a/mm/hugetlb.c
@@ -4898,7 +4898,7 @@ int copy_hugetlb_page_range(struct mm_st
pte_t *src_pte, *dst_pte, entry;
struct folio *pte_folio;
unsigned long addr;
- bool cow = is_cow_mapping(src_vma->vm_flags);
+ bool cow = vma_is_cow_mapping(src_vma);
struct hstate *h = hstate_vma(src_vma);
unsigned long sz = huge_page_size(h);
unsigned long npages = pages_per_huge_page(h);
--- a/mm/internal.h~b
+++ a/mm/internal.h
@@ -240,19 +240,18 @@ static inline int mmap_file(struct file
{
int err = vfs_mmap(file, vma);
- /* Hooks cannot mark themselves anonymous. */
- if (WARN_ON_ONCE(vma_is_anonymous(vma)))
- err = -EINVAL;
-
- if (likely(!err))
- return 0;
-
/*
- * 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.
+ * 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.
*/
- vma->vm_ops = &vma_dummy_vm_ops;
+ if (unlikely(err || !vma->vm_ops))
+ vma->vm_ops = &vma_dummy_vm_ops;
return err;
}
@@ -950,7 +949,7 @@ folio_within_range(struct folio *folio,
pgoff_folio = folio_pgoff(folio);
pgoff_vma_start = folio_test_anon(folio) ?
- vma_start_virt_pgoff(vma) : vma_start_pgoff(vma);
+ vma_start_anon_pgoff(vma) : vma_start_pgoff(vma);
if (start < vma->vm_start)
start = vma->vm_start;
@@ -1064,20 +1063,20 @@ static inline unsigned long vma_fileback
* vma_anon_address - Find the virtual address an anonymous page range is mapped
* at.
* @vma: The vma which maps this object.
- * @pgoff_virt: The virtual page index belonging to the folio.
+ * @pgoff_anon: The anonymous page index belonging to the folio.
* @nr_pages: The number of pages to consider.
*
* This is only valid for anonymous or MAP_PRIVATE-mapped file-backed VMAs.
*
- * Returns: If any page in this range is mapped by this VMA, return the first address
- * where any of these pages appear. Otherwise, return -EFAULT.
+ * Returns: If any page in this range is mapped by this VMA, return the first
+ * address where any of these pages appear. Otherwise, return -EFAULT.
*/
static inline unsigned long vma_anon_address(const struct vm_area_struct *vma,
- pgoff_t pgoff_virt, unsigned long nr_pages)
+ pgoff_t pgoff_anon, unsigned long nr_pages)
{
- VM_WARN_ON_ONCE(!vma_is_anonymous(vma) && vma_test(vma, VMA_SHARED_BIT));
+ VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
- return __vma_address(vma, pgoff_virt, vma_start_virt_pgoff(vma), nr_pages);
+ return __vma_address(vma, pgoff_anon, vma_start_anon_pgoff(vma), nr_pages);
}
/*
@@ -1086,22 +1085,20 @@ static inline unsigned long vma_anon_add
*/
static inline unsigned long vma_address_end(struct page_vma_mapped_walk *pvmw)
{
+ const pgoff_t pgoff_end = pvmw->pgoff + pvmw->nr_pages;
const struct vm_area_struct *vma = pvmw->vma;
- const pgoff_t pgoff = pvmw->pgoff;
pgoff_t pgoff_vma_start;
unsigned long address;
- pgoff_t pgoff_end;
/* Common case, plus ->pgoff is invalid for KSM */
if (pvmw->nr_pages == 1)
return pvmw->address + PAGE_SIZE;
- if (pvmw->is_anon_walk)
- pgoff_vma_start = vma_start_virt_pgoff(vma);
+ if (pvmw->pgoff_is_anon)
+ pgoff_vma_start = vma_start_anon_pgoff(vma);
else
pgoff_vma_start = vma_start_pgoff(vma);
- pgoff_end = pgoff + pvmw->nr_pages;
address = vma->vm_start +
((pgoff_end - pgoff_vma_start) << PAGE_SHIFT);
/* Check for address beyond vma (or wrapped through 0?) */
@@ -1393,7 +1390,7 @@ static inline bool gup_must_unshare(stru
* ... because we only care about writable private ("COW")
* mappings where we have to break COW early.
*/
- return is_cow_mapping(vma->vm_flags);
+ return vma_is_cow_mapping(vma);
}
/* Paired with a memory barrier in folio_try_share_anon_rmap_*(). */
--- a/mm/interval_tree.c~b
+++ a/mm/interval_tree.c
@@ -83,12 +83,12 @@ mapping_rmap_tree_iter_next(struct vm_ar
static pgoff_t avc_start_pgoff(struct anon_vma_chain *avc)
{
- return vma_start_virt_pgoff(avc->vma);
+ return vma_start_anon_pgoff(avc->vma);
}
static pgoff_t avc_last_pgoff(struct anon_vma_chain *avc)
{
- return vma_last_virt_pgoff(avc->vma);
+ return vma_last_anon_pgoff(avc->vma);
}
INTERVAL_TREE_DEFINE(struct anon_vma_chain, rb, pgoff_t, rb_subtree_last,
--- a/mm/ksm.c~b
+++ a/mm/ksm.c
@@ -1625,8 +1625,7 @@ static int try_to_merge_with_ksm_page(st
* stable_tree, break_cow() will clean it up.
*/
rmap_item->anon_vma = vma->anon_vma;
- /* The VMA is always anon/MAP_PRIVATE-file backed so use anon index. */
- rmap_item->linear_page_index = linear_virt_page_index(vma, rmap_item->address);
+ rmap_item->linear_page_index = linear_anon_page_index(vma, rmap_item->address);
get_anon_vma(vma->anon_vma);
out:
mmap_read_unlock(mm);
@@ -3153,7 +3152,7 @@ struct folio *ksm_might_need_to_copy(str
return folio; /* no need to copy it */
} else if (!anon_vma) {
return folio; /* no need to copy it */
- } else if (folio->index == linear_virt_page_index(vma, addr) &&
+ } else if (folio->index == linear_anon_page_index(vma, addr) &&
anon_vma->root == vma->anon_vma->root) {
return folio; /* still no need to copy it */
}
@@ -3223,7 +3222,7 @@ again:
/*
* Currently, KSM folios are always small folios, so it's
* sufficient to search for a single page. We can simply use
- * the linear_virt_page_index of the original de-duplicate
+ * the linear_anon_page_index of the original de-duplicate
* anonymous page that we remembered in the rmap_item while
* de-duplicating. Note that mremap() always de-duplicates KSM
* folios: so if there was mremap() in our parent or our child,
--- a/mm/memory.c~b
+++ a/mm/memory.c
@@ -631,14 +631,14 @@ static void print_bad_page_map(struct vm
{
struct address_space *mapping;
char entry_str[PTVAL_STR_MAX];
- pgoff_t index, virt_index;
+ pgoff_t index, anon_index;
if (is_bad_page_map_ratelimited())
return;
mapping = vma->vm_file ? vma->vm_file->f_mapping : NULL;
index = linear_page_index(vma, addr);
- virt_index = __linear_virt_page_index(vma, addr);
+ anon_index = __linear_anon_page_index(vma, addr);
ptval_bytes_to_hex_str(entry_str, sizeof(entry_str), entry, entry_size);
pr_alert("BUG: Bad page map in process %s %s:%s", current->comm,
@@ -646,9 +646,14 @@ static void print_bad_page_map(struct vm
__print_bad_page_map_pgtable(vma->vm_mm, addr);
if (page)
dump_page(page, "bad page map");
- pr_alert("addr:%px vm_flags:%08lx anon_vma:%px mapping:%px index:%lx virt_index:%lx\n",
- (void *)addr, vma->vm_flags, vma->anon_vma, mapping, index,
- virt_index);
+ pr_alert("addr:%px vm_flags:%08lx anon_vma:%px mapping:%px",
+ (void *)addr, vma->vm_flags, vma->anon_vma, mapping);
+ if (!vma_is_cow_mapping(vma) || index == anon_index) {
+ pr_cont(" index:%lx\n", index);
+ } else {
+ pr_cont(" index:%lx (file) %lx (anon)\n", index, anon_index);
+ }
+
pr_alert("file:%pD fault:%ps mmap:%ps mmap_prepare: %ps read_folio:%ps\n",
vma->vm_file,
vma->vm_ops ? vma->vm_ops->fault : NULL,
@@ -782,7 +787,7 @@ static inline struct page *__vm_normal_p
/* Only CoW'ed anon folios are "normal". */
if (pfn == index)
return NULL;
- if (!is_cow_mapping(vma->vm_flags))
+ if (!vma_is_cow_mapping(vma))
return NULL;
}
}
@@ -1004,7 +1009,6 @@ copy_nonpresent_pte(struct mm_struct *ds
pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *dst_vma,
struct vm_area_struct *src_vma, unsigned long addr, int *rss)
{
- vm_flags_t vm_flags = dst_vma->vm_flags;
pte_t orig_pte = ptep_get(src_pte);
softleaf_t entry = softleaf_from_pte(orig_pte);
pte_t pte = orig_pte;
@@ -1028,7 +1032,7 @@ copy_nonpresent_pte(struct mm_struct *ds
rss[mm_counter(folio)]++;
if (!softleaf_is_migration_read(entry) &&
- is_cow_mapping(vm_flags)) {
+ vma_is_cow_mapping(dst_vma)) {
/*
* COW mappings require pages in both parent and child
* to be set to read. A previously exclusive entry is
@@ -1069,7 +1073,7 @@ copy_nonpresent_pte(struct mm_struct *ds
* save and restore device driver state).
*/
if (softleaf_is_device_private_write(entry) &&
- is_cow_mapping(vm_flags)) {
+ vma_is_cow_mapping(dst_vma)) {
entry = make_readable_device_private_entry(
swp_offset(entry));
pte = swp_entry_to_pte(entry);
@@ -1084,7 +1088,7 @@ copy_nonpresent_pte(struct mm_struct *ds
* exclusive entries currently only support private writable
* (ie. COW) mappings.
*/
- VM_BUG_ON(!is_cow_mapping(src_vma->vm_flags));
+ VM_BUG_ON(!vma_is_cow_mapping(src_vma));
if (try_restore_exclusive_pte(src_vma, addr, src_pte, orig_pte))
return -EBUSY;
return -ENOENT;
@@ -1183,7 +1187,7 @@ static __always_inline void __copy_prese
}
/* If it's a COW mapping, write protect it both processes. */
- if (is_cow_mapping(src_vma->vm_flags) && writable) {
+ if (vma_is_cow_mapping(src_vma) && writable) {
wrprotect_ptes(src_mm, addr, src_pte, nr);
pte = pte_wrprotect(pte);
}
@@ -1604,9 +1608,9 @@ copy_page_range(struct vm_area_struct *d
* We need to invalidate the secondary MMU mappings only when
* there could be a permission downgrade on the ptes of the
* parent mm. And a permission downgrade will only happen if
- * is_cow_mapping() returns true.
+ * vma_is_cow_mapping() returns true.
*/
- is_cow = is_cow_mapping(src_vma->vm_flags);
+ is_cow = vma_is_cow_mapping(src_vma);
if (is_cow) {
mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE,
@@ -2439,7 +2443,7 @@ static bool vm_mixed_zeropage_allowed(st
if (mm_forbids_zeropage(vma->vm_mm))
return false;
/* zeropages in COW mappings are common and unproblematic. */
- if (is_cow_mapping(vma->vm_flags))
+ if (vma_is_cow_mapping(vma))
return true;
/* Mappings that do not allow for writable PTEs are unproblematic. */
if (!(vma->vm_flags & (VM_WRITE | VM_MAYWRITE)))
@@ -2890,7 +2894,7 @@ vm_fault_t vmf_insert_pfn_prot(struct vm
BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
(VM_PFNMAP|VM_MIXEDMAP));
- BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));
+ BUG_ON((vma->vm_flags & VM_PFNMAP) && vma_is_cow_mapping(vma));
BUG_ON((vma->vm_flags & VM_MIXEDMAP) && pfn_valid(pfn));
if (addr < vma->vm_start || addr >= vma->vm_end)
@@ -3302,7 +3306,7 @@ static int remap_pfn_range_prepare_vma(s
unsigned long size)
{
const unsigned long end = addr + PAGE_ALIGN(size);
- const bool is_cow = is_cow_mapping(vma->vm_flags);
+ const bool is_cow = vma_is_cow_mapping(vma);
int err;
err = get_remap_pgoff(is_cow, addr, end, vma->vm_start, vma->vm_end,
@@ -6802,7 +6806,7 @@ static vm_fault_t sanitize_fault_flags(s
* FAULT_FLAG_UNSHARE only applies to COW mappings. Let's
* just treat it like an ordinary read-fault otherwise.
*/
- if (!is_cow_mapping(vma->vm_flags))
+ if (!vma_is_cow_mapping(vma))
*flags &= ~FAULT_FLAG_UNSHARE;
} else if (*flags & FAULT_FLAG_WRITE) {
/* Write faults on read-only mappings are impossible ... */
@@ -6810,7 +6814,7 @@ static vm_fault_t sanitize_fault_flags(s
return VM_FAULT_SIGSEGV;
/* ... and FOLL_FORCE only applies to COW mappings. */
if (WARN_ON_ONCE(!(vma->vm_flags & VM_WRITE) &&
- !is_cow_mapping(vma->vm_flags)))
+ !vma_is_cow_mapping(vma)))
return VM_FAULT_SIGSEGV;
}
#ifdef CONFIG_PER_VMA_LOCK
--- a/mm/mempolicy.c~b
+++ a/mm/mempolicy.c
@@ -844,7 +844,7 @@ bool folio_can_map_prot_numa(struct foli
return false;
/* Also skip shared copy-on-write folios */
- if (is_cow_mapping(vma->vm_flags) && folio_maybe_mapped_shared(folio))
+ if (vma_is_cow_mapping(vma) && folio_maybe_mapped_shared(folio))
return false;
/* Folios are pinned and can't be migrated */
--- a/mm/migrate.c~b
+++ a/mm/migrate.c
@@ -356,25 +356,18 @@ static bool remove_migration_pte(struct
while (page_vma_mapped_walk(&pvmw)) {
rmap_t rmap_flags = RMAP_NONE;
- pte_t old_pte;
- pte_t pte;
+ unsigned long idx = 0;
softleaf_t entry;
struct page *new;
- unsigned long idx = 0;
-
- /* pgoff is invalid for ksm pages, but they are never large */
- if (folio_test_large(folio) && !folio_test_hugetlb(folio)) {
- idx += linear_folio_page_index(folio, vma, pvmw.address);
- idx -= pvmw.pgoff;
- }
- new = folio_page(folio, idx);
+ pte_t old_pte;
+ pte_t pte;
#ifdef CONFIG_ARCH_HAS_PMD_SOFTLEAVES
/* PMD-mapped THP migration entry */
if (!pvmw.pte) {
VM_BUG_ON_FOLIO(folio_test_hugetlb(folio) ||
!folio_test_pmd_mappable(folio), folio);
- remove_migration_pmd(&pvmw, new);
+ remove_migration_pmd(&pvmw, folio);
continue;
}
#endif
@@ -383,14 +376,18 @@ static bool remove_migration_pte(struct
pvmw.pte);
else
old_pte = ptep_get(pvmw.pte);
+
+ entry = softleaf_from_pte(old_pte);
+ if (folio_test_large(folio) && !folio_test_hugetlb(folio))
+ idx = softleaf_to_pfn(entry) - pvmw.pfn;
+
if (rmap_walk_arg->map_unused_to_zeropage &&
try_to_map_unused_to_zeropage(&pvmw, folio, old_pte, idx))
continue;
folio_get(folio);
+ new = folio_page(folio, idx);
pte = mk_pte(new, READ_ONCE(vma->vm_page_prot));
-
- entry = softleaf_from_pte(old_pte);
if (!softleaf_is_migration_young(entry))
pte = pte_mkold(pte);
if (folio_test_dirty(folio) && softleaf_is_migration_dirty(entry))
--- a/mm/mremap.c~b
+++ a/mm/mremap.c
@@ -1266,8 +1266,8 @@ static int copy_vma_and_data(struct vma_
struct vm_area_struct **new_vma_ptr)
{
const pgoff_t new_pgoff = linear_page_index(vrm->vma, vrm->addr);
- const pgoff_t new_virt_pgoff =
- __linear_virt_page_index(vrm->vma, vrm->addr);
+ const pgoff_t new_anon_pgoff =
+ __linear_anon_page_index(vrm->vma, vrm->addr);
struct vm_area_struct *vma = vrm->vma;
struct vm_area_struct *new_vma;
unsigned long moved_len;
@@ -1275,7 +1275,7 @@ static int copy_vma_and_data(struct vma_
PAGETABLE_MOVE(pmc, NULL, NULL, vrm->addr, vrm->new_addr, vrm->old_len);
new_vma = copy_vma(&vma, vrm->new_addr, vrm->new_len, new_pgoff,
- new_virt_pgoff, &pmc.need_rmap_locks);
+ new_anon_pgoff, &pmc.need_rmap_locks);
if (!new_vma) {
vrm_uncharge(vrm);
*new_vma_ptr = NULL;
--- a/mm/rmap.c~b
+++ a/mm/rmap.c
@@ -1240,7 +1240,7 @@ static bool mapping_wrprotect_range_one(
.vma = vma,
.address = address,
.flags = PVMW_SYNC,
- .is_anon_walk = false,
+ .pgoff_is_anon = false,
};
state->cleaned += page_vma_mkclean_one(&pvmw);
@@ -1318,7 +1318,7 @@ int pfn_mkclean_range(unsigned long pfn,
.pgoff = pgoff,
.vma = vma,
.flags = PVMW_SYNC,
- .is_anon_walk = false,
+ .pgoff_is_anon = false,
};
if (invalid_mkclean_vma(vma, NULL))
@@ -1485,7 +1485,7 @@ static void __folio_set_anon(struct foli
*/
anon_vma = (void *) anon_vma + FOLIO_MAPPING_ANON;
WRITE_ONCE(folio->mapping, (struct address_space *) anon_vma);
- folio->index = linear_virt_page_index(vma, address);
+ folio->index = linear_anon_page_index(vma, address);
}
/**
@@ -1513,7 +1513,7 @@ static void __page_check_anon_rmap(const
VM_BUG_ON_FOLIO(folio_anon_vma(folio)->root != vma->anon_vma->root,
folio);
VM_BUG_ON_PAGE(page_pgoff(folio, page) !=
- linear_virt_page_index(vma, address), page);
+ linear_anon_page_index(vma, address), page);
}
static __always_inline void __folio_add_anon_rmap(struct folio *folio,
--- a/mm/userfaultfd.c~b
+++ a/mm/userfaultfd.c
@@ -1352,8 +1352,7 @@ static long move_present_ptes(struct mm_
}
folio_move_anon_rmap(src_folio, dst_vma);
- src_folio->index = linear_folio_page_index(src_folio, dst_vma,
- dst_addr);
+ src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
orig_dst_pte = folio_mk_pte(src_folio, dst_vma->vm_page_prot);
/* Set soft dirty bit so userspace can notice the pte was moved */
@@ -1429,8 +1428,7 @@ static int move_swap_pte(struct mm_struc
*/
if (src_folio) {
folio_move_anon_rmap(src_folio, dst_vma);
- src_folio->index = linear_folio_page_index(src_folio, dst_vma,
- dst_addr);
+ src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
} else {
/*
* Check if the swap entry is cached after acquiring the src_pte
--- a/mm/vma.c~b
+++ a/mm/vma.c
@@ -18,7 +18,7 @@ struct mmap_state {
unsigned long addr;
unsigned long end;
pgoff_t pgoff;
- pgoff_t virt_pgoff;
+ pgoff_t anon_pgoff;
unsigned long pglen;
union {
vm_flags_t vm_flags;
@@ -47,22 +47,14 @@ struct mmap_state {
bool file_doesnt_need_get :1;
};
-static inline pgoff_t map_anon_pgoff(const struct mmap_state *map)
-{
- if (vma_flags_test(&map->vma_flags, VMA_SHARED_BIT))
- return map->pgoff;
-
- return map->virt_pgoff;
-}
-
-#define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, virt_pgoff_, vma_flags_, file_) \
+#define MMAP_STATE(name, mm_, vmi_, addr_, len_, pgoff_, anon_pgoff_, vma_flags_, file_) \
struct mmap_state name = { \
.mm = mm_, \
.vmi = vmi_, \
.addr = addr_, \
.end = (addr_) + (len_), \
.pgoff = pgoff_, \
- .virt_pgoff = virt_pgoff_, \
+ .anon_pgoff = anon_pgoff_, \
.pglen = PHYS_PFN(len_), \
.vma_flags = vma_flags_, \
.file = file_, \
@@ -77,7 +69,7 @@ static inline pgoff_t map_anon_pgoff(con
.end = (map_)->end, \
.vma_flags = (map_)->vma_flags, \
.pgoff = (map_)->pgoff, \
- .anon_pgoff = map_anon_pgoff(map_), \
+ .anon_pgoff = (map_)->anon_pgoff, \
.file = (map_)->file, \
.prev = (map_)->prev, \
.middle = vma_, \
@@ -93,11 +85,11 @@ static void __vma_set_range(struct vm_ar
}
static void vma_set_range(struct vm_area_struct *vma, unsigned long start,
- unsigned long end, pgoff_t pgoff, pgoff_t virt_pgoff)
+ unsigned long end, pgoff_t pgoff, pgoff_t anon_pgoff)
{
__vma_set_range(vma, start, end);
vma_set_pgoff(vma, pgoff);
- vma_set_virt_pgoff(vma, virt_pgoff);
+ vma_set_anon_pgoff(vma, anon_pgoff);
}
/* Was this VMA ever forked from a parent, i.e. maybe contains CoW mappings? */
@@ -213,6 +205,25 @@ static void init_multi_vma_prep(struct v
}
/*
+ * Does this merge require that adjacent VMAs must have adjacent anonymous page
+ * offsets in addition to having adjacent vma->vm_pgoff?
+ *
+ * This is only required for MAP_PRIVATE-file backed mappings as the page offset
+ * for pure anonymous VMAs is equal to the anonymous page offset.
+ *
+ * Read-only shared mappings (with VMA_SHARED_BIT cleared) are always unfaulted
+ * so automatically have correct anonymous page offset (as it is always updated
+ * on remap).
+ *
+ * 'Special' mappings in the sense of VDSO, VVAR etc. have !file but would in
+ * any case not be candidates for merge nor be mergeable.
+ */
+static bool needs_adjacent_anon_pgoff(const struct vma_merge_struct *vmg)
+{
+ return vmg->file && vma_flags_is_cow_mapping(&vmg->vma_flags);
+}
+
+/*
* Return true if we can merge this (vma_flags,anon_vma,file,vm_pgoff)
* in front of (at a lower virtual address and file offset than) the vma.
*
@@ -233,7 +244,8 @@ static bool can_vma_merge_before(struct
return false;
if (vmg_end_pgoff(vmg) != vma_start_pgoff(vmg->next))
return false;
- if (vmg_end_anon_pgoff(vmg) != vma_start_anon_pgoff(vmg->next))
+ if (needs_adjacent_anon_pgoff(vmg) &&
+ vmg_end_anon_pgoff(vmg) != vma_start_anon_pgoff(vmg->next))
return false;
return true;
}
@@ -255,7 +267,8 @@ static bool can_vma_merge_after(struct v
return false;
if (vma_end_pgoff(vmg->prev) != vmg_start_pgoff(vmg))
return false;
- if (vma_end_anon_pgoff(vmg->prev) != vmg_start_anon_pgoff(vmg))
+ if (needs_adjacent_anon_pgoff(vmg) &&
+ vma_end_anon_pgoff(vmg->prev) != vmg_start_anon_pgoff(vmg))
return false;
return true;
}
@@ -1930,27 +1943,27 @@ static int vma_link(struct mm_struct *mm
*/
struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
unsigned long addr, unsigned long len, pgoff_t pgoff,
- pgoff_t virt_pgoff, bool *need_rmap_locks)
+ pgoff_t anon_pgoff, bool *need_rmap_locks)
{
struct vm_area_struct *vma = *vmap;
- const bool is_shared = vma_test(vma, VMA_SHARED_BIT);
- unsigned long vma_start = vma->vm_start;
+ unsigned long old_vma_start = vma->vm_start;
struct mm_struct *mm = vma->vm_mm;
struct vm_area_struct *new_vma;
- bool faulted_in_anon_vma = true;
+ bool can_self_merge = false;
VMA_ITERATOR(vmi, mm, addr);
VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len);
/*
- * If a vma has not yet been faulted, update its virtual pgoff to match
- * the new location to increase its chance of merging.
+ * If a vma has not yet been faulted, update its anonymous pgoff to
+ * match the new location to increase its chance of merging.
*/
- if (!vma->anon_vma && !is_shared) {
- virt_pgoff = addr >> PAGE_SHIFT;
+ if (!vma->anon_vma) {
+ anon_pgoff = addr >> PAGE_SHIFT;
- if (vma_is_anonymous(vma))
- pgoff = virt_pgoff;
- faulted_in_anon_vma = false;
+ if (vma_is_anonymous(vma)) {
+ pgoff = anon_pgoff;
+ can_self_merge = true;
+ }
}
/*
@@ -1966,39 +1979,35 @@ struct vm_area_struct *copy_vma(struct v
return NULL; /* should never get here */
vmg.pgoff = pgoff;
- vmg.anon_pgoff = is_shared ? pgoff : virt_pgoff;
+ vmg.anon_pgoff = anon_pgoff;
vmg.next = vma_iter_next_rewind(&vmi, NULL);
new_vma = vma_merge_copied_range(&vmg);
if (new_vma) {
- /*
- * Source vma may have been merged into new_vma
- */
- if (unlikely(vma_start >= new_vma->vm_start &&
- vma_start < new_vma->vm_end)) {
+ /* Self-merged and VMA replaced. */
+ if (unlikely(new_vma->vm_start < old_vma_start &&
+ new_vma->vm_end > old_vma_start)) {
/*
- * The only way we can get a vma_merge with
- * self during an mremap is if the vma hasn't
- * been faulted in yet and we were allowed to
- * reset the dst vma->vm_pgoff to the
- * destination address of the mremap to allow
- * the merge to happen. mremap must change the
- * vm_pgoff linearity between src and dst vmas
- * (in turn preventing a vma_merge) to be
- * safe. It is only safe to keep the vm_pgoff
- * linear if there are no pages mapped yet.
+ * The only way a VMA can both self-merge and be
+ * replaced is if the remap places the new VMA
+ * immediately prior to its old self ('next') and
+ * immediately after another VMA ('prev') causing the
+ * next to be removed and prev to be expanded to cover
+ * the entire range.
+ *
+ * This should only be possible if the anonymous page
+ * offset was updated, i.e. the VMA is unfaulted.
*/
- VM_WARN_ON_ONCE_VMA(faulted_in_anon_vma, new_vma);
+ VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma);
*vmap = vma = new_vma;
}
*need_rmap_locks =
- (vma_start_pgoff(new_vma) <= vma_start_pgoff(vma)) ||
- (vma_start_anon_pgoff(new_vma) <= vma_start_anon_pgoff(vma));
+ (vma_start_pgoff(new_vma) <= vma_start_pgoff(vma));
} else {
new_vma = vm_area_dup(vma);
if (!new_vma)
goto out;
- vma_set_range(new_vma, addr, addr + len, pgoff, virt_pgoff);
+ vma_set_range(new_vma, addr, addr + len, pgoff, anon_pgoff);
if (vma_dup_policy(vma, new_vma))
goto out_free_vma;
if (anon_vma_clone(new_vma, vma, VMA_OP_REMAP))
@@ -2066,7 +2075,7 @@ static int anon_vma_compatible(struct vm
/* Page offset must align. */
if (vma_end_pgoff(a) != vma_start_pgoff(b))
return false;
- /* Anon page offset must align. */
+ /* Only reached from anon path, so either MAP_PRIVATE file or anon. */
if (vma_end_anon_pgoff(a) != vma_start_anon_pgoff(b))
return false;
return true;
@@ -2615,8 +2624,11 @@ static int __mmap_new_file_vma(struct mm
static bool map_is_dev_zero(const struct mmap_state *map)
{
const struct file *file = map->file;
- const struct inode *inode = file_inode(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;
@@ -2671,7 +2683,7 @@ static int __mmap_new_vma(struct mmap_st
if (is_anon)
vma_set_anonymous(vma);
- vma_set_range(vma, map->addr, map->end, map->pgoff, map->virt_pgoff);
+ vma_set_range(vma, map->addr, map->end, map->pgoff, map->anon_pgoff);
vma->flags = map->vma_flags;
vma->vm_page_prot = map->page_prot;
@@ -2869,8 +2881,8 @@ static unsigned long __mmap_region(struc
struct vm_area_struct *vma = NULL;
bool have_mmap_prepare = file && file->f_op->mmap_prepare;
VMA_ITERATOR(vmi, mm, addr);
- const pgoff_t virt_pgoff = addr >> PAGE_SHIFT;
- MMAP_STATE(map, mm, &vmi, addr, len, pgoff, virt_pgoff, vma_flags, file);
+ const pgoff_t anon_pgoff = addr >> PAGE_SHIFT;
+ MMAP_STATE(map, mm, &vmi, addr, len, pgoff, anon_pgoff, vma_flags, file);
struct vm_area_desc desc = {
.mm = mm,
.file = file,
@@ -3452,7 +3464,7 @@ int insert_vm_struct(struct mm_struct *m
WARN_ON_ONCE(vma->anon_vma);
vma_set_pgoff(vma, vma->vm_start >> PAGE_SHIFT);
}
- vma_set_virt_pgoff(vma, vma->vm_start >> PAGE_SHIFT);
+ vma_set_anon_pgoff(vma, vma->vm_start >> PAGE_SHIFT);
if (vma_link(mm, vma)) {
if (vma_test(vma, VMA_ACCOUNT_BIT))
--- a/mm/vma.h~b
+++ a/mm/vma.h
@@ -291,86 +291,32 @@ static inline pgoff_t vmg_end_anon_pgoff
return vmg_start_anon_pgoff(vmg) + vmg_pages(vmg);
}
-static inline void __vma_set_virt_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
+static inline void __vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
{
#ifdef CONFIG_64BIT
- vma->__vm_virt_pgoff_hi = pgoff >> 32;
+ vma->__vm_anon_pgoff_hi = pgoff >> 32;
#endif
- vma->__vm_virt_pgoff_lo = pgoff & GENMASK(31, 0);
+ vma->__vm_anon_pgoff_lo = pgoff & GENMASK(31, 0);
}
-static inline void vma_set_virt_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
+static inline void vma_set_anon_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
{
vma_assert_can_modify(vma);
- __vma_set_virt_pgoff(vma, pgoff);
+ __vma_set_anon_pgoff(vma, pgoff);
}
static inline void vma_add_pgoff(struct vm_area_struct *vma, pgoff_t delta)
{
vma_assert_can_modify(vma);
vma_set_pgoff(vma, vma_start_pgoff(vma) + delta);
- vma_set_virt_pgoff(vma, vma_start_virt_pgoff(vma) + delta);
+ vma_set_anon_pgoff(vma, vma_start_anon_pgoff(vma) + delta);
}
static inline void vma_sub_pgoff(struct vm_area_struct *vma, pgoff_t delta)
{
vma_assert_can_modify(vma);
vma_set_pgoff(vma, vma_start_pgoff(vma) - delta);
- vma_set_virt_pgoff(vma, vma_start_virt_pgoff(vma) - delta);
-}
-
-/**
- * vma_anon_pgoff_addr() - Calculates the absolute anonymous page offset of
- * @address.
- * @vma: The VMA whose anonymous page offset is required.
- * @address: The address whose absolute page offset is required.
- *
- * If the VMA is a shared file-backed mapping, then the file-based page offset
- * is returned.
- *
- * Otherwise, the virtual page offset is returned.
- *
- * This means that shared file-backed mappings are correctly merged based on
- * their file page offset compatibility.
- *
- * Returns: The absolute anonymous page offset of @address within @vma.
- */
-static inline pgoff_t vma_anon_pgoff_addr(const struct vm_area_struct *vma,
- unsigned long address)
-{
- if (vma_test(vma, VMA_SHARED_BIT))
- return linear_page_index(vma, address);
-
- return linear_virt_page_index(vma, address);
-}
-
-/**
- * vma_start_anon_pgoff() - Calculates the absolute anonymous page offset used
- * for purposes of merge compatibility.
- * @vma: The VMA whose anonymous page offset is required.
- *
- * See vma_anon_pgoff_addr().
- *
- * Returns: The absolute anonymous page offset of @vma for purposes of merging.
- */
-static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma)
-{
- return vma_anon_pgoff_addr(vma, vma->vm_start);
-}
-
-/**
- * vma_end_anon_pgoff() - Calculates the absolute exclusive end anonymous page
- * offset used for purposes of merge compatibility.
- * @vma: The VMA whose anonymous end page offset is required.
- *
- * See vma_start_anon_pgoff().
- *
- * Returns: The absolute exclusive end anonymous page offset of @vma for
- * purposes of merging.
- */
-static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma)
-{
- return vma_start_anon_pgoff(vma) + vma_pages(vma);
+ vma_set_anon_pgoff(vma, vma_start_anon_pgoff(vma) - delta);
}
#define VMG_STATE(name, mm_, vmi_, start_, end_, vma_flags_, pgoff_, anon_pgoff_) \
@@ -396,7 +342,7 @@ static inline pgoff_t vma_end_anon_pgoff
.end = end_, \
.vm_flags = vma_->vm_flags, \
.pgoff = linear_page_index(vma_, start_), \
- .anon_pgoff = vma_anon_pgoff_addr(vma_, start_), \
+ .anon_pgoff = __linear_anon_page_index(vma_, start_), \
.file = vma_->vm_file, \
.anon_vma = vma_->anon_vma, \
.policy = vma_policy(vma_), \
--- a/mm/vma_init.c~b
+++ a/mm/vma_init.c
@@ -51,7 +51,7 @@ static void vm_area_init_from(const stru
dest->vm_end = src->vm_end;
dest->anon_vma = src->anon_vma;
dest->vm_pgoff = vma_start_pgoff(src);
- __vma_set_virt_pgoff(dest, vma_start_virt_pgoff(src));
+ __vma_set_anon_pgoff(dest, vma_start_anon_pgoff(src));
dest->vm_file = src->vm_file;
dest->vm_private_data = src->vm_private_data;
vm_flags_init(dest, src->vm_flags);
--- a/tools/testing/selftests/mm/merge.c~b
+++ a/tools/testing/selftests/mm/merge.c
@@ -1305,7 +1305,7 @@ TEST_F(merge, merge_vmas_with_mseal)
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 2 * page_size);
}
-TEST_F(merge, virt_and_page_offset_mismatch_memfd)
+TEST_F(merge, anon_and_page_offset_mismatch_memfd)
{
struct procmap_fd *procmap = &self->procmap;
unsigned int page_size = self->page_size;
@@ -1314,7 +1314,7 @@ TEST_F(merge, virt_and_page_offset_misma
int fd;
/* Create a 10 page memfd descriptor. */
- fd = memfd_create("virt_page_offset_test", MFD_CLOEXEC);
+ fd = memfd_create("anon_page_offset_test", MFD_CLOEXEC);
ASSERT_NE(fd, -1);
ASSERT_EQ(ftruncate(fd, 10 * page_size), 0);
@@ -1346,10 +1346,10 @@ TEST_F(merge, virt_and_page_offset_misma
* | unfaulted | | faulted |
* |-----------| |---------|
*
- * Because virtual page offset of the faulted region is now
+ * Because the anonymous page offset of the faulted region is now
* &carveout[10 * page_size], despite the two regions being mergeable
- * due to file page offset, they are NOT mergeable due to virtual page
- * offset.
+ * due to file page offset, they are NOT mergeable due to anonymous
+ * page offset.
*/
ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size,
MREMAP_MAYMOVE | MREMAP_FIXED,
--- 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("/dev/zero", O_RDONLY);
+ fd = open("/proc/self/exe", 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("/dev/zero", O_RDONLY);
+ fd = open("/proc/self/exe", O_RDONLY);
if (fd == -1)
return 1;
--- a/tools/testing/vma/include/dup.h~b
+++ a/tools/testing/vma/include/dup.h
@@ -599,7 +599,7 @@ struct vm_area_struct {
*/
unsigned int vm_lock_seq;
#endif
- unsigned int __vm_virt_pgoff_lo;
+ unsigned int __vm_anon_pgoff_lo;
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
@@ -637,7 +637,7 @@ struct vm_area_struct {
refcount_t vm_refcnt;
#endif
#ifdef CONFIG_64BIT
- unsigned int __vm_virt_pgoff_hi;
+ unsigned int __vm_anon_pgoff_hi;
#endif
/*
* For areas with an address space and backing store,
@@ -1184,6 +1184,17 @@ static inline bool vma_is_shared_maywrit
return is_shared_maywrite(&vma->flags);
}
+static inline bool vma_flags_is_cow_mapping(const vma_flags_t *flags)
+{
+ return vma_flags_test(flags, VMA_MAYWRITE_BIT) &&
+ !vma_flags_test(flags, VMA_SHARED_BIT);
+}
+
+static inline bool vma_is_cow_mapping(const struct vm_area_struct *vma)
+{
+ return vma_flags_is_cow_mapping(&vma->flags);
+}
+
static inline struct vm_area_struct *vma_next(struct vma_iterator *vmi)
{
/*
@@ -1346,26 +1357,26 @@ static inline pgoff_t vma_end_pgoff(cons
return vma_start_pgoff(vma) + vma_pages(vma);
}
-static inline pgoff_t vma_start_virt_pgoff(const struct vm_area_struct *vma)
+static inline pgoff_t vma_start_anon_pgoff(const struct vm_area_struct *vma)
{
pgoff_t pgoff = 0;
#ifdef CONFIG_64BIT
- pgoff += vma->__vm_virt_pgoff_hi;
+ pgoff += vma->__vm_anon_pgoff_hi;
pgoff <<= 32;
#endif
- pgoff += vma->__vm_virt_pgoff_lo;
+ pgoff += vma->__vm_anon_pgoff_lo;
return pgoff;
}
-static inline pgoff_t vma_end_virt_pgoff(const struct vm_area_struct *vma)
+static inline pgoff_t vma_end_anon_pgoff(const struct vm_area_struct *vma)
{
- return vma_start_virt_pgoff(vma) + vma_pages(vma);
+ return vma_start_anon_pgoff(vma) + vma_pages(vma);
}
-static inline pgoff_t vma_last_virt_pgoff(const struct vm_area_struct *vma)
+static inline pgoff_t vma_last_anon_pgoff(const struct vm_area_struct *vma)
{
- return vma_end_virt_pgoff(vma) - 1;
+ return vma_end_anon_pgoff(vma) - 1;
}
static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
@@ -1633,22 +1644,22 @@ static inline pgprot_t vma_get_page_prot
return vma_flags_to_page_prot(vma->flags);
}
-static inline pgoff_t __linear_virt_page_index(const struct vm_area_struct *vma,
+static inline pgoff_t __linear_anon_page_index(const struct vm_area_struct *vma,
const unsigned long address)
{
pgoff_t pgoff;
pgoff = linear_page_delta(vma, address);
- pgoff += vma_start_virt_pgoff(vma);
+ pgoff += vma_start_anon_pgoff(vma);
return pgoff;
}
-static inline pgoff_t linear_virt_page_index(const struct vm_area_struct *vma,
- const unsigned long address)
+static inline pgoff_t linear_anon_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
{
- const pgoff_t pgoff = __linear_virt_page_index(vma, address);
+ const pgoff_t pgoff = __linear_anon_page_index(vma, address);
- VM_WARN_ON_ONCE(vma_test(vma, VMA_SHARED_BIT));
+ VM_WARN_ON_ONCE(!vma_is_cow_mapping(vma));
if (vma_is_anonymous(vma))
VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
--- a/tools/testing/vma/shared.c~b
+++ a/tools/testing/vma/shared.c
@@ -24,7 +24,7 @@ struct vm_area_struct *alloc_vma(struct
vma->vm_start = start;
vma->vm_end = end;
vma_set_pgoff(vma, pgoff);
- vma_set_virt_pgoff(vma, start >> PAGE_SHIFT);
+ vma_set_anon_pgoff(vma, start >> PAGE_SHIFT);
vma->flags = vma_flags;
vma_assert_detached(vma);
--- a/tools/testing/vma/tests/merge.c~b
+++ a/tools/testing/vma/tests/merge.c
@@ -121,7 +121,7 @@ static bool test_simple_merge(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_FLAGS_SAME_MASK(&vma->flags, vma_flags);
detach_free_vma(vma);
@@ -154,7 +154,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
ASSERT_EQ(vma_start_pgoff(vma), 1);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 1);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 1);
/*
* Now walk through the three split VMAs and make sure they are as
@@ -167,7 +167,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -177,7 +177,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
ASSERT_EQ(vma_start_pgoff(vma), 1);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 1);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 1);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -187,7 +187,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 2);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 2);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -217,7 +217,7 @@ static bool test_simple_expand(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -240,7 +240,7 @@ static bool test_simple_shrink(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -353,7 +353,7 @@ static bool __test_merge_new(bool is_sti
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -375,7 +375,7 @@ static bool __test_merge_new(bool is_sti
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 6);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 6);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 6);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -396,7 +396,7 @@ static bool __test_merge_new(bool is_sti
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -417,7 +417,7 @@ static bool __test_merge_new(bool is_sti
ASSERT_EQ(vma->vm_start, 0xa000);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0xa);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0xa);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0xa);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -437,7 +437,7 @@ static bool __test_merge_new(bool is_sti
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -458,7 +458,7 @@ static bool __test_merge_new(bool is_sti
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
detach_free_vma(vma);
@@ -655,7 +655,8 @@ static bool test_vma_merge_with_close(vo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x5000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(cleanup_mm(&mm, &vmi), 2);
@@ -766,7 +767,8 @@ static bool test_vma_merge_with_close(vo
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x5000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(cleanup_mm(&mm, &vmi), 2);
@@ -821,7 +823,7 @@ static bool test_vma_merge_new_with_clos
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_EQ(vma->vm_ops, &vm_ops);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -878,12 +880,12 @@ static bool __test_merge_existing(bool p
ASSERT_EQ(vma_next->vm_start, 0x3000);
ASSERT_EQ(vma_next->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma_next), 3);
- ASSERT_EQ(vma_start_virt_pgoff(vma_next), 3);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_next), 3);
ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
ASSERT_EQ(vma_start_pgoff(vma), 2);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 2);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_TRUE(vma_write_started(vma_next));
ASSERT_EQ(mm.map_count, 2);
@@ -913,7 +915,8 @@ static bool __test_merge_existing(bool p
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_next->vm_start, 0x2000);
ASSERT_EQ(vma_next->vm_end, 0x9000);
- ASSERT_EQ(vma_next->vm_pgoff, 2);
+ ASSERT_EQ(vma_start_pgoff(vma_next), 2);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_next), 2);
ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_next));
ASSERT_EQ(mm.map_count, 1);
@@ -946,12 +949,12 @@ static bool __test_merge_existing(bool p
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x6000);
ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x7000);
ASSERT_EQ(vma_start_pgoff(vma), 6);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 6);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 6);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -983,7 +986,7 @@ static bool __test_merge_existing(bool p
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_EQ(mm.map_count, 1);
@@ -1016,7 +1019,7 @@ static bool __test_merge_existing(bool p
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_EQ(mm.map_count, 1);
@@ -1146,7 +1149,8 @@ static bool test_anon_vma_non_mergeable(
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_FALSE(vma_write_started(vma_next));
@@ -1177,7 +1181,8 @@ static bool test_anon_vma_non_mergeable(
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma_prev->vm_start, 0);
ASSERT_EQ(vma_prev->vm_end, 0x7000);
- ASSERT_EQ(vma_prev->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma_prev), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma_prev), 0);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_FALSE(vma_write_started(vma_next));
@@ -1439,7 +1444,7 @@ static bool test_merge_extend(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x4000);
ASSERT_EQ(vma_start_pgoff(vma), 0);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -1480,7 +1485,7 @@ static bool test_expand_only_mode(void)
ASSERT_EQ(vma->vm_start, 0x3000);
ASSERT_EQ(vma->vm_end, 0x9000);
ASSERT_EQ(vma_start_pgoff(vma), 3);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 3);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 3);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(vma_iter_addr(&vmi), 0x3000);
vma_assert_attached(vma);
--- a/tools/testing/vma/tests/mmap.c~b
+++ a/tools/testing/vma/tests/mmap.c
@@ -74,7 +74,7 @@ static bool test_pure_anon_dev_zero(void
/*
* Map a MAP_PRIVATE-/dev/zero mapping at address 0x300000 with a page
- * offset of 0x10, which we expect to be reset to the virtual 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);
@@ -86,9 +86,9 @@ static bool test_pure_anon_dev_zero(void
ASSERT_TRUE(vma_is_anonymous(vma));
ASSERT_EQ(vma->vm_file, NULL);
ASSERT_EQ(vma->vm_private_data, NULL);
- /* Expect virtual page offsets. */
+ /* Expect anonymous page offsets. */
ASSERT_EQ(vma->vm_pgoff, 0x300);
- ASSERT_EQ(vma_start_virt_pgoff(vma), 0x300);
+ ASSERT_EQ(vma_start_anon_pgoff(vma), 0x300);
cleanup_mm(&mm, &vmi);
return true;
--- a/tools/testing/vma/tests/vma.c~b
+++ a/tools/testing/vma/tests/vma.c
@@ -33,7 +33,51 @@ static bool test_copy_vma(void)
struct mm_struct mm = {};
bool need_locks = false;
VMA_ITERATOR(vmi, &mm, 0);
- struct vm_area_struct *vma, *vma_new, *vma_next;
+ struct vm_area_struct *vma, *vma_prev, *vma_new, *vma_next, *vma_orig;
+
+ /* Move forwards, adjacent to old self - self-merge. */
+
+ vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks);
+ ASSERT_EQ(vma_new, vma_orig);
+ ASSERT_EQ(vma, vma_orig);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x3000);
+
+ cleanup_mm(&mm, &vmi);
+
+ /* Move backwards, adjacent to old self - self-merge. */
+
+ vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks);
+ ASSERT_EQ(vma_new, vma_orig);
+ ASSERT_EQ(vma, vma_orig);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x3000);
+
+ cleanup_mm(&mm, &vmi);
+
+ /*
+ * Move backwards between prior VMA and old self - self-merge and vma
+ * updated to a new VMA.
+ */
+
+ vma_prev = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags);
+ vma_set_anonymous(vma_prev);
+ vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags);
+ vma_set_anonymous(vma);
+ vma_orig = vma;
+ vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks);
+ ASSERT_NE(vma_new, vma_orig);
+ ASSERT_EQ(vma_new, vma);
+ ASSERT_EQ(vma_new->vm_start, 0x1000);
+ ASSERT_EQ(vma_new->vm_end, 0x4000);
+
+ cleanup_mm(&mm, &vmi);
/* Move backwards and do not merge. */
_
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-08-06 23:27 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06 20:21 [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 01/20] mm/vma: introduce VMA anon page offset field and add helpers Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 02/20] mm: provide vma_[flags_]is_cow_mapping() and remove is_cow_mapping() Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 03/20] mm: introduce linear_anon_page_index() Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 04/20] mm: abstract vma_address() and introduce vma_anon_address() Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 05/20] mm: update print_bad_page_map() to show anon index if appropriate Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 06/20] mm: introduce and use vma_filebacked_address() Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 07/20] mm/vma: fix self-merge check in copy_vma() Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 08/20] tools/testing/vma: add tests for copy_vma() self-merge Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 09/20] mm: propagate VMA anonymous page offset on map, remap, split + merge Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 10/20] mm/rmap: track whether the page VMA mapped pgoff is anonymous Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 11/20] mm: clean up vma_address_end() Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 12/20] mm/huge_memory: update remove_migration_pmd() to accept a folio Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 13/20] mm/migrate: calculate large folio page index using PFN Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 14/20] mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 15/20] tools/testing/vma: expand VMA merge tests to assert anon pgoff Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 16/20] tools/testing/selftests/mm: test anonymous page offset merge behaviour Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 17/20] mm/vma: only permit MAP_PRIVATE /dev/zero to be mapped anonymous Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 18/20] mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 19/20] tools/testing/vma: add test to assert MAP_PRIVATE-/dev/zero is anon Lorenzo Stoakes (ARM)
2026-08-06 20:21 ` [PATCH v4 20/20] tools/testing/selftests/mm: add MAP_PRIVATE-/dev/zero merge tests Lorenzo Stoakes (ARM)
2026-08-06 23:27 ` [PATCH v4 00/20] mm/rmap: index MAP_PRIVATE file-backed folios by anonymous pgoff Andrew Morton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox