]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
treewide: provide a generic clear_user_page() variant
authorDavid Hildenbrand <david@redhat.com>
Wed, 7 Jan 2026 07:20:02 +0000 (23:20 -0800)
committerAndrew Morton <akpm@linux-foundation.org>
Wed, 21 Jan 2026 03:24:39 +0000 (19:24 -0800)
Patch series "mm: folio_zero_user: clear page ranges", v11.

This series adds clearing of contiguous page ranges for hugepages.

The series improves on the current discontiguous clearing approach in two
ways:

  - clear pages in a contiguous fashion.
  - use batched clearing via clear_pages() wherever exposed.

The first is useful because it allows us to make much better use of
hardware prefetchers.

The second, enables advertising the real extent to the processor.  Where
specific instructions support it (ex.  string instructions on x86; "mops"
on arm64 etc), a processor can optimize based on this because, instead of
seeing a sequence of 8-byte stores, or a sequence of 4KB pages, it sees a
larger unit being operated on.

For instance, AMD Zen uarchs (for extents larger than LLC-size) switch to
a mode where they start eliding cacheline allocation.  This is helpful not
just because it results in higher bandwidth, but also because now the
cache is not evicting useful cachelines and replacing them with zeroes.

Demand faulting a 64GB region shows performance improvement:

 $ perf bench mem mmap -p $pg-sz -f demand -s 64GB -l 5

                       baseline              +series
                   (GBps +- %stdev)      (GBps +- %stdev)

   pg-sz=2MB       11.76 +- 1.10%        25.34 +- 1.18% [*]   +115.47%   preempt=*

   pg-sz=1GB       24.85 +- 2.41%        39.22 +- 2.32%       + 57.82%   preempt=none|voluntary
   pg-sz=1GB         (similar)           52.73 +- 0.20% [#]   +112.19%   preempt=full|lazy

 [*] This improvement is because switching to sequential clearing
  allows the hardware prefetchers to do a much better job.

 [#] For pg-sz=1GB a large part of the improvement is because of the
  cacheline elision mentioned above. preempt=full|lazy improves upon
  that because, not needing explicit invocations of cond_resched() to
  ensure reasonable preemption latency, it can clear the full extent
  as a single unit. In comparison the maximum extent used for
  preempt=none|voluntary is PROCESS_PAGES_NON_PREEMPT_BATCH (32MB).

  When provided the full extent the processor forgoes allocating
  cachelines on this path almost entirely.

  (The hope is that eventually, in the fullness of time, the lazy
   preemption model will be able to do the same job that none or
   voluntary models are used for, allowing us to do away with
   cond_resched().)

Raghavendra also tested previous version of the series on AMD Genoa and
sees similar improvement [1] with preempt=lazy.

  $ perf bench mem map -p $page-size -f populate -s 64GB -l 10

                    base               patched              change
   pg-sz=2MB       12.731939 GB/sec    26.304263 GB/sec     106.6%
   pg-sz=1GB       26.232423 GB/sec    61.174836 GB/sec     133.2%

This patch (of 8):

Let's drop all variants that effectively map to clear_page() and provide
it in a generic variant instead.

We'll use the macro clear_user_page to indicate whether an architecture
provides it's own variant.

Also, clear_user_page() is only called from the generic variant of
clear_user_highpage(), so define it only if the architecture does not
provide a clear_user_highpage().  And, for simplicity define it in
linux/highmem.h.

Note that for parisc, clear_page() and clear_user_page() map to
clear_page_asm(), so we can just get rid of the custom clear_user_page()
implementation.  There is a clear_user_page_asm() function on parisc, that
seems to be unused.  Not sure what's up with that.

Link: https://lkml.kernel.org/r/20260107072009.1615991-1-ankur.a.arora@oracle.com
Link: https://lkml.kernel.org/r/20260107072009.1615991-2-ankur.a.arora@oracle.com
Signed-off-by: David Hildenbrand <david@redhat.com>
Co-developed-by: Ankur Arora <ankur.a.arora@oracle.com>
Signed-off-by: Ankur Arora <ankur.a.arora@oracle.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Ankur Arora <ankur.a.arora@oracle.com>
Cc: "Borislav Petkov (AMD)" <bp@alien8.de>
Cc: Boris Ostrovsky <boris.ostrovsky@oracle.com>
Cc: David Hildenbrand <david@kernel.org>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Konrad Rzessutek Wilk <konrad.wilk@oracle.com>
Cc: Lance Yang <ioworker0@gmail.com>
Cc: "Liam R. Howlett" <Liam.Howlett@oracle.com>
Cc: Li Zhe <lizhe.67@bytedance.com>
Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Mateusz Guzik <mjguzik@gmail.com>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Raghavendra K T <raghavendra.kt@amd.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
22 files changed:
arch/alpha/include/asm/page.h
arch/arc/include/asm/page.h
arch/arm/include/asm/page-nommu.h
arch/arm64/include/asm/page.h
arch/csky/abiv1/inc/abi/page.h
arch/csky/abiv2/inc/abi/page.h
arch/hexagon/include/asm/page.h
arch/loongarch/include/asm/page.h
arch/m68k/include/asm/page_no.h
arch/microblaze/include/asm/page.h
arch/mips/include/asm/page.h
arch/nios2/include/asm/page.h
arch/openrisc/include/asm/page.h
arch/parisc/include/asm/page.h
arch/powerpc/include/asm/page.h
arch/riscv/include/asm/page.h
arch/s390/include/asm/page.h
arch/sparc/include/asm/page_64.h
arch/um/include/asm/page.h
arch/x86/include/asm/page.h
arch/xtensa/include/asm/page.h
include/linux/highmem.h

index d2c6667d73e9ee5bd1ed1aea37908a13de7e70b7..59d01f9b77f6c6d5cccc46fb1d436c285dea0768 100644 (file)
@@ -11,7 +11,6 @@
 #define STRICT_MM_TYPECHECKS
 
 extern void clear_page(void *page);
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 
 #define vma_alloc_zeroed_movable_folio(vma, vaddr) \
        vma_alloc_folio(GFP_HIGHUSER_MOVABLE | __GFP_ZERO, 0, vma, vaddr)
index 9720fe6b2c24577ef1a12aab1184b957c72d8a20..38214e126c6de96b89bf9f63d8df6def5825b441 100644 (file)
@@ -32,6 +32,8 @@ struct page;
 
 void copy_user_highpage(struct page *to, struct page *from,
                        unsigned long u_vaddr, struct vm_area_struct *vma);
+
+#define clear_user_page clear_user_page
 void clear_user_page(void *to, unsigned long u_vaddr, struct page *page);
 
 typedef struct {
index 7c2c72323d1761198bb4afbdbb4b8db054a98237..e74415c959beac2543ed9dc88237568ec491279d 100644 (file)
@@ -11,7 +11,6 @@
 #define clear_page(page)       memset((page), 0, PAGE_SIZE)
 #define copy_page(to,from)     memcpy((to), (from), PAGE_SIZE)
 
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)    copy_page(to, from)
 
 /*
index 00f117ff4f7a2539d451ad95605b6a6cd8e903fb..b39cc1127e1f1f027331dd982ec73397a66c2faf 100644 (file)
@@ -36,7 +36,6 @@ struct folio *vma_alloc_zeroed_movable_folio(struct vm_area_struct *vma,
 bool tag_clear_highpages(struct page *to, int numpages);
 #define __HAVE_ARCH_TAG_CLEAR_HIGHPAGES
 
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)    copy_page(to, from)
 
 typedef struct page *pgtable_t;
index 2d2159933b76624e905efc92663f22c0af06f09e..58307254e7e519e44e4f30c4e5f3243918a9dc00 100644 (file)
@@ -10,6 +10,7 @@ static inline unsigned long pages_do_alias(unsigned long addr1,
        return (addr1 ^ addr2) & (SHMLBA-1);
 }
 
+#define clear_user_page clear_user_page
 static inline void clear_user_page(void *addr, unsigned long vaddr,
                                   struct page *page)
 {
index cf005f13cd15d34ac237f9e71aee7aa25ef9532a..a5a2550133081abe1af992dd5db98d707e2fe285 100644 (file)
@@ -1,11 +1,4 @@
 /* SPDX-License-Identifier: GPL-2.0 */
-
-static inline void clear_user_page(void *addr, unsigned long vaddr,
-                                  struct page *page)
-{
-       clear_page(addr);
-}
-
 static inline void copy_user_page(void *to, void *from, unsigned long vaddr,
                                  struct page *page)
 {
index 137ba7c5de4811638ced0fb1acdcc72badc75412..f0aed3ed812b95333c0f3ec970ed093fc3878fe5 100644 (file)
@@ -113,7 +113,6 @@ static inline void clear_page(void *page)
 /*
  * Under assumption that kernel always "sees" user map...
  */
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)    copy_page(to, from)
 
 static inline unsigned long virt_to_pfn(const void *kaddr)
index 256d1ff7a1e366c62ad48188622ad47b6f383eac..327bf0bc92bf920983fb43b2671e540c0b35d149 100644 (file)
@@ -30,7 +30,6 @@
 extern void clear_page(void *page);
 extern void copy_page(void *to, void *from);
 
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)    copy_page(to, from)
 
 extern unsigned long shm_align_mask;
index 39db2026a4b4c9e1674d3e4d9c529908499f8b82..d2532bc407effa70cb75b0f166567af4838af025 100644 (file)
@@ -10,7 +10,6 @@ extern unsigned long memory_end;
 #define clear_page(page)       memset((page), 0, PAGE_SIZE)
 #define copy_page(to,from)     memcpy((to), (from), PAGE_SIZE)
 
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)    copy_page(to, from)
 
 #define vma_alloc_zeroed_movable_folio(vma, vaddr) \
index 90ac9f34b4b492e5adb8e5d8a0eb544a500fd4d9..e1e396367ba7e9bf39689e81f50b5da7e4dee39f 100644 (file)
@@ -45,7 +45,6 @@ typedef unsigned long pte_basic_t;
 # define copy_page(to, from)                   memcpy((to), (from), PAGE_SIZE)
 # define clear_page(pgaddr)                    memset((pgaddr), 0, PAGE_SIZE)
 
-# define clear_user_page(pgaddr, vaddr, page)  memset((pgaddr), 0, PAGE_SIZE)
 # define copy_user_page(vto, vfrom, vaddr, topg) \
                        memcpy((vto), (vfrom), PAGE_SIZE)
 
index bc3e3484c1bfa9f7e1cfaecf034099a9b84b7b2b..5ec428fcc8877a308ee29d33128da6b2001c35bb 100644 (file)
@@ -90,6 +90,7 @@ static inline void clear_user_page(void *addr, unsigned long vaddr,
        if (pages_do_alias((unsigned long) addr, vaddr & PAGE_MASK))
                flush_data_cache_page((unsigned long)addr);
 }
+#define clear_user_page clear_user_page
 
 struct vm_area_struct;
 extern void copy_user_highpage(struct page *to, struct page *from,
index 00a51623d38a545a4790cc8b2e6a532c72d76bfa..722956ac0bf8744a7491624cac024f2d1bcef6c6 100644 (file)
@@ -45,6 +45,7 @@
 
 struct page;
 
+#define clear_user_page clear_user_page
 extern void clear_user_page(void *addr, unsigned long vaddr, struct page *page);
 extern void copy_user_page(void *vto, void *vfrom, unsigned long vaddr,
                                struct page *to);
index 85797f94d1d74ab8ea01fcbf1d3da8b9a8901cdf..d2cdbf3579bb182b04b57a2d3b95a7880386bb13 100644 (file)
@@ -30,7 +30,6 @@
 #define clear_page(page)       memset((page), 0, PAGE_SIZE)
 #define copy_page(to, from)    memcpy((to), (from), PAGE_SIZE)
 
-#define clear_user_page(page, vaddr, pg)        clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)     copy_page(to, from)
 
 /*
index 8f4e51071ea1d8c427597478e4240ca07700ad68..3630b36d07da77dade59ea0827ff2ab7184656cb 100644 (file)
@@ -21,7 +21,6 @@ struct vm_area_struct;
 
 void clear_page_asm(void *page);
 void copy_page_asm(void *to, void *from);
-#define clear_user_page(vto, vaddr, page) clear_page_asm(vto)
 void copy_user_highpage(struct page *to, struct page *from, unsigned long vaddr,
                struct vm_area_struct *vma);
 #define __HAVE_ARCH_COPY_USER_HIGHPAGE
index b28fbb1d57eb90ac844a2d476a5e37f96836a7bf..f2bb1f98eebed8a1c6e422a3809fa8845d15ca37 100644 (file)
@@ -271,6 +271,7 @@ static inline const void *pfn_to_kaddr(unsigned long pfn)
 
 struct page;
 extern void clear_user_page(void *page, unsigned long vaddr, struct page *pg);
+#define clear_user_page clear_user_page
 extern void copy_user_page(void *to, void *from, unsigned long vaddr,
                struct page *p);
 extern int devmem_is_allowed(unsigned long pfn);
index ffe213ad65a4eef1f9d69d55bb436af830cbc727..061b60b954ecb13a95f139b96f9cd12fe6ac161b 100644 (file)
@@ -50,7 +50,6 @@ void clear_page(void *page);
 #endif
 #define copy_page(to, from)                    memcpy((to), (from), PAGE_SIZE)
 
-#define clear_user_page(pgaddr, vaddr, page)   clear_page(pgaddr)
 #define copy_user_page(vto, vfrom, vaddr, topg) \
                        memcpy((vto), (vfrom), PAGE_SIZE)
 
index c1d63b613bf9bf81f780064dfdde79b52f45b171..9c8c5283258e324af557ee19caa7bd8a1ac2a248 100644 (file)
@@ -65,7 +65,6 @@ static inline void copy_page(void *to, void *from)
                : : "memory", "cc");
 }
 
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)    copy_page(to, from)
 
 #define vma_alloc_zeroed_movable_folio(vma, vaddr) \
index d764d8a8586bd936c4c253d0debcb4c65a19e9ed..fd4dc85fb38b715e167152cb8bbdcd339ad7f90b 100644 (file)
@@ -43,6 +43,7 @@ void _clear_page(void *page);
 #define clear_page(X)  _clear_page((void *)(X))
 struct page;
 void clear_user_page(void *addr, unsigned long vaddr, struct page *page);
+#define clear_user_page clear_user_page
 #define copy_page(X,Y) memcpy((void *)(X), (void *)(Y), PAGE_SIZE)
 void copy_user_page(void *to, void *from, unsigned long vaddr, struct page *topage);
 #define __HAVE_ARCH_COPY_USER_HIGHPAGE
index 2d363460d89604cc382277a6f34c12dd55d6c6fb..e348ff489b89bcda95294c7bf869e7bd73fb1b46 100644 (file)
@@ -26,7 +26,6 @@ struct page;
 #define clear_page(page)       memset((void *)(page), 0, PAGE_SIZE)
 #define copy_page(to,from)     memcpy((void *)(to), (void *)(from), PAGE_SIZE)
 
-#define clear_user_page(page, vaddr, pg)       clear_page(page)
 #define copy_user_page(to, from, vaddr, pg)    copy_page(to, from)
 
 typedef struct { unsigned long pte; } pte_t;
index 9265f2fca99ae111365ec2942e95d0a9e28633f9..416dc88e35c150c0458874a788120b4cb9f76045 100644 (file)
@@ -22,12 +22,6 @@ struct page;
 extern struct range pfn_mapped[];
 extern int nr_pfn_mapped;
 
-static inline void clear_user_page(void *page, unsigned long vaddr,
-                                  struct page *pg)
-{
-       clear_page(page);
-}
-
 static inline void copy_user_page(void *to, void *from, unsigned long vaddr,
                                  struct page *topage)
 {
index 20655174b11151a40cc66d9de242e282b803a05c..0594932567654796d6095f32701bdbd512a81ca4 100644 (file)
@@ -126,7 +126,6 @@ void clear_user_highpage(struct page *page, unsigned long vaddr);
 void copy_user_highpage(struct page *to, struct page *from,
                        unsigned long vaddr, struct vm_area_struct *vma);
 #else
-# define clear_user_page(page, vaddr, pg)      clear_page(page)
 # define copy_user_page(to, from, vaddr, pg)   copy_page(to, from)
 #endif
 
index abc20f9810fd4219626d1342a84e628418b294c4..393bd51e5a1f96c976690c34467e5a11d36e11c0 100644 (file)
@@ -197,15 +197,35 @@ static inline void invalidate_kernel_vmap_range(void *vaddr, int size)
 }
 #endif
 
-/* when CONFIG_HIGHMEM is not set these will be plain clear/copy_page */
 #ifndef clear_user_highpage
+#ifndef clear_user_page
+/**
+ * clear_user_page() - clear a page to be mapped to user space
+ * @addr: the address of the page
+ * @vaddr: the address of the user mapping
+ * @page: the page
+ *
+ * We condition the definition of clear_user_page() on the architecture
+ * not having a custom clear_user_highpage(). That's because if there
+ * is some special flushing needed for clear_user_highpage() then it
+ * is likely that clear_user_page() also needs some magic. And, since
+ * our only caller is the generic clear_user_highpage(), not defining
+ * is not much of a loss.
+ */
+static inline void clear_user_page(void *addr, unsigned long vaddr, struct page *page)
+{
+       clear_page(addr);
+}
+#endif
+
+/* when CONFIG_HIGHMEM is not set these will be plain clear/copy_page */
 static inline void clear_user_highpage(struct page *page, unsigned long vaddr)
 {
        void *addr = kmap_local_page(page);
        clear_user_page(addr, vaddr, page);
        kunmap_local(addr);
 }
-#endif
+#endif /* clear_user_highpage */
 
 #ifndef vma_alloc_zeroed_movable_folio
 /**