A private node is invisible to the tiering/demotion hierarchy by default:
memory-tiers.c never includes it, so reclaim never demotes onto it.

Add NODE_PRIVATE_CAP_DEMOTION to opt a private node into demotion.

When set, memory-tiers adds the node to the demotion set and reclaim's
demote path may target it (allocating from the private zonelist).

Demotion is driven by reclaim, so CAP_DEMOTION requires CAP_RECLAIM.
Otherwise the node can either fill up and drive odd system-wide OOM
behavior, or demotion doesn't work (nothing can demote from the node).

Signed-off-by: Gregory Price <[email protected]>
---
 drivers/base/node.c          |  5 +++++
 include/linux/node_private.h | 30 +++++++++++++++++++++++++++++
 mm/memory-tiers.c            | 37 +++++++++++++++++++++++++++---------
 mm/vmscan.c                  |  4 ++++
 4 files changed, 67 insertions(+), 9 deletions(-)

diff --git a/drivers/base/node.c b/drivers/base/node.c
index 94cd51f51b7e8..3d61ca1b805dc 100644
--- a/drivers/base/node.c
+++ b/drivers/base/node.c
@@ -896,6 +896,11 @@ int node_private_register(int nid, struct node_private *np)
        if (!np || !node_possible(nid))
                return -EINVAL;
 
+       /* Demotion is driven by reclaim, so it requires reclaim. */
+       if ((np->caps & NODE_PRIVATE_CAP_DEMOTION) &&
+           !(np->caps & NODE_PRIVATE_CAP_RECLAIM))
+               return -EINVAL;
+
        mutex_lock(&node_private_lock);
        mem_hotplug_begin();
 
diff --git a/include/linux/node_private.h b/include/linux/node_private.h
index 7b617b1fa9c28..87b03444b2c97 100644
--- a/include/linux/node_private.h
+++ b/include/linux/node_private.h
@@ -15,6 +15,7 @@ struct page;
 #define NODE_PRIVATE_CAP_RECLAIM       (1UL << 0)      /* allow mm reclaim */
 #define NODE_PRIVATE_CAP_USER_NUMA     (1UL << 1)      /* allow mempolicy */
 #define NODE_PRIVATE_CAP_HOTUNPLUG     (1UL << 2)      /* allow hot-unplug */
+#define NODE_PRIVATE_CAP_DEMOTION      (1UL << 3)      /* allow tiering 
demotion */
 
 /**
  * struct node_private - Per-node container for N_MEMORY_PRIVATE nodes
@@ -118,6 +119,30 @@ static inline bool node_allows_hotunplug(int nid)
        return ret;
 }
 
+/**
+ * node_allows_demotion - may kernel tiering demote to this node?
+ * @nid: the node to test
+ *
+ * Governs whether a private node participates in the demotion hierarchy.
+ * Demotion accumulates pages on the node, so CAP_DEMOTION requires CAP_RECLAIM
+ * (enforced at registration) as a safety valve.
+ *
+ * return: true for normal nodes and private nodes opted into CAP_DEMOTION.
+ */
+static inline bool node_allows_demotion(int nid)
+{
+       struct node_private *np;
+       bool ret;
+
+       if (!node_state(nid, N_MEMORY_PRIVATE))
+               return true;
+       rcu_read_lock();
+       np = rcu_dereference(NODE_DATA(nid)->node_private);
+       ret = np && (np->caps & NODE_PRIVATE_CAP_DEMOTION);
+       rcu_read_unlock();
+       return ret;
+}
+
 #else /* !CONFIG_NUMA */
 
 static inline bool folio_is_private_node(struct folio *folio)
@@ -150,6 +175,11 @@ static inline bool node_allows_hotunplug(int nid)
        return true;
 }
 
+static inline bool node_allows_demotion(int nid)
+{
+       return true;
+}
+
 #endif /* CONFIG_NUMA */
 
 #if defined(CONFIG_NUMA) && defined(CONFIG_MEMORY_HOTPLUG)
diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
index 25e121851b586..c673080d153e4 100644
--- a/mm/memory-tiers.c
+++ b/mm/memory-tiers.c
@@ -7,6 +7,7 @@
 #include <linux/memory-tiers.h>
 #include <linux/notifier.h>
 #include <linux/sched/sysctl.h>
+#include <linux/node_private.h>
 
 #include "internal.h"
 
@@ -317,6 +318,21 @@ void node_get_allowed_targets(pg_data_t *pgdat, nodemask_t 
*targets)
        rcu_read_unlock();
 }
 
+/* Tiering set: N_MEMORY | (N_MEMORY_PRIVATE w/ CAP_DEMOTION) */
+static nodemask_t tierable_nodes;
+
+static void update_tierable_nodes(void)
+{
+       int node;
+
+       lockdep_assert_held_once(&memory_tier_lock);
+
+       tierable_nodes = node_states[N_MEMORY];
+       for_each_node_state(node, N_MEMORY_PRIVATE)
+               if (node_allows_demotion(node))
+                       node_set(node, tierable_nodes);
+}
+
 /**
  * next_demotion_node() - Get the next node in the demotion path
  * @node: The starting node to lookup the next node
@@ -330,7 +346,7 @@ void node_get_allowed_targets(pg_data_t *pgdat, nodemask_t 
*targets)
 int next_demotion_node(int node, const nodemask_t *allowed_mask)
 {
        struct demotion_nodes *nd;
-       nodemask_t mask;
+       nodemask_t mask, tierable;
 
        if (!node_demotion)
                return NUMA_NO_NODE;
@@ -370,7 +386,8 @@ int next_demotion_node(int node, const nodemask_t 
*allowed_mask)
         * closest demotion target.
         */
        nodes_complement(mask, *allowed_mask);
-       return find_next_best_node_in(node, &mask, &node_states[N_MEMORY]);
+       tierable = tierable_nodes;
+       return find_next_best_node_in(node, &mask, &tierable);
 }
 
 static void disable_all_demotion_targets(void)
@@ -378,7 +395,7 @@ static void disable_all_demotion_targets(void)
        struct memory_tier *memtier;
        int node;
 
-       for_each_node_state(node, N_MEMORY) {
+       for_each_node_mask(node, tierable_nodes) {
                node_demotion[node].preferred = NODE_MASK_NONE;
                /*
                 * We are holding memory_tier_lock, it is safe
@@ -401,7 +418,7 @@ static void dump_demotion_targets(void)
 {
        int node;
 
-       for_each_node_state(node, N_MEMORY) {
+       for_each_node_mask(node, tierable_nodes) {
                struct memory_tier *memtier = __node_get_memory_tier(node);
                nodemask_t preferred = node_demotion[node].preferred;
 
@@ -435,9 +452,10 @@ static void establish_demotion_targets(void)
        if (!node_demotion)
                return;
 
+       update_tierable_nodes();
        disable_all_demotion_targets();
 
-       for_each_node_state(node, N_MEMORY) {
+       for_each_node_mask(node, tierable_nodes) {
                best_distance = -1;
                nd = &node_demotion[node];
 
@@ -455,7 +473,7 @@ static void establish_demotion_targets(void)
                 * nodelist to skip list so that we find the best node from the
                 * memtier nodelist.
                 */
-               nodes_andnot(tier_nodes, node_states[N_MEMORY], tier_nodes);
+               nodes_andnot(tier_nodes, tierable_nodes, tier_nodes);
 
                /*
                 * Find all the nodes in the memory tier node list of same best 
distance.
@@ -464,7 +482,7 @@ static void establish_demotion_targets(void)
                 */
                do {
                        target = find_next_best_node_in(node, &tier_nodes,
-                                                       &node_states[N_MEMORY]);
+                                                       &tierable_nodes);
                        if (target == NUMA_NO_NODE)
                                break;
 
@@ -503,7 +521,7 @@ static void establish_demotion_targets(void)
         * allocation to a set of nodes that is closer the above selected
         * preferred node.
         */
-       lower_tier = node_states[N_MEMORY];
+       lower_tier = tierable_nodes;
        list_for_each_entry(memtier, &memory_tiers, list) {
                /*
                 * Keep removing current tier from lower_tier nodes,
@@ -550,7 +568,8 @@ static struct memory_tier *set_node_memory_tier(int node)
 
        lockdep_assert_held_once(&memory_tier_lock);
 
-       if (!node_state(node, N_MEMORY))
+       /* Include N_MEMORY and N_MEMORY_PRIVATE with CAP_DEMOTION */
+       if (!node_state(node, N_MEMORY) && !node_allows_demotion(node))
                return ERR_PTR(-EINVAL);
 
        mt_calc_adistance(node, &adist);
diff --git a/mm/vmscan.c b/mm/vmscan.c
index f1722693ac2db..b617f7cd1e716 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -963,6 +963,10 @@ static struct folio *alloc_demote_folio(struct folio *src,
 
        mtc = (struct migration_target_control *)private;
 
+       if (mtc->nmask &&
+           nodes_intersects(*mtc->nmask, node_states[N_MEMORY_PRIVATE]))
+               mtc->alloc_flags = ALLOC_ZONELIST_PRIVATE;
+
        /*
         * make sure we allocate from the target node first also trying to
         * demote or reclaim pages from the target node via kswapd if we are
-- 
2.53.0-Meta


Reply via email to