[PATCH v4 03/20] mm: introduce linear_anon_page_index()
From: Lorenzo Stoakes (ARM)
Date: Thu Aug 06 2026 - 16:24:13 EST
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@xxxxxxxxxx>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@xxxxxxxxxx>
---
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