include/linux/gfp.h | 13 +------------ mm/khugepaged.c | 2 +- mm/migrate.c | 2 +- mm/page_alloc.c | 46 ++++++++++++++++++++++++++++++--------------- mm/page_alloc.h | 8 ++++++++ 5 files changed, 42 insertions(+), 29 deletions(-)
__alloc_pages_noprof() takes an explicit alloc_flags, but the bulk and
folio entry points do not, so callers cannot select allocator behaviour
(e.g. an alternate zonelist) through them.
Thread alloc_flags through both, matching __alloc_pages_noprof(), and
keep the flag-carrying primitives mm-internal (page_alloc.h) so the
public gfp.h wrappers stay flag-free:
- add __alloc_pages_bulk_noprof(gfp, ..., alloc_flags) in page_alloc.h
alloc_pages_bulk_noprof() becomes a wrapper passing ALLOC_DEFAULT
- give __folio_alloc_noprof() an alloc_flags parameter and moves
__folio_alloc_node_noprof() moves into page_alloc.h
__folio_alloc_noprof() is no longer exported
No functional change: every caller passes ALLOC_DEFAULT.
Cc: Brendan Jackman <brendan.jackman@linux.dev>
Signed-off-by: Gregory Price <gourry@gourry.net>
---
Brendan this will conflict mildly with your ALLOC_UNMAPPED work,
because you add an init_alloc_flags call in alloc_pages_bulk_noprof :
https://lore.kernel.org/linux-mm/20260726-page_alloc-unmapped-v3-20-6f5729aa9832@google.com/
I wanted to pull this out ahead to try to deal with the conflict.
include/linux/gfp.h | 13 +------------
mm/khugepaged.c | 2 +-
mm/migrate.c | 2 +-
mm/page_alloc.c | 46 ++++++++++++++++++++++++++++++---------------
mm/page_alloc.h | 8 ++++++++
5 files changed, 42 insertions(+), 29 deletions(-)
diff --git a/include/linux/gfp.h b/include/linux/gfp.h
index 872bc53f32ec..a327e58c313f 100644
--- a/include/linux/gfp.h
+++ b/include/linux/gfp.h
@@ -204,10 +204,6 @@ static inline void arch_free_page(struct page *page, int order) { }
static inline void arch_alloc_page(struct page *page, int order) { }
#endif
-struct folio *__folio_alloc_noprof(gfp_t gfp, unsigned int order, int preferred_nid,
- nodemask_t *nodemask);
-#define __folio_alloc(...) alloc_hooks(__folio_alloc_noprof(__VA_ARGS__))
-
unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
nodemask_t *nodemask, int nr_pages,
struct page **page_array);
@@ -252,14 +248,7 @@ static inline void warn_if_node_offline(int this_node, gfp_t gfp_mask)
dump_stack();
}
-static inline
-struct folio *__folio_alloc_node_noprof(gfp_t gfp, unsigned int order, int nid)
-{
- warn_if_node_offline(nid, gfp);
-
- return __folio_alloc_noprof(gfp, order, nid, NULL);
-}
-
+struct folio *__folio_alloc_node_noprof(gfp_t gfp, unsigned int order, int nid);
#define __folio_alloc_node(...) alloc_hooks(__folio_alloc_node_noprof(__VA_ARGS__))
/*
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 0d8cd44b14c5..431c2e610b39 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1193,7 +1193,7 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru
int node = collapse_find_target_node(cc);
struct folio *folio;
- folio = __folio_alloc(gfp, order, node, &cc->alloc_nmask);
+ folio = __folio_alloc(gfp, order, node, &cc->alloc_nmask, ALLOC_DEFAULT);
if (!folio) {
*foliop = NULL;
count_collapse_event(order, THP_COLLAPSE_ALLOC_FAILED,
diff --git a/mm/migrate.c b/mm/migrate.c
index 7e3a81f06974..8e1c25a6f750 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -2229,7 +2229,7 @@ struct folio *alloc_migration_target(struct folio *src, unsigned long private)
if (is_highmem_idx(zidx) || zidx == ZONE_MOVABLE)
gfp_mask |= __GFP_HIGHMEM;
- return __folio_alloc(gfp_mask, order, nid, mtc->nmask);
+ return __folio_alloc(gfp_mask, order, nid, mtc->nmask, ALLOC_DEFAULT);
}
#ifdef CONFIG_NUMA_MIGRATION
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index d977b8bf7245..f4ceec8cf77e 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -5151,8 +5151,8 @@ static inline bool prepare_alloc_pages(gfp_t gfp_mask, unsigned int order,
* @page_array were set to %NULL on entry, the slots from 0 to the return value
* - 1 will be filled.
*/
-unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
- nodemask_t *nodemask, int nr_pages,
+unsigned long __alloc_pages_bulk_noprof(gfp_t gfp, unsigned int alloc_flags,
+ int preferred_nid, nodemask_t *nodemask, int nr_pages,
struct page **page_array)
{
struct page *page;
@@ -5160,8 +5160,8 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
struct zoneref *z;
struct per_cpu_pages *pcp;
struct list_head *pcp_list;
- struct alloc_context ac;
- unsigned int alloc_flags = ALLOC_WMARK_LOW;
+ struct alloc_context ac = { .alloc_flags = alloc_flags };
+ unsigned int fastpath_alloc_flags = alloc_flags | ALLOC_WMARK_LOW;
int nr_populated = 0, nr_account = 0;
/*
@@ -5202,7 +5202,7 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
/* May set ALLOC_NOFRAGMENT, fragmentation will return 1 page. */
gfp &= gfp_allowed_mask;
gfp = current_gfp_context(gfp);
- if (!prepare_alloc_pages(gfp, 0, preferred_nid, nodemask, &ac, &gfp, &alloc_flags))
+ if (!prepare_alloc_pages(gfp, 0, preferred_nid, nodemask, &ac, &gfp, &fastpath_alloc_flags))
goto out;
/* Find an allowed local zone that meets the low watermark. */
@@ -5210,7 +5210,7 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
for_next_zone_zonelist_nodemask(zone, z, ac.highest_zoneidx, ac.nodemask) {
unsigned long mark;
- if (cpusets_enabled() && (alloc_flags & ALLOC_CPUSET) &&
+ if (cpusets_enabled() && (fastpath_alloc_flags & ALLOC_CPUSET) &&
!__cpuset_zone_allowed(zone, gfp)) {
continue;
}
@@ -5220,16 +5220,17 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
goto failed;
}
- cond_accept_memory(zone, 0, alloc_flags);
+ cond_accept_memory(zone, 0, fastpath_alloc_flags);
retry_this_zone:
- mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK) + nr_pages - nr_populated;
+ mark = wmark_pages(zone, fastpath_alloc_flags & ALLOC_WMARK_MASK) +
+ nr_pages - nr_populated;
if (zone_watermark_fast(zone, 0, mark,
zonelist_zone_idx(ac.preferred_zoneref),
- alloc_flags, gfp)) {
+ fastpath_alloc_flags, gfp)) {
break;
}
- if (cond_accept_memory(zone, 0, alloc_flags))
+ if (cond_accept_memory(zone, 0, fastpath_alloc_flags))
goto retry_this_zone;
/* Try again if zone has deferred pages */
@@ -5261,7 +5262,7 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
continue;
}
- page = __rmqueue_pcplist(zone, 0, ac.migratetype, alloc_flags,
+ page = __rmqueue_pcplist(zone, 0, ac.migratetype, fastpath_alloc_flags,
pcp, pcp_list);
if (unlikely(!page)) {
/* Try and allocate at least one page */
@@ -5289,11 +5290,19 @@ unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
return nr_populated;
failed:
- page = __alloc_pages_noprof(gfp, 0, preferred_nid, nodemask, ALLOC_DEFAULT);
+ page = __alloc_pages_noprof(gfp, 0, preferred_nid, nodemask, alloc_flags);
if (page)
page_array[nr_populated++] = page;
goto out;
}
+
+unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
+ nodemask_t *nodemask, int nr_pages,
+ struct page **page_array)
+{
+ return __alloc_pages_bulk_noprof(gfp, ALLOC_DEFAULT, preferred_nid,
+ nodemask, nr_pages, page_array);
+}
EXPORT_SYMBOL_GPL(alloc_pages_bulk_noprof);
/*
@@ -5484,13 +5493,20 @@ struct page *alloc_pages_node_noprof(int nid, gfp_t gfp_mask, unsigned int order
EXPORT_SYMBOL(alloc_pages_node_noprof);
struct folio *__folio_alloc_noprof(gfp_t gfp, unsigned int order, int preferred_nid,
- nodemask_t *nodemask)
+ nodemask_t *nodemask, unsigned int alloc_flags)
{
struct page *page = __alloc_pages_noprof(gfp | __GFP_COMP, order,
- preferred_nid, nodemask, ALLOC_DEFAULT);
+ preferred_nid, nodemask, alloc_flags);
return page_rmappable_folio(page);
}
-EXPORT_SYMBOL(__folio_alloc_noprof);
+
+struct folio *__folio_alloc_node_noprof(gfp_t gfp, unsigned int order, int nid)
+{
+ warn_if_node_offline(nid, gfp);
+
+ return __folio_alloc_noprof(gfp, order, nid, NULL, ALLOC_DEFAULT);
+}
+EXPORT_SYMBOL(__folio_alloc_node_noprof);
/*
* Common helper functions. Never use with __GFP_HIGHMEM because the returned
diff --git a/mm/page_alloc.h b/mm/page_alloc.h
index b9259deddb59..23fc79ce97b6 100644
--- a/mm/page_alloc.h
+++ b/mm/page_alloc.h
@@ -258,6 +258,14 @@ struct page *__alloc_pages_noprof(gfp_t gfp, unsigned int order, int preferred_n
nodemask_t *nodemask, unsigned int alloc_flags);
#define __alloc_pages(...) alloc_hooks(__alloc_pages_noprof(__VA_ARGS__))
+struct folio *__folio_alloc_noprof(gfp_t gfp, unsigned int order, int preferred_nid,
+ nodemask_t *nodemask, unsigned int alloc_flags);
+#define __folio_alloc(...) alloc_hooks(__folio_alloc_noprof(__VA_ARGS__))
+
+unsigned long __alloc_pages_bulk_noprof(gfp_t gfp, unsigned int alloc_flags,
+ int preferred_nid, nodemask_t *nodemask, int nr_pages,
+ struct page **page_array);
+
extern void zone_pcp_reset(struct zone *zone);
extern void zone_pcp_disable(struct zone *zone);
extern void zone_pcp_enable(struct zone *zone);
--
2.55.0
On Mon, Sep 14, 2026 at 11:51:14AM -0400, Gregory Price wrote: >__alloc_pages_noprof() takes an explicit alloc_flags, but the bulk and >folio entry points do not, so callers cannot select allocator behaviour >(e.g. an alternate zonelist) through them. > >Thread alloc_flags through both, matching __alloc_pages_noprof(), and >keep the flag-carrying primitives mm-internal (page_alloc.h) so the >public gfp.h wrappers stay flag-free: > > - add __alloc_pages_bulk_noprof(gfp, ..., alloc_flags) in page_alloc.h > alloc_pages_bulk_noprof() becomes a wrapper passing ALLOC_DEFAULT > > - give __folio_alloc_noprof() an alloc_flags parameter and moves > __folio_alloc_node_noprof() moves into page_alloc.h > __folio_alloc_noprof() is no longer exported > >No functional change: every caller passes ALLOC_DEFAULT. Yeah, but what if a caller passes ALLOC_NOLOCK in the future? __alloc_pages_noprof() checks alloc_nolock_allowed() first, but __alloc_pages_bulk_noprof() can enter its fast path without that check. That fast path can reach _deferred_grow_zone() or pcp_spin_trylock(). Shouldn't we do the same check first? Or am I missing something? Cheers, Lance
On Tue, Sep 22, 2026 at 09:05:58PM +0800, Lance Yang wrote: > > On Mon, Sep 14, 2026 at 11:51:14AM -0400, Gregory Price wrote: > >__alloc_pages_noprof() takes an explicit alloc_flags, but the bulk and > >folio entry points do not, so callers cannot select allocator behaviour > >(e.g. an alternate zonelist) through them. > > > >Thread alloc_flags through both, matching __alloc_pages_noprof(), and > >keep the flag-carrying primitives mm-internal (page_alloc.h) so the > >public gfp.h wrappers stay flag-free: > > > > - add __alloc_pages_bulk_noprof(gfp, ..., alloc_flags) in page_alloc.h > > alloc_pages_bulk_noprof() becomes a wrapper passing ALLOC_DEFAULT > > > > - give __folio_alloc_noprof() an alloc_flags parameter and moves > > __folio_alloc_node_noprof() moves into page_alloc.h > > __folio_alloc_noprof() is no longer exported > > > >No functional change: every caller passes ALLOC_DEFAULT. > > Yeah, but what if a caller passes ALLOC_NOLOCK in the future? > > __alloc_pages_noprof() checks alloc_nolock_allowed() first, but > __alloc_pages_bulk_noprof() can enter its fast path without that check. > > That fast path can reach _deferred_grow_zone() or pcp_spin_trylock(). > Shouldn't we do the same check first? > > Or am I missing something? > > Cheers, Lance There is some concern around NOLOCK here yes, in fact sashiko picked this issue up and I've been poking at it. I've actually been reworking this patch and pulled in changes from the ALLOC_UNMAPPED series to address this all at once. I've been developing a page allocator unit-testing harness to help validate some assumptions before I post it. ~Gregory
On 9/23/26 03:59, Gregory Price wrote: > On Tue, Sep 22, 2026 at 09:05:58PM +0800, Lance Yang wrote: >> >> On Mon, Sep 14, 2026 at 11:51:14AM -0400, Gregory Price wrote: >> >__alloc_pages_noprof() takes an explicit alloc_flags, but the bulk and >> >folio entry points do not, so callers cannot select allocator behaviour >> >(e.g. an alternate zonelist) through them. >> > >> >Thread alloc_flags through both, matching __alloc_pages_noprof(), and >> >keep the flag-carrying primitives mm-internal (page_alloc.h) so the >> >public gfp.h wrappers stay flag-free: >> > >> > - add __alloc_pages_bulk_noprof(gfp, ..., alloc_flags) in page_alloc.h >> > alloc_pages_bulk_noprof() becomes a wrapper passing ALLOC_DEFAULT >> > >> > - give __folio_alloc_noprof() an alloc_flags parameter and moves >> > __folio_alloc_node_noprof() moves into page_alloc.h >> > __folio_alloc_noprof() is no longer exported >> > >> >No functional change: every caller passes ALLOC_DEFAULT. >> >> Yeah, but what if a caller passes ALLOC_NOLOCK in the future? >> >> __alloc_pages_noprof() checks alloc_nolock_allowed() first, but >> __alloc_pages_bulk_noprof() can enter its fast path without that check. >> >> That fast path can reach _deferred_grow_zone() or pcp_spin_trylock(). >> Shouldn't we do the same check first? >> >> Or am I missing something? >> >> Cheers, Lance > > There is some concern around NOLOCK here yes, in fact sashiko picked > this issue up and I've been poking at it. It's possible to document it as unsupported, add a VM_WARN_ON_ONCE etc. It's not necessary for bulk alloc to support ALLOC_NOLOCK just because it takes flags. e.g. kmalloc_nolock() supports a subset of gfp flags as well. > I've actually been reworking this patch and pulled in changes from the > ALLOC_UNMAPPED series to address this all at once. I've been developing > a page allocator unit-testing harness to help validate some assumptions > before I post it. > > ~Gregory
On Wed, Sep 23, 2026 at 08:32:29AM +0200, Vlastimil Babka (SUSE) wrote: > On 9/23/26 03:59, Gregory Price wrote: > > On Tue, Sep 22, 2026 at 09:05:58PM +0800, Lance Yang wrote: > > > > There is some concern around NOLOCK here yes, in fact sashiko picked > > this issue up and I've been poking at it. > > It's possible to document it as unsupported, add a VM_WARN_ON_ONCE etc. > It's not necessary for bulk alloc to support ALLOC_NOLOCK just because it > takes flags. e.g. kmalloc_nolock() supports a subset of gfp flags as well. > It seemed simpler to just support it than to create more future footguns. I can RFC my current rework if you like, i attempted to deduplicate the logic via a prepare_alloc_flags() called from both the bulk and non-bulk contexts. But if we prefer the WARNs over adding the new semantics to the bulk allocator, that also doesn't seem unreasonable. ~Gregory
On 9/23/26 17:24, Gregory Price wrote: > On Wed, Sep 23, 2026 at 08:32:29AM +0200, Vlastimil Babka (SUSE) wrote: >> On 9/23/26 03:59, Gregory Price wrote: >> > On Tue, Sep 22, 2026 at 09:05:58PM +0800, Lance Yang wrote: >> > >> > There is some concern around NOLOCK here yes, in fact sashiko picked >> > this issue up and I've been poking at it. >> >> It's possible to document it as unsupported, add a VM_WARN_ON_ONCE etc. >> It's not necessary for bulk alloc to support ALLOC_NOLOCK just because it >> takes flags. e.g. kmalloc_nolock() supports a subset of gfp flags as well. >> > > It seemed simpler to just support it than to create more future > footguns. I can RFC my current rework if you like, i attempted > to deduplicate the logic via a prepare_alloc_flags() called from > both the bulk and non-bulk contexts. Ok if it's simple enough, lets see :) Thanks > But if we prefer the WARNs over adding the new semantics to the bulk > allocator, that also doesn't seem unreasonable. > > ~Gregory
On Mon, Sep 14, 2026 at 11:51:14AM -0400, Gregory Price wrote: > __alloc_pages_noprof() takes an explicit alloc_flags, but the bulk and > folio entry points do not, so callers cannot select allocator behaviour > (e.g. an alternate zonelist) through them. > > Thread alloc_flags through both, matching __alloc_pages_noprof(), and > keep the flag-carrying primitives mm-internal (page_alloc.h) so the > public gfp.h wrappers stay flag-free: > > - add __alloc_pages_bulk_noprof(gfp, ..., alloc_flags) in page_alloc.h > alloc_pages_bulk_noprof() becomes a wrapper passing ALLOC_DEFAULT > > - give __folio_alloc_noprof() an alloc_flags parameter and moves > __folio_alloc_node_noprof() moves into page_alloc.h > __folio_alloc_noprof() is no longer exported > > No functional change: every caller passes ALLOC_DEFAULT. > > Cc: Brendan Jackman <brendan.jackman@linux.dev> > Signed-off-by: Gregory Price <gourry@gourry.net> > --- > > Brendan this will conflict mildly with your ALLOC_UNMAPPED work, > because you add an init_alloc_flags call in alloc_pages_bulk_noprof : > https://lore.kernel.org/linux-mm/20260726-page_alloc-unmapped-v3-20-6f5729aa9832@google.com/ > > I wanted to pull this out ahead to try to deal with the conflict. > Sashiko pointed out some issues that arise from rebasing on the recent ALLOC_NOLOCK work, and in a quick review I found a similar slow-path fallback issue with the NOBLOCK patch separate of this patch. Brendan, if you're alright with it, can I pull the NOBLOCK patch out ahead into a 2-commit series and I'll add the small fixups? ~Gregory
© 2016 - 2026 Red Hat, Inc.