mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/2] mm: refactor zonelist constructors and iterators
@ 2026-09-12  3:04 Gregory Price
  2026-09-12  3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
                   ` (2 more replies)
  0 siblings, 3 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-12  3:04 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy

find_next_best_node() picks the next-closest node when building a
fallback list, and hardcodes N_MEMORY as the set it picks from.

Refactor it into find_next_best_node_in(), which takes the candidate
set explicitly.

This makes the existing behaviour explicit at both mm/memory-tiers.c
call sites - they select demotion targets in fallback order from
N_MEMORY - and lets callers narrow that set.

Then extract the per-node construction loop out of build_zonelists()
into build_node_zonelist(),i parameterised on the candidate nodemask
and destination zonelist index.

Together these allow a zonelist to be built over a candidate set other
than N_MEMORY, into a zonelist other than FALLBACK, and iterated in
fallback order over a caller-defined subset.

These are prerequisites for generating a private node zonelist (nodes
unreachable by default), but are otherwise general improvements to
the existing interfaces so I'm proposing them separately.

No functional change intended - purely refactor commits.

Verified on x86_64:

  - find_next_best_node_in() compiles to the same 319 bytes as
    find_next_best_node().
  - Both mm/memory-tiers.c callers grow a single instruction - the
    added argument.
  - Boot-time fallback orders are identical on a 4-node guest with an
    asymmetric distance matrix.

Gregory Price (2):
  mm: refactor find_next_best_node to find_next_best_node_in
  mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()

 mm/internal.h     |  6 ++--
 mm/memory-tiers.c |  7 +++--
 mm/page_alloc.c   | 74 +++++++++++++++++++----------------------------
 3 files changed, 38 insertions(+), 49 deletions(-)

---

v2: 
- drop extern (Vlastimil)
- move printing into build_node_zonelist() (Vlastimil)
- get rid of node_order array (Vlastimil)
- drop update_load bool (will add back when relevant)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-12  3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
@ 2026-09-12  3:04 ` Gregory Price
  2026-09-14 15:00   ` Zi Yan
  2026-09-18 20:42   ` David Hildenbrand (Arm)
  2026-09-12  3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
  2026-09-15 16:27 ` [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Zenghui Yu
  2 siblings, 2 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-12  3:04 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy, Balbir Singh

find_next_best_node() picks the next-closest node for a fallback list
from the full N_MEMORY set. Refactor it into find_next_best_node_in(),
which takes an explicit candidates nodemask.

This enables building fallback lists with non-N_MEMORY candidates.

No functional change: every caller still selects from N_MEMORY.

Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Acked-by: Balbir Singh <balbirs@nvidia.com>
Signed-off-by: Gregory Price <gourry@gourry.net>
---
 mm/internal.h     |  6 ++++--
 mm/memory-tiers.c |  7 ++++---
 mm/page_alloc.c   | 13 ++++++++-----
 3 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/mm/internal.h b/mm/internal.h
index da14c56fb24e..1519dd21a900 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1133,7 +1133,8 @@ extern int node_reclaim_mode;
 
 extern unsigned long node_reclaim(struct pglist_data *pgdat,
 				  gfp_t gfp_mask, unsigned int order);
-extern int find_next_best_node(int node, nodemask_t *used_node_mask);
+int find_next_best_node_in(int node, nodemask_t *used_node_mask,
+		const nodemask_t *candidates);
 #else
 #define node_reclaim_mode 0
 
@@ -1142,7 +1143,8 @@ static inline unsigned long node_reclaim(struct pglist_data *pgdat,
 {
 	return 0;
 }
-static inline int find_next_best_node(int node, nodemask_t *used_node_mask)
+static inline int find_next_best_node_in(int node, nodemask_t *used_node_mask,
+		const nodemask_t *candidates)
 {
 	return NUMA_NO_NODE;
 }
diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
index 54851d8a195b..25e121851b58 100644
--- a/mm/memory-tiers.c
+++ b/mm/memory-tiers.c
@@ -370,7 +370,7 @@ int next_demotion_node(int node, const nodemask_t *allowed_mask)
 	 * closest demotion target.
 	 */
 	nodes_complement(mask, *allowed_mask);
-	return find_next_best_node(node, &mask);
+	return find_next_best_node_in(node, &mask, &node_states[N_MEMORY]);
 }
 
 static void disable_all_demotion_targets(void)
@@ -450,7 +450,7 @@ static void establish_demotion_targets(void)
 		memtier = list_next_entry(memtier, list);
 		tier_nodes = get_memtier_nodemask(memtier);
 		/*
-		 * find_next_best_node, use 'used' nodemask as a skip list.
+		 * find_next_best_node_in, use 'used' nodemask as a skip list.
 		 * Add all memory nodes except the selected memory tier
 		 * nodelist to skip list so that we find the best node from the
 		 * memtier nodelist.
@@ -463,7 +463,8 @@ static void establish_demotion_targets(void)
 		 * in the preferred mask when allocating pages during demotion.
 		 */
 		do {
-			target = find_next_best_node(node, &tier_nodes);
+			target = find_next_best_node_in(node, &tier_nodes,
+							&node_states[N_MEMORY]);
 			if (target == NUMA_NO_NODE)
 				break;
 
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 1cfbd0582a10..7efce139d562 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -5793,9 +5793,10 @@ static int numa_zonelist_order_handler(const struct ctl_table *table, int write,
 static int node_load[MAX_NUMNODES];
 
 /**
- * find_next_best_node - find the next node that should appear in a given node's fallback list
+ * find_next_best_node_in - find the next node that should appear in a given node's fallback list
  * @node: node whose fallback list we're appending
  * @used_node_mask: nodemask_t of already used nodes
+ * @candidates: nodemask_t of nodes eligible for selection
  *
  * We use a number of factors to determine which is the next node that should
  * appear on a given node's fallback list.  The node should not have appeared
@@ -5807,7 +5808,8 @@ static int node_load[MAX_NUMNODES];
  *
  * Return: node id of the found node or %NUMA_NO_NODE if no node is found.
  */
-int find_next_best_node(int node, nodemask_t *used_node_mask)
+int find_next_best_node_in(int node, nodemask_t *used_node_mask,
+			   const nodemask_t *candidates)
 {
 	int n, val;
 	int min_val = INT_MAX;
@@ -5817,12 +5819,12 @@ int find_next_best_node(int node, nodemask_t *used_node_mask)
 	 * Use the local node if we haven't already, but for memoryless local
 	 * node, we should skip it and fall back to other nodes.
 	 */
-	if (!node_isset(node, *used_node_mask) && node_state(node, N_MEMORY)) {
+	if (!node_isset(node, *used_node_mask) && node_isset(node, *candidates)) {
 		node_set(node, *used_node_mask);
 		return node;
 	}
 
-	for_each_node_state(n, N_MEMORY) {
+	for_each_node_mask(n, *candidates) {
 
 		/* Don't want a node to appear more than once */
 		if (node_isset(n, *used_node_mask))
@@ -5907,7 +5909,8 @@ static void build_zonelists(pg_data_t *pgdat)
 	prev_node = local_node;
 
 	memset(node_order, 0, sizeof(node_order));
-	while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {
+	while ((node = find_next_best_node_in(local_node, &used_mask,
+					      &node_states[N_MEMORY])) >= 0) {
 		/*
 		 * We don't want to pressure a particular node.
 		 * So adding penalty to the first node in same
-- 
2.55.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-12  3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
  2026-09-12  3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
@ 2026-09-12  3:04 ` Gregory Price
  2026-09-14 14:56   ` Vlastimil Babka (SUSE)
  2026-09-21  2:49   ` Zi Yan
  2026-09-15 16:27 ` [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Zenghui Yu
  2 siblings, 2 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-12  3:04 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy

Extract per-node fallback-list construction into build_node_zonelist().
Build each selected node directly into the destination zonelist so no
intermediate node_order array or node count is needed. Print the fallback
order as each node is added.

This lets us build new zonelists from candidate nodemasks instead of just
the default N_MEMORY node state list.

No functional change: build_zonelists() builds and prints the same FALLBACK
list over N_MEMORY with node_load updates as before.

Signed-off-by: Gregory Price <gourry@gourry.net>
---
 mm/page_alloc.c | 63 ++++++++++++++++++-------------------------------
 1 file changed, 23 insertions(+), 40 deletions(-)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 7efce139d562..d1888d5630e0 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
 }
 
 
-/*
- * Build zonelists ordered by node and zones within node.
- * This results in maximum locality--normal zone overflows into local
- * DMA zone, if any--but risks exhausting DMA zone.
- */
-static void build_zonelists_in_node_order(pg_data_t *pgdat, int *node_order,
-		unsigned nr_nodes)
-{
-	struct zoneref *zonerefs;
-	int i;
-
-	zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
-
-	for (i = 0; i < nr_nodes; i++) {
-		int nr_zones;
-
-		pg_data_t *node = NODE_DATA(node_order[i]);
-
-		nr_zones = build_zonerefs_node(node, zonerefs);
-		zonerefs += nr_zones;
-	}
-	zonerefs->zone = NULL;
-	zonerefs->zone_idx = 0;
-}
-
 /*
  * Build __GFP_THISNODE zonelists
  */
@@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
 	zonerefs->zone_idx = 0;
 }
 
-static void build_zonelists(pg_data_t *pgdat)
+/*
+ * Build one zonelist ordered by node and zones within node. This results in
+ * maximum locality--normal zone overflows into local DMA zone, if any--but
+ * risks exhausting DMA zone.
+ */
+static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
+				int zlidx)
 {
-	static int node_order[MAX_NUMNODES];
-	int node, nr_nodes = 0;
+	struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
 	nodemask_t used_mask = NODE_MASK_NONE;
-	int local_node, prev_node;
+	int local_node = pgdat->node_id;
+	int prev_node = local_node;
+	int node;
 
-	/* NUMA-aware ordering of nodes */
-	local_node = pgdat->node_id;
-	prev_node = local_node;
+	pr_info("Fallback order for Node %d: ", local_node);
 
-	memset(node_order, 0, sizeof(node_order));
 	while ((node = find_next_best_node_in(local_node, &used_mask,
-					      &node_states[N_MEMORY])) >= 0) {
+					      candidates)) >= 0) {
 		/*
 		 * We don't want to pressure a particular node.
 		 * So adding penalty to the first node in same
@@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
 		    node_distance(local_node, prev_node))
 			node_load[node] += 1;
 
-		node_order[nr_nodes++] = node;
+		zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
+		pr_cont("%d ", node);
 		prev_node = node;
 	}
 
-	build_zonelists_in_node_order(pgdat, node_order, nr_nodes);
-	build_thisnode_zonelists(pgdat);
-	pr_info("Fallback order for Node %d: ", local_node);
-	for (node = 0; node < nr_nodes; node++)
-		pr_cont("%d ", node_order[node]);
+	zonerefs->zone = NULL;
+	zonerefs->zone_idx = 0;
 	pr_cont("\n");
 }
 
+static void build_zonelists(pg_data_t *pgdat)
+{
+	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
+	build_thisnode_zonelists(pgdat);
+}
+
 #ifdef CONFIG_HAVE_MEMORYLESS_NODES
 /*
  * Return node id of node used for "local" allocations.
-- 
2.55.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-12  3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
@ 2026-09-14 14:56   ` Vlastimil Babka (SUSE)
  2026-09-14 15:04     ` Gregory Price
  2026-09-21  2:49   ` Zi Yan
  1 sibling, 1 reply; 20+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-09-14 14:56 UTC (permalink / raw)
  To: Gregory Price, linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, rppt, surenb,
	mhocko, brendan.jackman, hannes, ziy

On 9/12/26 05:04, Gregory Price wrote:
> Extract per-node fallback-list construction into build_node_zonelist().
> Build each selected node directly into the destination zonelist so no
> intermediate node_order array or node count is needed. Print the fallback
> order as each node is added.
> 
> This lets us build new zonelists from candidate nodemasks instead of just
> the default N_MEMORY node state list.
> 
> No functional change: build_zonelists() builds and prints the same FALLBACK
> list over N_MEMORY with node_load updates as before.
> 
> Signed-off-by: Gregory Price <gourry@gourry.net>

Nice, glad this way of refactor was feasible.

Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>

> ---
>  mm/page_alloc.c | 63 ++++++++++++++++++-------------------------------
>  1 file changed, 23 insertions(+), 40 deletions(-)
> 
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 7efce139d562..d1888d5630e0 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
>  }
>  
>  
> -/*
> - * Build zonelists ordered by node and zones within node.
> - * This results in maximum locality--normal zone overflows into local
> - * DMA zone, if any--but risks exhausting DMA zone.
> - */
> -static void build_zonelists_in_node_order(pg_data_t *pgdat, int *node_order,
> -		unsigned nr_nodes)
> -{
> -	struct zoneref *zonerefs;
> -	int i;
> -
> -	zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
> -
> -	for (i = 0; i < nr_nodes; i++) {
> -		int nr_zones;
> -
> -		pg_data_t *node = NODE_DATA(node_order[i]);
> -
> -		nr_zones = build_zonerefs_node(node, zonerefs);
> -		zonerefs += nr_zones;
> -	}
> -	zonerefs->zone = NULL;
> -	zonerefs->zone_idx = 0;
> -}
> -
>  /*
>   * Build __GFP_THISNODE zonelists
>   */
> @@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
>  	zonerefs->zone_idx = 0;
>  }
>  
> -static void build_zonelists(pg_data_t *pgdat)
> +/*
> + * Build one zonelist ordered by node and zones within node. This results in
> + * maximum locality--normal zone overflows into local DMA zone, if any--but
> + * risks exhausting DMA zone.
> + */
> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> +				int zlidx)
>  {
> -	static int node_order[MAX_NUMNODES];
> -	int node, nr_nodes = 0;
> +	struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
>  	nodemask_t used_mask = NODE_MASK_NONE;
> -	int local_node, prev_node;
> +	int local_node = pgdat->node_id;
> +	int prev_node = local_node;
> +	int node;
>  
> -	/* NUMA-aware ordering of nodes */
> -	local_node = pgdat->node_id;
> -	prev_node = local_node;
> +	pr_info("Fallback order for Node %d: ", local_node);
>  
> -	memset(node_order, 0, sizeof(node_order));
>  	while ((node = find_next_best_node_in(local_node, &used_mask,
> -					      &node_states[N_MEMORY])) >= 0) {
> +					      candidates)) >= 0) {
>  		/*
>  		 * We don't want to pressure a particular node.
>  		 * So adding penalty to the first node in same
> @@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
>  		    node_distance(local_node, prev_node))
>  			node_load[node] += 1;
>  
> -		node_order[nr_nodes++] = node;
> +		zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
> +		pr_cont("%d ", node);
>  		prev_node = node;
>  	}
>  
> -	build_zonelists_in_node_order(pgdat, node_order, nr_nodes);
> -	build_thisnode_zonelists(pgdat);
> -	pr_info("Fallback order for Node %d: ", local_node);
> -	for (node = 0; node < nr_nodes; node++)
> -		pr_cont("%d ", node_order[node]);
> +	zonerefs->zone = NULL;
> +	zonerefs->zone_idx = 0;
>  	pr_cont("\n");
>  }
>  
> +static void build_zonelists(pg_data_t *pgdat)
> +{
> +	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
> +	build_thisnode_zonelists(pgdat);
> +}
> +
>  #ifdef CONFIG_HAVE_MEMORYLESS_NODES
>  /*
>   * Return node id of node used for "local" allocations.


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-12  3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
@ 2026-09-14 15:00   ` Zi Yan
  2026-09-18 20:42   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-14 15:00 UTC (permalink / raw)
  To: Gregory Price
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes,
	Balbir Singh

On 11 Sep 2026, at 23:04, Gregory Price wrote:

> find_next_best_node() picks the next-closest node for a fallback list
> from the full N_MEMORY set. Refactor it into find_next_best_node_in(),
> which takes an explicit candidates nodemask.
>
> This enables building fallback lists with non-N_MEMORY candidates.
>
> No functional change: every caller still selects from N_MEMORY.
>
> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> Acked-by: Balbir Singh <balbirs@nvidia.com>
> Signed-off-by: Gregory Price <gourry@gourry.net>
> ---
>  mm/internal.h     |  6 ++++--
>  mm/memory-tiers.c |  7 ++++---
>  mm/page_alloc.c   | 13 ++++++++-----
>  3 files changed, 16 insertions(+), 10 deletions(-)
>
LGTM.

Reviewed-by: Zi Yan <ziy@nvidia.com>

Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-14 14:56   ` Vlastimil Babka (SUSE)
@ 2026-09-14 15:04     ` Gregory Price
  0 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-14 15:04 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE)
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	rppt, surenb, mhocko, brendan.jackman, hannes, ziy

On Mon, Sep 14, 2026 at 04:56:30PM +0200, Vlastimil Babka (SUSE) wrote:
> On 9/12/26 05:04, Gregory Price wrote:
> > Extract per-node fallback-list construction into build_node_zonelist().
> > Build each selected node directly into the destination zonelist so no
> > intermediate node_order array or node count is needed. Print the fallback
> > order as each node is added.
> > 
> > This lets us build new zonelists from candidate nodemasks instead of just
> > the default N_MEMORY node state list.
> > 
> > No functional change: build_zonelists() builds and prints the same FALLBACK
> > list over N_MEMORY with node_load updates as before.
> > 
> > Signed-off-by: Gregory Price <gourry@gourry.net>
> 
> Nice, glad this way of refactor was feasible.
> 

Yup, came out much cleaner than i expected.

> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> 

Thank you!

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 0/2] mm: refactor zonelist constructors and iterators
  2026-09-12  3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
  2026-09-12  3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
  2026-09-12  3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
@ 2026-09-15 16:27 ` Zenghui Yu
  2 siblings, 0 replies; 20+ messages in thread
From: Zenghui Yu @ 2026-09-15 16:27 UTC (permalink / raw)
  To: Gregory Price
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes, ziy

On 9/12/26 11:04 AM, Gregory Price wrote:
> find_next_best_node() picks the next-closest node when building a
> fallback list, and hardcodes N_MEMORY as the set it picks from.
> 
> Refactor it into find_next_best_node_in(), which takes the candidate
> set explicitly.
> 
> This makes the existing behaviour explicit at both mm/memory-tiers.c
> call sites - they select demotion targets in fallback order from
> N_MEMORY - and lets callers narrow that set.
> 
> Then extract the per-node construction loop out of build_zonelists()
> into build_node_zonelist(),i parameterised on the candidate nodemask
> and destination zonelist index.
> 
> Together these allow a zonelist to be built over a candidate set other
> than N_MEMORY, into a zonelist other than FALLBACK, and iterated in
> fallback order over a caller-defined subset.
> 
> These are prerequisites for generating a private node zonelist (nodes
> unreachable by default), but are otherwise general improvements to
> the existing interfaces so I'm proposing them separately.
> 
> No functional change intended - purely refactor commits.
> 
> Verified on x86_64:
> 
>   - find_next_best_node_in() compiles to the same 319 bytes as
>     find_next_best_node().
>   - Both mm/memory-tiers.c callers grow a single instruction - the
>     added argument.
>   - Boot-time fallback orders are identical on a 4-node guest with an
>     asymmetric distance matrix.
> 
> Gregory Price (2):
>   mm: refactor find_next_best_node to find_next_best_node_in
>   mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
> 
>  mm/internal.h     |  6 ++--
>  mm/memory-tiers.c |  7 +++--
>  mm/page_alloc.c   | 74 +++++++++++++++++++----------------------------
>  3 files changed, 38 insertions(+), 49 deletions(-)

Reviewed-by: Zenghui Yu (Huawei) <zenghui.yu@linux.dev>

Thanks,
Zenghui

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-12  3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
  2026-09-14 15:00   ` Zi Yan
@ 2026-09-18 20:42   ` David Hildenbrand (Arm)
  2026-09-18 21:49     ` Gregory Price
  1 sibling, 1 reply; 20+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-18 20:42 UTC (permalink / raw)
  To: Gregory Price, linux-mm
  Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
	mhocko, brendan.jackman, hannes, ziy, Balbir Singh

On 9/12/26 05:04, Gregory Price wrote:
> find_next_best_node() picks the next-closest node for a fallback list
> from the full N_MEMORY set. Refactor it into find_next_best_node_in(),
> which takes an explicit candidates nodemask.
> 
> This enables building fallback lists with non-N_MEMORY candidates.
> 
> No functional change: every caller still selects from N_MEMORY.
> 
> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> Acked-by: Balbir Singh <balbirs@nvidia.com>
> Signed-off-by: Gregory Price <gourry@gourry.net>
> ---
>  mm/internal.h     |  6 ++++--
>  mm/memory-tiers.c |  7 ++++---
>  mm/page_alloc.c   | 13 ++++++++-----
>  3 files changed, 16 insertions(+), 10 deletions(-)
> 
> diff --git a/mm/internal.h b/mm/internal.h
> index da14c56fb24e..1519dd21a900 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -1133,7 +1133,8 @@ extern int node_reclaim_mode;
>  
>  extern unsigned long node_reclaim(struct pglist_data *pgdat,
>  				  gfp_t gfp_mask, unsigned int order);
> -extern int find_next_best_node(int node, nodemask_t *used_node_mask);
> +int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> +		const nodemask_t *candidates);

Just curious, for your use case, wouldn't it be enough to pass N_MEMORY?

Or do we expect to pass other actual node masks that are not derived from
node_states?


-- 
Cheers,

David

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-18 20:42   ` David Hildenbrand (Arm)
@ 2026-09-18 21:49     ` Gregory Price
  0 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-18 21:49 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, kernel-team, akpm, ljs, liam, vbabka,
	rppt, surenb, mhocko, brendan.jackman, hannes, ziy, Balbir Singh

On Fri, Sep 18, 2026 at 10:42:12PM +0200, David Hildenbrand (Arm) wrote:
> > diff --git a/mm/internal.h b/mm/internal.h
> > index da14c56fb24e..1519dd21a900 100644
> > --- a/mm/internal.h
> > +++ b/mm/internal.h
> > @@ -1133,7 +1133,8 @@ extern int node_reclaim_mode;
> >  
> >  extern unsigned long node_reclaim(struct pglist_data *pgdat,
> >  				  gfp_t gfp_mask, unsigned int order);
> > -extern int find_next_best_node(int node, nodemask_t *used_node_mask);
> > +int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> > +		const nodemask_t *candidates);
> 
> Just curious, for your use case, wouldn't it be enough to pass N_MEMORY?
> 
> Or do we expect to pass other actual node masks that are not derived from
> node_states?
> 

That is the base case today - and in fact that's what's hard-coded
in the current logic.

For the private node series we will pass:

ZONELIST_FALLBACK:  N_MEMORY_GENERAL    /* general purpose nodes */

ZONELIST_PRIVATE:   N_MEMORY            /* All nodes, including private */

ZONELIST_KTEST:     (N_MEMORY_GENERAL ^ N_MEMORY)  /* private only */


the ZONELIST_KTEST is something i'm still working with - but it gives us
fully isolated pgdat structures that allow a KTEST to treat the node
mutations as determinstic and otherwise unreachable by the kernel
(because the kernel outside ktest can't select ZOENLIST_KTEST).


ANYWAY...

I don't expect us to build hundreds of these zonelists - they are
expensive to construct (space-wise), but that's largely because we
haven't needed to optimize it.

But you could imagine having a custom zonelist built on a subset
of N_MEMORY so that you could do like...

for_each_reclaimable_zone(z) {
	/* Reclaim */
}

and hide all this nasty zone business from other components of mm/ that
really don't need all that complexity (at most they want the LRUs).

~Gregory

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-12  3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
  2026-09-14 14:56   ` Vlastimil Babka (SUSE)
@ 2026-09-21  2:49   ` Zi Yan
  2026-09-21  2:53     ` Zi Yan
  2026-09-21  3:29     ` Gregory Price
  1 sibling, 2 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-21  2:49 UTC (permalink / raw)
  To: Gregory Price, linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes

On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
> Extract per-node fallback-list construction into build_node_zonelist().
> Build each selected node directly into the destination zonelist so no
> intermediate node_order array or node count is needed. Print the fallback
> order as each node is added.
>
> This lets us build new zonelists from candidate nodemasks instead of just
> the default N_MEMORY node state list.
>
> No functional change: build_zonelists() builds and prints the same FALLBACK
> list over N_MEMORY with node_load updates as before.
>
> Signed-off-by: Gregory Price <gourry@gourry.net>
> ---
>  mm/page_alloc.c | 63 ++++++++++++++++++-------------------------------
>  1 file changed, 23 insertions(+), 40 deletions(-)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 7efce139d562..d1888d5630e0 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
>  }
>  
>  
> -/*
> - * Build zonelists ordered by node and zones within node.
> - * This results in maximum locality--normal zone overflows into local
> - * DMA zone, if any--but risks exhausting DMA zone.
> - */
> -static void build_zonelists_in_node_order(pg_data_t *pgdat, int *node_order,
> -		unsigned nr_nodes)
> -{
> -	struct zoneref *zonerefs;
> -	int i;
> -
> -	zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
> -
> -	for (i = 0; i < nr_nodes; i++) {
> -		int nr_zones;
> -
> -		pg_data_t *node = NODE_DATA(node_order[i]);
> -
> -		nr_zones = build_zonerefs_node(node, zonerefs);
> -		zonerefs += nr_zones;
> -	}
> -	zonerefs->zone = NULL;
> -	zonerefs->zone_idx = 0;
> -}
> -
>  /*
>   * Build __GFP_THISNODE zonelists
>   */
> @@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
>  	zonerefs->zone_idx = 0;
>  }
>  
> -static void build_zonelists(pg_data_t *pgdat)
> +/*
> + * Build one zonelist ordered by node and zones within node. This results in
> + * maximum locality--normal zone overflows into local DMA zone, if any--but
> + * risks exhausting DMA zone.
> + */
> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> +				int zlidx)
>  {
> -	static int node_order[MAX_NUMNODES];
> -	int node, nr_nodes = 0;
> +	struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;

Why does build_node_zonelist() need to have a new zlidx instead of using
ZONELIST_FALLBACK like build_zonelists_in_node_order() did?

>  	nodemask_t used_mask = NODE_MASK_NONE;
> -	int local_node, prev_node;
> +	int local_node = pgdat->node_id;
> +	int prev_node = local_node;
> +	int node;
>  
> -	/* NUMA-aware ordering of nodes */
> -	local_node = pgdat->node_id;
> -	prev_node = local_node;
> +	pr_info("Fallback order for Node %d: ", local_node);
>  
> -	memset(node_order, 0, sizeof(node_order));
>  	while ((node = find_next_best_node_in(local_node, &used_mask,
> -					      &node_states[N_MEMORY])) >= 0) {
> +					      candidates)) >= 0) {
>  		/*
>  		 * We don't want to pressure a particular node.
>  		 * So adding penalty to the first node in same
> @@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
>  		    node_distance(local_node, prev_node))
>  			node_load[node] += 1;
>  
> -		node_order[nr_nodes++] = node;
> +		zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
> +		pr_cont("%d ", node);
>  		prev_node = node;
>  	}
>  
> -	build_zonelists_in_node_order(pgdat, node_order, nr_nodes);
> -	build_thisnode_zonelists(pgdat);
> -	pr_info("Fallback order for Node %d: ", local_node);
> -	for (node = 0; node < nr_nodes; node++)
> -		pr_cont("%d ", node_order[node]);
> +	zonerefs->zone = NULL;
> +	zonerefs->zone_idx = 0;
>  	pr_cont("\n");
>  }
>  
> +static void build_zonelists(pg_data_t *pgdat)
> +{
> +	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
> +	build_thisnode_zonelists(pgdat);

If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
cannot build_node_zonelist() imply ZONELIST_FALLBACK?

> +}
> +
>  #ifdef CONFIG_HAVE_MEMORYLESS_NODES
>  /*
>   * Return node id of node used for "local" allocations.


-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21  2:49   ` Zi Yan
@ 2026-09-21  2:53     ` Zi Yan
  2026-09-21  3:33       ` Gregory Price
  2026-09-21  3:29     ` Gregory Price
  1 sibling, 1 reply; 20+ messages in thread
From: Zi Yan @ 2026-09-21  2:53 UTC (permalink / raw)
  To: Gregory Price, linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes

On Sun Sep 20, 2026 at 10:49 PM EDT, Zi Yan wrote:
> On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
>> Extract per-node fallback-list construction into build_node_zonelist().
>> Build each selected node directly into the destination zonelist so no
>> intermediate node_order array or node count is needed. Print the fallback
>> order as each node is added.
>>
>> This lets us build new zonelists from candidate nodemasks instead of just
>> the default N_MEMORY node state list.
>>
>> No functional change: build_zonelists() builds and prints the same FALLBACK
>> list over N_MEMORY with node_load updates as before.
>>
>> Signed-off-by: Gregory Price <gourry@gourry.net>
>> ---
>>  mm/page_alloc.c | 63 ++++++++++++++++++-------------------------------
>>  1 file changed, 23 insertions(+), 40 deletions(-)
>>
>> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
>> index 7efce139d562..d1888d5630e0 100644
>> --- a/mm/page_alloc.c
>> +++ b/mm/page_alloc.c
>> @@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
>>  }
>>  
>>  
>> -/*
>> - * Build zonelists ordered by node and zones within node.
>> - * This results in maximum locality--normal zone overflows into local
>> - * DMA zone, if any--but risks exhausting DMA zone.
>> - */
>> -static void build_zonelists_in_node_order(pg_data_t *pgdat, int *node_order,
>> -		unsigned nr_nodes)
>> -{
>> -	struct zoneref *zonerefs;
>> -	int i;
>> -
>> -	zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
>> -
>> -	for (i = 0; i < nr_nodes; i++) {
>> -		int nr_zones;
>> -
>> -		pg_data_t *node = NODE_DATA(node_order[i]);
>> -
>> -		nr_zones = build_zonerefs_node(node, zonerefs);
>> -		zonerefs += nr_zones;
>> -	}
>> -	zonerefs->zone = NULL;
>> -	zonerefs->zone_idx = 0;
>> -}
>> -
>>  /*
>>   * Build __GFP_THISNODE zonelists
>>   */
>> @@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
>>  	zonerefs->zone_idx = 0;
>>  }
>>  
>> -static void build_zonelists(pg_data_t *pgdat)
>> +/*
>> + * Build one zonelist ordered by node and zones within node. This results in
>> + * maximum locality--normal zone overflows into local DMA zone, if any--but
>> + * risks exhausting DMA zone.
>> + */
>> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
>> +				int zlidx)

Is build_numa_aware_zonelist() a better name? Since the old comment said
"NUMA-aware ording of nodes" and the code uses find_next_best_node_in().

>>  {
>> -	static int node_order[MAX_NUMNODES];
>> -	int node, nr_nodes = 0;
>> +	struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
>
> Why does build_node_zonelist() need to have a new zlidx instead of using
> ZONELIST_FALLBACK like build_zonelists_in_node_order() did?
>
>>  	nodemask_t used_mask = NODE_MASK_NONE;
>> -	int local_node, prev_node;
>> +	int local_node = pgdat->node_id;
>> +	int prev_node = local_node;
>> +	int node;
>>  
>> -	/* NUMA-aware ordering of nodes */
>> -	local_node = pgdat->node_id;
>> -	prev_node = local_node;
>> +	pr_info("Fallback order for Node %d: ", local_node);
>>  
>> -	memset(node_order, 0, sizeof(node_order));
>>  	while ((node = find_next_best_node_in(local_node, &used_mask,
>> -					      &node_states[N_MEMORY])) >= 0) {
>> +					      candidates)) >= 0) {
>>  		/*
>>  		 * We don't want to pressure a particular node.
>>  		 * So adding penalty to the first node in same
>> @@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
>>  		    node_distance(local_node, prev_node))
>>  			node_load[node] += 1;
>>  
>> -		node_order[nr_nodes++] = node;
>> +		zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
>> +		pr_cont("%d ", node);
>>  		prev_node = node;
>>  	}
>>  
>> -	build_zonelists_in_node_order(pgdat, node_order, nr_nodes);
>> -	build_thisnode_zonelists(pgdat);
>> -	pr_info("Fallback order for Node %d: ", local_node);
>> -	for (node = 0; node < nr_nodes; node++)
>> -		pr_cont("%d ", node_order[node]);
>> +	zonerefs->zone = NULL;
>> +	zonerefs->zone_idx = 0;
>>  	pr_cont("\n");
>>  }
>>  
>> +static void build_zonelists(pg_data_t *pgdat)
>> +{
>> +	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
>> +	build_thisnode_zonelists(pgdat);
>
> If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
> cannot build_node_zonelist() imply ZONELIST_FALLBACK?
>
>> +}
>> +
>>  #ifdef CONFIG_HAVE_MEMORYLESS_NODES
>>  /*
>>   * Return node id of node used for "local" allocations.




-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21  2:49   ` Zi Yan
  2026-09-21  2:53     ` Zi Yan
@ 2026-09-21  3:29     ` Gregory Price
  2026-09-21 16:18       ` Zi Yan
  1 sibling, 1 reply; 20+ messages in thread
From: Gregory Price @ 2026-09-21  3:29 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On Sun, Sep 20, 2026 at 10:49:05PM -0400, Zi Yan wrote:
> On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
> > +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> > +				int zlidx)
> >  {
> > -	static int node_order[MAX_NUMNODES];
> > -	int node, nr_nodes = 0;
> > +	struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
> 
> Why does build_node_zonelist() need to have a new zlidx instead of using
> ZONELIST_FALLBACK like build_zonelists_in_node_order() did?
> 

The intent is to build new zonelist over a set of candidate nodes, and
it's also just clearer: build ZONELIST_FALLBACK from N_MEMORY.

With this we get:

build_node_zonelists(pgdat, &node_states[N_MEMORY_COMMON], ZONELIST_FALLBACK);
build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_PRIVATE);
#ifdef PAGEALLOC_KTEST
nodemask_andnot(&private_only, &node_states[N_MEMORY],
		&node_states[N_MEMORY_COMMON]
build_node_zonelists(pgdat, &private_only, ZONELIST_KTEST);
#endif 


> > +static void build_zonelists(pg_data_t *pgdat)
> > +{
> > +	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
> > +	build_thisnode_zonelists(pgdat);
> 
> If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
> cannot build_node_zonelist() imply ZONELIST_FALLBACK?
> 

thisnode actually means ZONELIST_X+1 as opposed to ZONELIST_NOFALLBACK.

Since folks are adamant about not allowing another GFP flag for zonelist
selection (beyond GFP_THISNODE), the result of this is that all future
zonelist additions must carry a FALLBACK + NOFALLBACK variant.

The question you actually want to ask is why build_thisnode_zonelists()
even exists - it should be part of build_node_zonelist()

I can probably follow up this series by just folding eveything into

/* build zlidx and zlidx+1 (nofallback) */
build_node_zonelists(pgdat, candidates, zlidx);

And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
balanced zonelist additions.

But I don't think it's strictly necessary for any of this, and we're
just shuffling code from one place to another.  Probably I can just add
that improvement when we add the next zonelist.  In the meantime - this
makes it easier to add new zonelists as-is (and just makes the code more
readable).

~Gregory

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21  2:53     ` Zi Yan
@ 2026-09-21  3:33       ` Gregory Price
  2026-09-21 16:20         ` Zi Yan
  0 siblings, 1 reply; 20+ messages in thread
From: Gregory Price @ 2026-09-21  3:33 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On Sun, Sep 20, 2026 at 10:53:13PM -0400, Zi Yan wrote:
> >> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> >> +				int zlidx)
> 
> Is build_numa_aware_zonelist() a better name? Since the old comment said
> "NUMA-aware ording of nodes" and the code uses find_next_best_node_in().
> 

I think that's a vestige of older version having different ordering
options.  Now the zonelists are always numa aware (except for
!CONFIG_NUMA, which... duh). 

Anyway numa-aware is implied by "node" in the name.

But I don't have strong feelings about the name either way.

~Gregory

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21  3:29     ` Gregory Price
@ 2026-09-21 16:18       ` Zi Yan
  2026-09-21 16:38         ` Gregory Price
  0 siblings, 1 reply; 20+ messages in thread
From: Zi Yan @ 2026-09-21 16:18 UTC (permalink / raw)
  To: Gregory Price
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On 20 Sep 2026, at 23:29, Gregory Price wrote:

> On Sun, Sep 20, 2026 at 10:49:05PM -0400, Zi Yan wrote:
>> On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
>>> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
>>> +				int zlidx)
>>>  {
>>> -	static int node_order[MAX_NUMNODES];
>>> -	int node, nr_nodes = 0;
>>> +	struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
>>
>> Why does build_node_zonelist() need to have a new zlidx instead of using
>> ZONELIST_FALLBACK like build_zonelists_in_node_order() did?
>>
>
> The intent is to build new zonelist over a set of candidate nodes, and
> it's also just clearer: build ZONELIST_FALLBACK from N_MEMORY.
>
> With this we get:
>
> build_node_zonelists(pgdat, &node_states[N_MEMORY_COMMON], ZONELIST_FALLBACK);
> build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_PRIVATE);
> #ifdef PAGEALLOC_KTEST
> nodemask_andnot(&private_only, &node_states[N_MEMORY],
> 		&node_states[N_MEMORY_COMMON]
> build_node_zonelists(pgdat, &private_only, ZONELIST_KTEST);
> #endif
>

OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
In theory, the new zlidx can be added when you add new ZONELIST_ types.

I am OK with adding it now, but you could mention this change in
the commit message to avoid confusion. Something like,
for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.


>
>>> +static void build_zonelists(pg_data_t *pgdat)
>>> +{
>>> +	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
>>> +	build_thisnode_zonelists(pgdat);
>>
>> If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
>> cannot build_node_zonelist() imply ZONELIST_FALLBACK?
>>
>
> thisnode actually means ZONELIST_X+1 as opposed to ZONELIST_NOFALLBACK.
>
> Since folks are adamant about not allowing another GFP flag for zonelist
> selection (beyond GFP_THISNODE), the result of this is that all future
> zonelist additions must carry a FALLBACK + NOFALLBACK variant.
>
> The question you actually want to ask is why build_thisnode_zonelists()
> even exists - it should be part of build_node_zonelist()
>
> I can probably follow up this series by just folding eveything into
>
> /* build zlidx and zlidx+1 (nofallback) */
> build_node_zonelists(pgdat, candidates, zlidx);
>
> And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
> balanced zonelist additions.

Got it. Thank you for the explanation. Are all combinations of
{ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
{N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?
Any enforcement if not? This is more related your “private node” series,
instead of this patchset.

>
> But I don't think it's strictly necessary for any of this, and we're
> just shuffling code from one place to another.  Probably I can just add
> that improvement when we add the next zonelist.  In the meantime - this
> makes it easier to add new zonelists as-is (and just makes the code more
> readable).

Sure, no rush.

Feel free to add

Reviewed-by: Zi Yan <ziy@nvidia.com>

after you add some text on the added zlidx in the commit message.


Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21  3:33       ` Gregory Price
@ 2026-09-21 16:20         ` Zi Yan
  0 siblings, 0 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-21 16:20 UTC (permalink / raw)
  To: Gregory Price
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On 20 Sep 2026, at 23:33, Gregory Price wrote:

> On Sun, Sep 20, 2026 at 10:53:13PM -0400, Zi Yan wrote:
>>>> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
>>>> +				int zlidx)
>>
>> Is build_numa_aware_zonelist() a better name? Since the old comment said
>> "NUMA-aware ording of nodes" and the code uses find_next_best_node_in().
>>
>
> I think that's a vestige of older version having different ordering
> options.  Now the zonelists are always numa aware (except for
> !CONFIG_NUMA, which... duh).

Yeah, I am OK with the current name to avoid confusion on !CONFIG_NUMA.

>
> Anyway numa-aware is implied by "node" in the name.
>
> But I don't have strong feelings about the name either way.



Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21 16:18       ` Zi Yan
@ 2026-09-21 16:38         ` Gregory Price
  2026-09-21 17:04           ` Gregory Price
                             ` (2 more replies)
  0 siblings, 3 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-21 16:38 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
> On 20 Sep 2026, at 23:29, Gregory Price wrote:
> 
> OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
> In theory, the new zlidx can be added when you add new ZONELIST_ types.
> 
> I am OK with adding it now, but you could mention this change in
> the commit message to avoid confusion. Something like,
> for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.
>
...
> > And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
> > balanced zonelist additions.
> 
> Got it. Thank you for the explanation. Are all combinations of
> {ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
> {N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?

Slight inaccuracy on the way zonelists are actually built.

Every node has a list in every zonelist.  The contents of each node's
list in that zonelist are limited to the candidate nodemask.

So consider the following nodes:  N0, N1, P2   (N=normal, P=private)

With the following candidate mappings:
   FALLBACK = N_MEMORY_GENERAL  (_COMMON)  (all normal nodes)
   PRIVATE  = N_MEMORY                     (all nodes)
   KTEST    = N_MEMORY & !N_MEMORY_GENERAL (only private nodes)

FALLBACK
  0  :  [0,1]
  1  :  [1,0]
  2  :  [0,1]  <- private node's fallback list is normal nodes

NOFALLBACK
  0  :  [0]
  1  :  [1]
  2  :  [2]

PRIVATE          Accessible via ALLOC_PRIVATE_ZONELIST
  0  :  [0,1,2]
  1  :  [1,0,2]
  2  :  [2,0,1]

PRIVATE_NOFALLBACK
  0  :  [0]
  1  :  [1]
  2  :  [2]

KTEST             Not accessible via any flag, completely isolated
  0  :  [2]
  1  :  [2]
  2  :  [2]

KTEST_NOFALLBACK  Not accessible via any flag, completely isolated
  0  :  []
  1  :  []
  2  :  [2]

I haven't posted the ktest series yet, but it's how i've made the page
allocator ktest-able.  Ktest is empowered to set the fallback list
directly rather than requiring a flag - not something any in-tree caller
can do - which lets it limit allocations to a private node and makes
mutations on the pgdat for that node deterministic from test-to-test.

Works on UML too, so testing is very fast. :]

Apologies for the added complexity in the explanation, but I figure it's
worth spelling out.

> Any enforcement if not? This is more related your “private node” series,
> instead of this patchset.
>

These zonelists aren't something we're going to allocate dynamically,
the enforcement is encoded in the function (build ZONELIST_X over
candidates N_MEMORY_Y).

> >
> > But I don't think it's strictly necessary for any of this, and we're
> > just shuffling code from one place to another.  Probably I can just add
> > that improvement when we add the next zonelist.  In the meantime - this
> > makes it easier to add new zonelists as-is (and just makes the code more
> > readable).
> 
> Sure, no rush.
> 
> Feel free to add
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> 
> after you add some text on the added zlidx in the commit message.
> 

ack. hopefully the explanation above helps.

I will work that into the commit message in a reduced capacity.

~Gregory

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21 16:38         ` Gregory Price
@ 2026-09-21 17:04           ` Gregory Price
  2026-09-21 17:18           ` Zi Yan
  2026-09-21 23:58           ` Zi Yan
  2 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-21 17:04 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On Mon, Sep 21, 2026 at 12:38:57PM -0400, Gregory Price wrote:
> On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
> NOFALLBACK
>   0  :  [0]
>   1  :  [1]
>   2  :  [2]

NOFALLBACK for a private node should be empty, sorry:

  0  :  [0]
  1  :  [1]
  2  :  []

if you attempt alloc(private_node, GFP_THISNODE) without selecting the
private zonelist, your allocation should fail.

But this is more discussion for the private node series.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21 16:38         ` Gregory Price
  2026-09-21 17:04           ` Gregory Price
@ 2026-09-21 17:18           ` Zi Yan
  2026-09-21 23:58           ` Zi Yan
  2 siblings, 0 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-21 17:18 UTC (permalink / raw)
  To: Gregory Price
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On 21 Sep 2026, at 12:38, Gregory Price wrote:

> On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
>> On 20 Sep 2026, at 23:29, Gregory Price wrote:
>>
>> OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
>> In theory, the new zlidx can be added when you add new ZONELIST_ types.
>>
>> I am OK with adding it now, but you could mention this change in
>> the commit message to avoid confusion. Something like,
>> for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.
>>
> ...
>>> And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
>>> balanced zonelist additions.
>>
>> Got it. Thank you for the explanation. Are all combinations of
>> {ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
>> {N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?
>
> Slight inaccuracy on the way zonelists are actually built.
>
> Every node has a list in every zonelist.  The contents of each node's
> list in that zonelist are limited to the candidate nodemask.

Got it. It makes sense to me now. Thanks.

>
> So consider the following nodes:  N0, N1, P2   (N=normal, P=private)
>
> With the following candidate mappings:
>    FALLBACK = N_MEMORY_GENERAL  (_COMMON)  (all normal nodes)
>    PRIVATE  = N_MEMORY                     (all nodes)
>    KTEST    = N_MEMORY & !N_MEMORY_GENERAL (only private nodes)
>
> FALLBACK
>   0  :  [0,1]
>   1  :  [1,0]
>   2  :  [0,1]  <- private node's fallback list is normal nodes
>
> NOFALLBACK
>   0  :  [0]
>   1  :  [1]
>   2  :  [2]
>
> PRIVATE          Accessible via ALLOC_PRIVATE_ZONELIST
>   0  :  [0,1,2]
>   1  :  [1,0,2]
>   2  :  [2,0,1]
>
> PRIVATE_NOFALLBACK
>   0  :  [0]
>   1  :  [1]
>   2  :  [2]
>
> KTEST             Not accessible via any flag, completely isolated
>   0  :  [2]
>   1  :  [2]
>   2  :  [2]
>
> KTEST_NOFALLBACK  Not accessible via any flag, completely isolated
>   0  :  []
>   1  :  []
>   2  :  [2]
>
> I haven't posted the ktest series yet, but it's how i've made the page
> allocator ktest-able.  Ktest is empowered to set the fallback list
> directly rather than requiring a flag - not something any in-tree caller
> can do - which lets it limit allocations to a private node and makes
> mutations on the pgdat for that node deterministic from test-to-test.
>
> Works on UML too, so testing is very fast. :]
>
> Apologies for the added complexity in the explanation, but I figure it's
> worth spelling out.

Definitely. Great information.

>
>> Any enforcement if not? This is more related your “private node” series,
>> instead of this patchset.
>>
>
> These zonelists aren't something we're going to allocate dynamically,
> the enforcement is encoded in the function (build ZONELIST_X over
> candidates N_MEMORY_Y).

ack.
>
>>>
>>> But I don't think it's strictly necessary for any of this, and we're
>>> just shuffling code from one place to another.  Probably I can just add
>>> that improvement when we add the next zonelist.  In the meantime - this
>>> makes it easier to add new zonelists as-is (and just makes the code more
>>> readable).
>>
>> Sure, no rush.
>>
>> Feel free to add
>>
>> Reviewed-by: Zi Yan <ziy@nvidia.com>
>>
>> after you add some text on the added zlidx in the commit message.
>>
>
> ack. hopefully the explanation above helps.

Yep. :)

>
> I will work that into the commit message in a reduced capacity.

To be clear, I just would like you to mention the addition of
zlidx to bulid_node_zonelist(). Thanks.

Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21 16:38         ` Gregory Price
  2026-09-21 17:04           ` Gregory Price
  2026-09-21 17:18           ` Zi Yan
@ 2026-09-21 23:58           ` Zi Yan
  2026-09-22  1:02             ` Gregory Price
  2 siblings, 1 reply; 20+ messages in thread
From: Zi Yan @ 2026-09-21 23:58 UTC (permalink / raw)
  To: Gregory Price
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On 21 Sep 2026, at 12:38, Gregory Price wrote:

> On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
>> On 20 Sep 2026, at 23:29, Gregory Price wrote:
>>
>> OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
>> In theory, the new zlidx can be added when you add new ZONELIST_ types.
>>
>> I am OK with adding it now, but you could mention this change in
>> the commit message to avoid confusion. Something like,
>> for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.
>>
> ...
>>> And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
>>> balanced zonelist additions.
>>
>> Got it. Thank you for the explanation. Are all combinations of
>> {ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
>> {N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?
>
> Slight inaccuracy on the way zonelists are actually built.
>
> Every node has a list in every zonelist.  The contents of each node's
> list in that zonelist are limited to the candidate nodemask.
>
> So consider the following nodes:  N0, N1, P2   (N=normal, P=private)
>
> With the following candidate mappings:
>    FALLBACK = N_MEMORY_GENERAL  (_COMMON)  (all normal nodes)
>    PRIVATE  = N_MEMORY                     (all nodes)
>    KTEST    = N_MEMORY & !N_MEMORY_GENERAL (only private nodes)
>

One more question. What prevents one adding
build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK)?
Based on your above mapping, each of ZONELIST_* has its own candidate.


Best Regards,
Yan, Zi

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-21 23:58           ` Zi Yan
@ 2026-09-22  1:02             ` Gregory Price
  0 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-22  1:02 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
	vbabka, rppt, surenb, mhocko, brendan.jackman, hannes

On Mon, Sep 21, 2026 at 07:58:38PM -0400, Zi Yan wrote:
> On 21 Sep 2026, at 12:38, Gregory Price wrote:
> 
> One more question. What prevents one adding
> build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK)?
> Based on your above mapping, each of ZONELIST_* has its own candidate.
> 

Other than build_zonelist and build_node_zonelist being static functions
only reachable by page_alloc.c?

Nothing presently.  It could possibly be asserted.

~Gregory

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-09-22  1:02 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-12  3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
2026-09-12  3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
2026-09-14 15:00   ` Zi Yan
2026-09-18 20:42   ` David Hildenbrand (Arm)
2026-09-18 21:49     ` Gregory Price
2026-09-12  3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
2026-09-14 14:56   ` Vlastimil Babka (SUSE)
2026-09-14 15:04     ` Gregory Price
2026-09-21  2:49   ` Zi Yan
2026-09-21  2:53     ` Zi Yan
2026-09-21  3:33       ` Gregory Price
2026-09-21 16:20         ` Zi Yan
2026-09-21  3:29     ` Gregory Price
2026-09-21 16:18       ` Zi Yan
2026-09-21 16:38         ` Gregory Price
2026-09-21 17:04           ` Gregory Price
2026-09-21 17:18           ` Zi Yan
2026-09-21 23:58           ` Zi Yan
2026-09-22  1:02             ` Gregory Price
2026-09-15 16:27 ` [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Zenghui Yu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®