* [PATCH 1/3] mm/mempolicy: factor weighted interleave state helpers
2026-11-03 12:28 [PATCH 0/3] mm/mempolicy: add per-cgroup weighted interleave controls Li Zhe
@ 2026-11-03 12:28 ` Li Zhe
2026-11-03 12:28 ` [PATCH 2/3] mm/mempolicy: add cgroup weighted interleave plumbing Li Zhe
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Li Zhe @ 2026-11-03 12:28 UTC (permalink / raw)
To: tj, hannes, mkoutny, akpm, david, gourry, ziy, joshua.hahnjy,
ying.huang, ljs, liam, rppt, surenb, mhocko
Cc: cgroups, linux-kernel, linux-mm, bpf, Li Zhe
Weighted interleave updates repeat the same allocation, default table
initialization, table copy and default table setup patterns in the sysfs
and auto-weighting paths.
Factor those pieces into small helpers so later changes can reuse one
state lifecycle for both the global table and cgroup-local override
tables. Keep the existing upstream wi_srcu retirement path unchanged.
No functional change is intended.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
mm/mempolicy.c | 54 ++++++++++++++++++++++++++++++--------------------
1 file changed, 32 insertions(+), 22 deletions(-)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 0884c629670d..fb9962302664 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -188,6 +188,30 @@ static void wi_state_retire(struct weighted_interleave_state *state)
call_srcu(&wi_srcu, &state->rcu, wi_state_free_rcu);
}
+static struct weighted_interleave_state *wi_state_alloc(void)
+{
+ struct weighted_interleave_state *state;
+
+ return kzalloc_flex(*state, iw_table, nr_node_ids, GFP_KERNEL);
+}
+
+static void wi_state_init(struct weighted_interleave_state *state)
+{
+ for (int i = 0; i < nr_node_ids; i++)
+ state->iw_table[i] = 1;
+}
+
+#if defined(CONFIG_SYSFS) || defined(CONFIG_BPF_WEIGHTED_INTERLEAVE)
+static void wi_state_init_from(struct weighted_interleave_state *dst,
+ struct weighted_interleave_state *src)
+{
+ if (src)
+ memcpy(dst->iw_table, src->iw_table, nr_node_ids * sizeof(u8));
+ else
+ wi_state_init(dst);
+}
+#endif
+
static u8 get_il_weight(int node)
{
struct weighted_interleave_state *state;
@@ -243,22 +267,17 @@ int mempolicy_set_node_perf(unsigned int node, struct access_coordinate *coords)
struct weighted_interleave_state *new_wi_state, *old_wi_state = NULL;
unsigned int *old_bw, *new_bw;
unsigned int bw_val;
- int i;
bw_val = min(coords->read_bandwidth, coords->write_bandwidth);
new_bw = kcalloc(nr_node_ids, sizeof(unsigned int), GFP_KERNEL);
if (!new_bw)
return -ENOMEM;
- new_wi_state = kmalloc_flex(*new_wi_state, iw_table, nr_node_ids);
+ new_wi_state = wi_state_alloc();
if (!new_wi_state) {
kfree(new_bw);
return -ENOMEM;
}
- new_wi_state->mode_auto = true;
- for (i = 0; i < nr_node_ids; i++)
- new_wi_state->iw_table[i] = 1;
-
/*
* Update bandwidth info, even in manual mode. That way, when switching
* to auto mode in the future, iw_table can be overwritten using
@@ -282,6 +301,8 @@ int mempolicy_set_node_perf(unsigned int node, struct access_coordinate *coords)
}
/* NULL wi_state assumes auto=true; reduce weights and update wi_state*/
+ wi_state_init(new_wi_state);
+ new_wi_state->mode_auto = true;
reduce_interleave_weights(new_bw, new_wi_state->iw_table);
rcu_assign_pointer(wi_state, new_wi_state);
@@ -3672,27 +3693,20 @@ static ssize_t node_store(struct kobject *kobj, struct kobj_attribute *attr,
struct weighted_interleave_state *new_wi_state, *old_wi_state = NULL;
struct iw_node_attr *node_attr;
u8 weight = 0;
- int i;
node_attr = container_of(attr, struct iw_node_attr, kobj_attr);
if (count == 0 || sysfs_streq(buf, "") ||
kstrtou8(buf, 0, &weight) || weight == 0)
return -EINVAL;
- new_wi_state = kzalloc_flex(*new_wi_state, iw_table, nr_node_ids);
+ new_wi_state = wi_state_alloc();
if (!new_wi_state)
return -ENOMEM;
mutex_lock(&wi_state_lock);
old_wi_state = rcu_dereference_protected(wi_state,
lockdep_is_held(&wi_state_lock));
- if (old_wi_state) {
- memcpy(new_wi_state->iw_table, old_wi_state->iw_table,
- nr_node_ids * sizeof(u8));
- } else {
- for (i = 0; i < nr_node_ids; i++)
- new_wi_state->iw_table[i] = 1;
- }
+ wi_state_init_from(new_wi_state, old_wi_state);
new_wi_state->iw_table[node_attr->nid] = weight;
new_wi_state->mode_auto = false;
@@ -3723,16 +3737,13 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj,
struct weighted_interleave_state *new_wi_state, *old_wi_state = NULL;
unsigned int *bw;
bool input;
- int i;
if (kstrtobool(buf, &input))
return -EINVAL;
- new_wi_state = kzalloc_flex(*new_wi_state, iw_table, nr_node_ids);
+ new_wi_state = wi_state_alloc();
if (!new_wi_state)
return -ENOMEM;
- for (i = 0; i < nr_node_ids; i++)
- new_wi_state->iw_table[i] = 1;
mutex_lock(&wi_state_lock);
old_wi_state = rcu_dereference_protected(wi_state,
@@ -3745,9 +3756,7 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj,
}
if (!input) {
- if (old_wi_state)
- memcpy(new_wi_state->iw_table, old_wi_state->iw_table,
- nr_node_ids * sizeof(u8));
+ wi_state_init_from(new_wi_state, old_wi_state);
goto update_wi_state;
}
@@ -3758,6 +3767,7 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj,
return -ENODEV;
}
+ wi_state_init(new_wi_state);
new_wi_state->mode_auto = true;
reduce_interleave_weights(bw, new_wi_state->iw_table);
--
2.20.1
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 2/3] mm/mempolicy: add cgroup weighted interleave plumbing
2026-11-03 12:28 [PATCH 0/3] mm/mempolicy: add per-cgroup weighted interleave controls Li Zhe
2026-11-03 12:28 ` [PATCH 1/3] mm/mempolicy: factor weighted interleave state helpers Li Zhe
@ 2026-11-03 12:28 ` Li Zhe
2026-11-03 12:28 ` [PATCH 3/3] mm/mempolicy: add BPF kfuncs for cgroup interleave weights Li Zhe
2026-11-03 15:35 ` [PATCH 0/3] mm/mempolicy: add per-cgroup weighted interleave controls Tejun Heo
3 siblings, 0 replies; 5+ messages in thread
From: Li Zhe @ 2026-11-03 12:28 UTC (permalink / raw)
To: tj, hannes, mkoutny, akpm, david, gourry, ziy, joshua.hahnjy,
ying.huang, ljs, liam, rppt, surenb, mhocko
Cc: cgroups, linux-kernel, linux-mm, bpf, Li Zhe
Add the kernel-side storage and lookup plumbing for per-cgroup weighted
interleave overrides. Each cgroup stores only an explicit override
table. Cgroups without one inherit the nearest ancestor override and
otherwise use the existing global weighted-interleave state.
The BPF control plane is built through a hidden derived Kconfig symbol,
so it follows the BPF syscall and BTF prerequisites without adding a
user-visible weighted-interleave knob.
Per-task weighted-interleave state continues to cache the current node
and the remaining dynamic weight quota. Weight changes and cgroup
migration are observed when that quota is refilled; the hot allocation
path does not revalidate the cached quota on every allocation.
Bulk weighted-interleave allocations continue to use wi_srcu for
concurrency protection.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
include/linux/cgroup-defs.h | 5 ++
include/linux/mempolicy.h | 10 ++++
kernel/cgroup/cgroup.c | 2 +
mm/Kconfig | 6 +++
mm/mempolicy.c | 104 ++++++++++++++++++++++++++++++++----
5 files changed, 117 insertions(+), 10 deletions(-)
diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h
index cbfe8937647c..2dc1cb7336a5 100644
--- a/include/linux/cgroup-defs.h
+++ b/include/linux/cgroup-defs.h
@@ -29,6 +29,7 @@ struct cgroup;
struct cgroup_root;
struct cgroup_subsys;
struct cgroup_taskset;
+struct weighted_interleave_state;
struct kernfs_node;
struct kernfs_ops;
struct kernfs_open_file;
@@ -647,6 +648,10 @@ struct cgroup {
#ifdef CONFIG_EXT_SUB_SCHED
struct scx_sched __rcu *scx_sched;
#endif
+#ifdef CONFIG_BPF_WEIGHTED_INTERLEAVE
+ /* Explicit weighted-interleave override for this cgroup. */
+ struct weighted_interleave_state __rcu *wi_state;
+#endif
/* All ancestors including self */
union {
diff --git a/include/linux/mempolicy.h b/include/linux/mempolicy.h
index 65c732d440d2..59bfb272da31 100644
--- a/include/linux/mempolicy.h
+++ b/include/linux/mempolicy.h
@@ -16,6 +16,7 @@
#include <linux/pagemap.h>
#include <uapi/linux/mempolicy.h>
+struct cgroup;
struct mm_struct;
#define NO_INTERLEAVE_INDEX (-1UL) /* use task il_prev for interleaving */
@@ -304,4 +305,13 @@ static inline bool mpol_is_preferred_many(struct mempolicy *pol)
}
#endif /* CONFIG_NUMA */
+
+#ifdef CONFIG_BPF_WEIGHTED_INTERLEAVE
+void weighted_interleave_cgroup_free(struct cgroup *cgrp);
+#else
+static inline void weighted_interleave_cgroup_free(struct cgroup *cgrp)
+{
+}
+#endif
+
#endif
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index a284225f8dc9..705bb2e85571 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -35,6 +35,7 @@
#include <linux/magic.h>
#include <linux/mutex.h>
#include <linux/mount.h>
+#include <linux/mempolicy.h>
#include <linux/pagemap.h>
#include <linux/proc_fs.h>
#include <linux/rcupdate.h>
@@ -5733,6 +5734,7 @@ static void css_free_rwork_fn(struct work_struct *work)
cgroup1_pidlist_destroy_all(cgrp);
cancel_work_sync(&cgrp->release_agent_work);
bpf_cgrp_storage_free(cgrp);
+ weighted_interleave_cgroup_free(cgrp);
if (cgroup_parent(cgrp)) {
/*
diff --git a/mm/Kconfig b/mm/Kconfig
index afff1b25b543..43190060f25f 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1530,6 +1530,12 @@ config ARCH_HAS_PTDUMP
config PTDUMP
bool
+config BPF_WEIGHTED_INTERLEAVE
+ def_bool y
+ depends on NUMA
+ depends on CGROUPS
+ depends on BPF_SYSCALL
+ depends on DEBUG_INFO_BTF
source "mm/damon/Kconfig"
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index fb9962302664..751a5e90a4e1 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -96,6 +96,7 @@
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/compat.h>
+#include <linux/cgroup.h>
#include <linux/ptrace.h>
#include <linux/swap.h>
#include <linux/seq_file.h>
@@ -112,6 +113,7 @@
#include <linux/printk.h>
#include <linux/leafops.h>
#include <linux/gcd.h>
+#include <linux/jump_label.h>
#include <linux/srcu.h>
#include <asm/tlbflush.h>
@@ -170,6 +172,11 @@ static unsigned int *node_bw_table;
*/
static DEFINE_MUTEX(wi_state_lock);
+#ifdef CONFIG_BPF_WEIGHTED_INTERLEAVE
+static DEFINE_STATIC_KEY_FALSE(wi_cgroup_weights_set);
+static DEFINE_MUTEX(wi_cgroup_lock);
+#endif
+
/* Readers that sleep while walking iw_table hold this instead */
DEFINE_STATIC_SRCU_FAST(wi_srcu);
@@ -212,18 +219,90 @@ static void wi_state_init_from(struct weighted_interleave_state *dst,
}
#endif
-static u8 get_il_weight(int node)
+static inline u8 wi_state_weight(const struct weighted_interleave_state *state,
+ int node)
+{
+ return state ? state->iw_table[node] : 1;
+}
+
+#ifdef CONFIG_BPF_WEIGHTED_INTERLEAVE
+static struct weighted_interleave_state *wi_cgroup_state_rcu(struct cgroup *cgrp)
+{
+ struct weighted_interleave_state *state;
+
+ for (; cgrp; cgrp = cgroup_parent(cgrp)) {
+ state = rcu_dereference(cgrp->wi_state);
+ if (state)
+ return state;
+ }
+ return NULL;
+}
+
+static struct weighted_interleave_state *wi_task_state_rcu(void)
+{
+ if (static_branch_unlikely(&wi_cgroup_weights_set) && in_task())
+ return wi_cgroup_state_rcu(task_dfl_cgroup(current));
+ return NULL;
+}
+
+void weighted_interleave_cgroup_free(struct cgroup *cgrp)
+{
+ struct weighted_interleave_state *old_wi_state;
+
+ mutex_lock(&wi_cgroup_lock);
+ old_wi_state = rcu_dereference_protected(cgrp->wi_state,
+ lockdep_is_held(&wi_cgroup_lock));
+ if (old_wi_state) {
+ rcu_assign_pointer(cgrp->wi_state, NULL);
+ static_branch_dec(&wi_cgroup_weights_set);
+ }
+ mutex_unlock(&wi_cgroup_lock);
+
+ wi_state_retire(old_wi_state);
+}
+#else
+static struct weighted_interleave_state *wi_task_state_rcu(void)
+{
+ return NULL;
+}
+
+#endif
+
+static struct weighted_interleave_state *wi_effective_state_rcu(void)
+{
+ struct weighted_interleave_state *state;
+
+ state = wi_task_state_rcu();
+ if (state)
+ return state;
+ return rcu_dereference(wi_state);
+}
+
+static u8 get_effective_il_weight(int node)
{
struct weighted_interleave_state *state;
- u8 weight = 1;
+ u8 weight;
+
+ rcu_read_lock();
+ state = wi_effective_state_rcu();
+ weight = wi_state_weight(state, node);
+ rcu_read_unlock();
+ return weight;
+}
+
+#ifdef CONFIG_SYSFS
+static u8 get_global_il_weight(int node)
+{
+ struct weighted_interleave_state *state;
+ u8 weight;
rcu_read_lock();
state = rcu_dereference(wi_state);
- if (state)
- weight = state->iw_table[node];
+ weight = wi_state_weight(state, node);
rcu_read_unlock();
return weight;
}
+#endif
/*
* Convert bandwidth values into weighted interleave weights.
@@ -2144,7 +2223,7 @@ static unsigned int weighted_interleave_nodes(struct mempolicy *policy)
if (node == MAX_NUMNODES)
return node;
current->il_prev = node;
- current->il_weight = get_il_weight(node);
+ current->il_weight = get_effective_il_weight(node);
}
current->il_weight--;
return node;
@@ -2228,8 +2307,8 @@ static unsigned int weighted_interleave_nid(struct mempolicy *pol, pgoff_t ilx)
rcu_read_lock();
- state = rcu_dereference(wi_state);
- /* Uninitialized wi_state means we should assume all weights are 1 */
+ state = wi_effective_state_rcu();
+ /* Uninitialized effective state means we should assume all weights are 1 */
if (state)
table = state->iw_table;
@@ -2734,10 +2813,15 @@ static unsigned long alloc_pages_bulk_weighted_interleave(gfp_t gfp,
me->il_weight = 0;
prev_node = node;
- /* The page allocator may sleep, pin the weight table with SRCU */
+ /*
+ * The page allocator may sleep. Pin the selected weight table with
+ * SRCU, but choose the task/cgroup effective table under RCU.
+ */
scp = srcu_read_lock_fast(&wi_srcu);
- state = srcu_dereference(wi_state, &wi_srcu);
+ rcu_read_lock();
+ state = wi_effective_state_rcu();
table = state ? state->iw_table : NULL;
+ rcu_read_unlock();
/* calculate total, detect system default usage */
for_each_node_mask(node, pol->nodes)
@@ -3683,7 +3767,7 @@ static ssize_t node_show(struct kobject *kobj, struct kobj_attribute *attr,
u8 weight;
node_attr = container_of(attr, struct iw_node_attr, kobj_attr);
- weight = get_il_weight(node_attr->nid);
+ weight = get_global_il_weight(node_attr->nid);
return sysfs_emit(buf, "%d\n", weight);
}
--
2.20.1
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 3/3] mm/mempolicy: add BPF kfuncs for cgroup interleave weights
2026-11-03 12:28 [PATCH 0/3] mm/mempolicy: add per-cgroup weighted interleave controls Li Zhe
2026-11-03 12:28 ` [PATCH 1/3] mm/mempolicy: factor weighted interleave state helpers Li Zhe
2026-11-03 12:28 ` [PATCH 2/3] mm/mempolicy: add cgroup weighted interleave plumbing Li Zhe
@ 2026-11-03 12:28 ` Li Zhe
2026-11-03 15:35 ` [PATCH 0/3] mm/mempolicy: add per-cgroup weighted interleave controls Tejun Heo
3 siblings, 0 replies; 5+ messages in thread
From: Li Zhe @ 2026-11-03 12:28 UTC (permalink / raw)
To: tj, hannes, mkoutny, akpm, david, gourry, ziy, joshua.hahnjy,
ying.huang, ljs, liam, rppt, surenb, mhocko
Cc: cgroups, linux-kernel, linux-mm, bpf, Li Zhe
Expose bpf_wi_set_weight(), bpf_wi_clear_weights(), and
bpf_wi_get_weight() as a BPF syscall control plane for cgroup
weighted-interleave overrides. The kfuncs let a privileged program
update one node weight, remove an override, or read the effective
weight.
All three kfuncs are sleepable, require CAP_SYS_ADMIN in the initial
user namespace, and accept only cgroup ids from the default cgroup-v2
hierarchy. Updates materialize a complete weight table and publish it
with RCU.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
mm/mempolicy.c | 201 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 200 insertions(+), 1 deletion(-)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 751a5e90a4e1..7521542712cb 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -78,6 +78,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/mempolicy.h>
+#include <linux/capability.h>
+#include <linux/bpf.h>
#include <linux/pagewalk.h>
#include <linux/highmem.h>
#include <linux/hugetlb.h>
@@ -103,6 +105,7 @@
#include <linux/proc_fs.h>
#include <linux/memory-tiers.h>
#include <linux/migrate.h>
+#include <linux/module.h>
#include <linux/ksm.h>
#include <linux/rmap.h>
#include <linux/security.h>
@@ -245,7 +248,7 @@ static struct weighted_interleave_state *wi_task_state_rcu(void)
return NULL;
}
-void weighted_interleave_cgroup_free(struct cgroup *cgrp)
+static void wi_cgroup_clear_weights(struct cgroup *cgrp)
{
struct weighted_interleave_state *old_wi_state;
@@ -260,6 +263,202 @@ void weighted_interleave_cgroup_free(struct cgroup *cgrp)
wi_state_retire(old_wi_state);
}
+
+void weighted_interleave_cgroup_free(struct cgroup *cgrp)
+{
+ wi_cgroup_clear_weights(cgrp);
+}
+
+static struct weighted_interleave_state *
+wi_effective_state_cgroup_rcu(struct cgroup *cgrp)
+{
+ struct weighted_interleave_state *state;
+
+ state = wi_cgroup_state_rcu(cgrp);
+ if (state)
+ return state;
+ return rcu_dereference(wi_state);
+}
+
+static void wi_cgroup_set_state(struct cgroup *cgrp,
+ struct weighted_interleave_state *new_wi_state,
+ u32 nid, u8 weight)
+{
+ struct weighted_interleave_state *old_wi_state;
+
+ mutex_lock(&wi_cgroup_lock);
+ old_wi_state = rcu_dereference_protected(cgrp->wi_state,
+ lockdep_is_held(&wi_cgroup_lock));
+ rcu_read_lock();
+ wi_state_init_from(new_wi_state, wi_effective_state_cgroup_rcu(cgrp));
+ rcu_read_unlock();
+
+ new_wi_state->mode_auto = false;
+ new_wi_state->iw_table[nid] = weight;
+ rcu_assign_pointer(cgrp->wi_state, new_wi_state);
+ if (!old_wi_state)
+ static_branch_inc(&wi_cgroup_weights_set);
+ mutex_unlock(&wi_cgroup_lock);
+
+ wi_state_retire(old_wi_state);
+}
+
+static bool wi_valid_nid(u32 nid)
+{
+ return nid < nr_node_ids && node_state(nid, N_MEMORY);
+}
+
+static int wi_cgroup_get(u64 cgid, struct cgroup **cgrpp)
+{
+ struct cgroup *cgrp;
+
+ cgrp = __cgroup_get_from_id(cgid);
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
+
+ if (!cgroup_on_dfl(cgrp)) {
+ cgroup_put(cgrp);
+ return -ENOENT;
+ }
+
+ *cgrpp = cgrp;
+ return 0;
+}
+
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_wi_set_weight - Set one weighted-interleave node weight for a cgroup.
+ * @cgid: cgroup id that must name a cgroup in default cgroup-v2 hierarchy
+ * @nid: memory NUMA node id to update
+ * @weight: new weight in range [1, 255]
+ *
+ * The first update creates an explicit override for @cgid by copying the
+ * nearest ancestor override, or the system-wide weighted-interleave weights
+ * if no ancestor has one. Nodes not named by the first update become local
+ * values in that table and do not keep tracking ancestor changes. Later
+ * updates modify the cgroup-local table. The override affects new
+ * MPOL_WEIGHTED_INTERLEAVE allocations made by tasks in the cgroup and
+ * descendants that do not have a nearer override.
+ *
+ * Return: 0 on success, -EINVAL for an invalid node or weight, -ENOENT
+ * when @cgid does not name a cgroup in the default cgroup-v2 hierarchy,
+ * -EPERM for insufficient privilege, or -ENOMEM when the override table
+ * cannot be allocated.
+ */
+__bpf_kfunc int bpf_wi_set_weight(u64 cgid, u32 nid, u32 weight)
+{
+ struct weighted_interleave_state *new_wi_state;
+ struct cgroup *cgrp;
+ int ret;
+
+ if (!wi_valid_nid(nid) || weight == 0 || weight > U8_MAX)
+ return -EINVAL;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ new_wi_state = wi_state_alloc();
+ if (!new_wi_state)
+ return -ENOMEM;
+
+ ret = wi_cgroup_get(cgid, &cgrp);
+ if (ret) {
+ kfree(new_wi_state);
+ return ret;
+ }
+
+ wi_cgroup_set_state(cgrp, new_wi_state, nid, weight);
+ cgroup_put(cgrp);
+ return 0;
+}
+
+/**
+ * bpf_wi_clear_weights - Remove a cgroup weighted-interleave override.
+ * @cgid: cgroup id that must name a cgroup in default cgroup-v2 hierarchy
+ *
+ * After clearing, the cgroup inherits the nearest ancestor override, or the
+ * system-wide weighted-interleave weights if no ancestor has an override.
+ *
+ * Return: 0 on success, -ENOENT when @cgid does not name a cgroup in
+ * the default cgroup-v2 hierarchy, or -EPERM for insufficient
+ * privilege.
+ */
+__bpf_kfunc int bpf_wi_clear_weights(u64 cgid)
+{
+ struct cgroup *cgrp;
+ int ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = wi_cgroup_get(cgid, &cgrp);
+ if (ret)
+ return ret;
+
+ wi_cgroup_clear_weights(cgrp);
+ cgroup_put(cgrp);
+ return 0;
+}
+
+/**
+ * bpf_wi_get_weight - Read the effective weighted-interleave node weight.
+ * @cgid: cgroup id that must name a cgroup in default cgroup-v2 hierarchy
+ * @nid: memory NUMA node id to query
+ *
+ * Return: the effective weight, -EINVAL for an invalid node, -ENOENT when
+ * @cgid does not name a cgroup in the default cgroup-v2 hierarchy, or
+ * -EPERM for insufficient privilege.
+ */
+__bpf_kfunc s64 bpf_wi_get_weight(u64 cgid, u32 nid)
+{
+ struct weighted_interleave_state *state;
+ struct cgroup *cgrp;
+ s64 weight;
+ int ret;
+
+ if (!wi_valid_nid(nid))
+ return -EINVAL;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = wi_cgroup_get(cgid, &cgrp);
+ if (ret)
+ return ret;
+
+ rcu_read_lock();
+ state = wi_effective_state_cgroup_rcu(cgrp);
+ weight = wi_state_weight(state, nid);
+ rcu_read_unlock();
+
+ cgroup_put(cgrp);
+ return weight;
+}
+
+__bpf_kfunc_end_defs();
+
+__ADDRESSABLE(bpf_wi_set_weight)
+__ADDRESSABLE(bpf_wi_clear_weights)
+__ADDRESSABLE(bpf_wi_get_weight)
+
+BTF_KFUNCS_START(wi_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_wi_set_weight, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_wi_clear_weights, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_wi_get_weight, KF_SLEEPABLE)
+BTF_KFUNCS_END(wi_kfunc_ids)
+
+static const struct btf_kfunc_id_set wi_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &wi_kfunc_ids,
+};
+
+static int __init wi_kfunc_init(void)
+{
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+ &wi_kfunc_set);
+}
+late_initcall(wi_kfunc_init);
#else
static struct weighted_interleave_state *wi_task_state_rcu(void)
{
--
2.20.1
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH 0/3] mm/mempolicy: add per-cgroup weighted interleave controls
2026-11-03 12:28 [PATCH 0/3] mm/mempolicy: add per-cgroup weighted interleave controls Li Zhe
` (2 preceding siblings ...)
2026-11-03 12:28 ` [PATCH 3/3] mm/mempolicy: add BPF kfuncs for cgroup interleave weights Li Zhe
@ 2026-11-03 15:35 ` Tejun Heo
3 siblings, 0 replies; 5+ messages in thread
From: Tejun Heo @ 2026-11-03 15:35 UTC (permalink / raw)
To: Li Zhe
Cc: hannes, mkoutny, akpm, david, gourry, ziy, joshua.hahnjy,
ying.huang, ljs, liam, rppt, surenb, mhocko, cgroups,
linux-kernel, linux-mm, bpf
Hello, Zhe.
On Tue, Nov 03, 2026 at 08:28:13PM +0800, Li Zhe wrote:
> MPOL_WEIGHTED_INTERLEAVE currently has one system-wide weight table,
> configured through sysfs or auto-derived from node bandwidth. This series
> adds an optional BPF control plane that lets privileged BPF syscall
> programs install manual weighted-interleave overrides for individual
> cgroups.
...
I don't think this has much to do with cgroups. Weighted interleave is
already a per-thread/per-VMA policy chosen through set_mempolicy() and
mbind(), and the weights are parameters of that policy. I think this
should be tied to the memory policy instead. This is different from
the per-cgroup NUMA balancing kfuncs. NUMA balancing has no per-task
opt-in and acts on memory with no task context.
Gregory Price's mempolicy2 series already proposed set_mempolicy2() and
mbind2() with task-local weights:
https://lore.kernel.org/all/20240103224209.2541-1-gregory.price@memverge.com/
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 5+ messages in thread