From: Tejun Heo <tj@kernel.org>
To: David Vernet <void@manifault.com>,
Andrea Righi <arighi@nvidia.com>,
Changwoo Min <changwoo@igalia.com>
Cc: sched-ext@lists.linux.dev, Emil Tsalapatis <emil@etsalapatis.com>,
Johannes Weiner <hannes@cmpxchg.org>,
Michal Koutny <mkoutny@suse.com>,
cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
Tejun Heo <tj@kernel.org>
Subject: [PATCH 7/8] tools/sched_ext: scx_qmap - Consume cgroup weights through set_weight
Date: Fri, 17 Jul 2026 22:17:26 -1000 [thread overview]
Message-ID: <20260718081727.582037-8-tj@kernel.org> (raw)
In-Reply-To: <20260718081727.582037-1-tj@kernel.org>
With the set_* ops delivered to the parent's sched, a parent qmap instance
now receives cgroup_set_weight for its child subs' attach points. Update
the matching sub_sched_ctx weight and redistribute() in-kernel, and drop
the userspace feed_weights() polling. This exercises the knob routing end
to end.
The self weight is fixed at 100: a cgroup's weight is its parent's knob and
not the scheduler's own business. This drops the self-weight polling and the
repartition PROG_RUN poke with it.
sub_attach seeds the slot with the cgroup's current weight, read through
bpf_cgroup_from_id(), so a weight set before the sub attaches is picked up.
A write racing the attach can still be lost until the next value-changing
cpu.weight write. Acceptable for a demo.
While at it, add a traced ops.cgroup_move() so tests can observe move
delivery.
Signed-off-by: Tejun Heo <tj@kernel.org>
---
tools/sched_ext/scx_qmap.bpf.c | 80 +++++++++++++++++++++-----
tools/sched_ext/scx_qmap.c | 102 +--------------------------------
tools/sched_ext/scx_qmap.h | 3 +-
3 files changed, 69 insertions(+), 116 deletions(-)
diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c
index 09aee49120c2..8b8b14309466 100644
--- a/tools/sched_ext/scx_qmap.bpf.c
+++ b/tools/sched_ext/scx_qmap.bpf.c
@@ -1019,10 +1019,32 @@ s32 BPF_STRUCT_OPS(qmap_cgroup_init, struct cgroup *cgrp, struct scx_cgroup_init
return 0;
}
+static void redistribute(void);
+
void BPF_STRUCT_OPS(qmap_cgroup_set_weight, struct cgroup *cgrp, u32 weight)
{
+ u64 cgid = cgrp->kn->id;
+ s32 i;
+
+ QMAP_TOUCH_ARENA();
+
if (print_msgs)
- bpf_printk("CGRP SET %llu weight=%u", cgrp->kn->id, weight);
+ bpf_printk("CGRP SET %llu weight=%u", cgid, weight);
+
+ /*
+ * Knobs belong to the parent, so this op carries the child subs'
+ * attach point weights. Adjust the matching sub's share of the cid
+ * partition. Other cgroups don't participate in the split.
+ */
+ for (i = 0; i < MAX_SUB_SCHEDS; i++) {
+ if (qa.sub_sched_ctxs[i].cgroup_id != cgid)
+ continue;
+ if (qa.sub_sched_ctxs[i].weight != weight) {
+ qa.sub_sched_ctxs[i].weight = weight;
+ redistribute();
+ }
+ break;
+ }
}
void BPF_STRUCT_OPS(qmap_cgroup_set_bandwidth, struct cgroup *cgrp,
@@ -1033,6 +1055,14 @@ void BPF_STRUCT_OPS(qmap_cgroup_set_bandwidth, struct cgroup *cgrp,
cgrp->kn->id, period_us, quota_us, burst_us);
}
+void BPF_STRUCT_OPS(qmap_cgroup_move, struct task_struct *p,
+ struct cgroup *from, struct cgroup *to)
+{
+ if (print_msgs)
+ bpf_printk("CGRP MOVE %d %llu -> %llu",
+ p->pid, from->kn->id, to->kn->id);
+}
+
void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle)
{
QMAP_TOUCH_ARENA();
@@ -1336,10 +1366,12 @@ __noinline void compute_partition(void)
}
/*
- * Snapshot membership and weights so the sum_w and share loops agree.
- * A mid-compute change would otherwise wrap nr_shared negative.
+ * Snapshot membership and weights so the sum_w and share loops agree. A
+ * mid-compute change would otherwise wrap nr_shared negative. The self
+ * weight is fixed at the default: a cgroup's weight is its parent's
+ * knob, not the scheduler's own business.
*/
- self_w = qa.self_weight ?: 100;
+ self_w = 100;
bpf_for(i, 0, MAX_SUB_SCHEDS) {
cgid_snap[i] = qa.sub_sched_ctxs[i].cgroup_id;
w_snap[i] = cgid_snap[i] ? (qa.sub_sched_ctxs[i].weight ?: 100) : 0;
@@ -1565,7 +1597,7 @@ __noinline void apply_partition(void)
/*
* Recompute the split off the node's held caps and apply it. The contexts this
- * runs from (the sub-sched callbacks, the userspace poke, the rr timer) are not
+ * runs from (the sub-sched and cgroup callbacks, the rr timer) are not
* serialized by the kernel, so a single runner does the work. A caller that
* finds the guard held leaves part_pending set; the holder drains it before
* releasing, with the rr timer as a backstop.
@@ -1592,14 +1624,6 @@ static void redistribute(void)
part_end();
}
-/* userspace pokes this (PROG_RUN) to resplit after a cpu.weight change */
-SEC("syscall")
-int repartition(void *ctx)
-{
- redistribute();
- return 0;
-}
-
/*
* Userspace pokes this (PROG_RUN) to bring alloc_ns[] current before reading
* it for the stats display. Skipping when the partition guard is held is
@@ -1849,6 +1873,33 @@ void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
UEI_RECORD(uei, ei);
}
+/*
+ * Seed a new sub slot with the cgroup's current weight. The kernel delivers
+ * ops.cgroup_set_weight() only on value-changing writes, so a weight set
+ * before the sub attached would otherwise go unnoticed.
+ */
+static u32 cgrp_cur_weight(u64 cgid)
+{
+ struct cgroup_subsys_state *css;
+ struct cgroup *cgrp;
+ u32 weight = 100;
+
+ cgrp = bpf_cgroup_from_id(cgid);
+ if (!cgrp)
+ return weight;
+
+ css = BPF_CORE_READ(cgrp, subsys[cpu_cgrp_id]);
+ if (css) {
+ struct task_group *tg = container_of(css, struct task_group, css);
+ u32 w = BPF_CORE_READ(tg, scx.weight);
+
+ if (w)
+ weight = w;
+ }
+ bpf_cgroup_release(cgrp);
+ return weight;
+}
+
s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
{
s32 i;
@@ -1862,7 +1913,7 @@ s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
continue;
qa.sub_sched_ctxs[i].cgroup_id = args->ops->sub_cgroup_id;
- qa.sub_sched_ctxs[i].weight = 100; /* until userspace feeds it */
+ qa.sub_sched_ctxs[i].weight = cgrp_cur_weight(args->ops->sub_cgroup_id);
qa.nr_sub_scheds++;
bpf_printk("attaching sub-sched[%d] on %s", i, args->cgroup_path);
redistribute();
@@ -1926,6 +1977,7 @@ SCX_OPS_CID_DEFINE(qmap_ops,
.cgroup_init = (void *)qmap_cgroup_init,
.cgroup_set_weight = (void *)qmap_cgroup_set_weight,
.cgroup_set_bandwidth = (void *)qmap_cgroup_set_bandwidth,
+ .cgroup_move = (void *)qmap_cgroup_move,
.sub_attach = (void *)qmap_sub_attach,
.sub_detach = (void *)qmap_sub_detach,
.sub_caps_updated = (void *)qmap_sub_caps_updated,
diff --git a/tools/sched_ext/scx_qmap.c b/tools/sched_ext/scx_qmap.c
index dda3ddf5b749..105745f51b8e 100644
--- a/tools/sched_ext/scx_qmap.c
+++ b/tools/sched_ext/scx_qmap.c
@@ -24,11 +24,6 @@
#include "scx_qmap.h"
#include "scx_qmap.bpf.skel.h"
-/* kernfs file-handle type for open_by_handle_at(), from linux/exportfs.h */
-#ifndef FILEID_KERNFS
-#define FILEID_KERNFS 0xfe
-#endif
-
const char help_fmt[] =
"A simple five-level FIFO queue sched_ext scheduler.\n"
"\n"
@@ -72,7 +67,7 @@ const char help_fmt[] =
" -I Turn on SCX_OPS_ALWAYS_ENQ_IMMED\n"
" -F COUNT IMMED stress: force every COUNT'th enqueue to a busy local DSQ (use with -I)\n"
" -C MODE cid-override test (shuffle|bad-dup|bad-range|bad-mono)\n"
-" -i SEC Stats and weight-refresh interval, seconds (default 5)\n"
+" -i SEC Stats interval, seconds (default 5)\n"
" -R MS Round-robin period for time-shared cpus, ms (default 200)\n"
" -J MODE Fault injection (wrong-cid: dispatch to a cid not held)\n"
" -v Print libbpf debug messages\n"
@@ -93,83 +88,6 @@ static void sigint_handler(int dummy)
exit_req = 1;
}
-/*
- * Open a cgroup directory directly from its id. In cgroup2 the cgroup id is the
- * kernfs node id, so a FILEID_KERNFS handle built from the id resolves to the
- * directory via open_by_handle_at() against the cgroup mount.
- */
-static int open_cgroup_by_id(u64 cgid)
-{
- static int mnt_fd = -1;
- struct {
- struct file_handle fh;
- u64 id;
- } h;
-
- if (mnt_fd < 0) {
- mnt_fd = open("/sys/fs/cgroup", O_RDONLY | O_DIRECTORY);
- if (mnt_fd < 0)
- return -1;
- }
- h.fh.handle_bytes = sizeof(h.id);
- h.fh.handle_type = FILEID_KERNFS;
- h.id = cgid;
- return open_by_handle_at(mnt_fd, &h.fh, O_RDONLY | O_DIRECTORY);
-}
-
-/* read a cgroup's cpu.weight (1-10000) by id, 0 if unavailable */
-static u32 read_cgroup_weight(u64 cgid)
-{
- char buf[32];
- int dfd, wfd;
- u32 w = 0;
- ssize_t n;
-
- dfd = open_cgroup_by_id(cgid);
- if (dfd < 0)
- return 0;
- wfd = openat(dfd, "cpu.weight", O_RDONLY);
- close(dfd);
- if (wfd < 0)
- return 0;
- n = read(wfd, buf, sizeof(buf) - 1);
- close(wfd);
- if (n > 0) {
- buf[n] = '\0';
- w = strtoul(buf, NULL, 10);
- }
- return w;
-}
-
-/* read each direct child's cpu.weight into the arena, true if any changed */
-static bool feed_weights(struct qmap_arena *qa)
-{
- bool changed = false;
- int i;
-
- for (i = 0; i < MAX_SUB_SCHEDS; i++) {
- u64 cgid = qa->sub_sched_ctxs[i].cgroup_id;
- u32 w;
-
- if (!cgid)
- continue;
- /* racy against slot reuse but weight is advisory and self-corrects */
- w = read_cgroup_weight(cgid);
- if (w && w != qa->sub_sched_ctxs[i].weight) {
- qa->sub_sched_ctxs[i].weight = w;
- changed = true;
- }
- }
- return changed;
-}
-
-static void invoke_repartition(struct scx_qmap *skel)
-{
- LIBBPF_OPTS(bpf_test_run_opts, opts);
-
- bpf_prog_test_run_opts(bpf_program__fd(skel->progs.repartition), &opts);
-}
-
static void invoke_flush_alloc(struct scx_qmap *skel)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
@@ -281,7 +199,7 @@ static void print_hier(struct qmap_arena *qa, struct hier_prev *prev, u64 own_cg
format_cid_ranges(qa, CID_SELF, ranges, sizeof(ranges));
printf("hier : %-4s %10llu %4u %6.2f %8s %s\n", "self",
- (unsigned long long)own_cgid, qa->self_weight,
+ (unsigned long long)own_cgid, 100,
secs > 0 ? (qa->self_alloc_ns - prev->self_alloc_ns) / (secs * 1e9) : 0.0,
"-", ranges);
prev->self_alloc_ns = qa->self_alloc_ns;
@@ -505,8 +423,6 @@ int main(int argc, char **argv)
while (!exit_req && !UEI_EXITED(skel, uei)) {
long nr_enqueued = qa->nr_enqueued;
long nr_dispatched = qa->nr_dispatched;
- u32 self_weight;
- bool repart;
printf("---- %s ----\n",
tstamp(tbuf, sizeof(tbuf)));
@@ -531,20 +447,6 @@ int main(int argc, char **argv)
qa->cpuperf_target_avg,
qa->cpuperf_target_max);
- self_weight = own_cgid ? read_cgroup_weight(own_cgid) : 100;
- if (!self_weight)
- self_weight = 100;
-
- repart = feed_weights(qa);
-
- if (self_weight != qa->self_weight) {
- qa->self_weight = self_weight;
- repart = true;
- }
-
- if (repart)
- invoke_repartition(skel);
-
invoke_flush_alloc(skel);
print_hier(qa, &hprev, own_cgid);
fflush(stdout);
diff --git a/tools/sched_ext/scx_qmap.h b/tools/sched_ext/scx_qmap.h
index 87642d21fce3..6db2ea4bdc85 100644
--- a/tools/sched_ext/scx_qmap.h
+++ b/tools/sched_ext/scx_qmap.h
@@ -90,7 +90,7 @@ struct task_ctx;
/* per-direct-child state for the sub-scheduler */
struct sub_sched_ctx {
u64 cgroup_id;
- u32 weight; /* cpu.weight, fed by userspace */
+ u32 weight; /* cpu.weight, seeded at attach, then set_weight */
u64 nr_dsps;
struct qmap_cmask granted_cids; /* cids granted excl to this child */
struct qmap_cmask prev_granted; /* last grant, for delta calculation */
@@ -147,7 +147,6 @@ struct qmap_arena {
struct sub_sched_ctx sub_sched_ctxs[MAX_SUB_SCHEDS]; /* per-child context */
u64 nr_sub_scheds; /* number of attached children */
- u32 self_weight; /* this node's cpu.weight, fed by userspace */
/* bpf-internal per-cid state */
u8 cid_shared[SCX_QMAP_MAX_CPUS]; /* per cid: 1 if held shared (ENQ_IMMED-only) */
--
2.55.0
next prev parent reply other threads:[~2026-07-18 8:17 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-18 8:17 [PATCHSET sched_ext/for-7.3] sched_ext: Cgroup migration and op delivery for sub-schedulers Tejun Heo
2026-07-18 8:17 ` [PATCH 1/8] cgroup: Add cgroup_task_notifier and task migration events Tejun Heo
2026-07-18 8:17 ` [PATCH 2/8] sched_ext: Factor out scx_rehome_task() and scx_punt_task() Tejun Heo
2026-07-18 8:17 ` [PATCH 3/8] sched_ext: Relocate scx_cgroup_enabled Tejun Heo
2026-07-18 8:17 ` [PATCH 4/8] sched_ext: Re-home tasks on cgroup migration Tejun Heo
2026-07-18 8:17 ` [PATCH 5/8] sched_ext: Deliver cgroup ops to each task_group's sched Tejun Heo
2026-07-18 8:17 ` [PATCH 6/8] sched_ext: Hand over cgroups at sub-scheduler enable/disable Tejun Heo
2026-07-18 8:17 ` Tejun Heo [this message]
2026-07-18 8:17 ` [PATCH 8/8] tools/sched_ext: scx_qmap - Add init fault injection modes Tejun Heo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260718081727.582037-8-tj@kernel.org \
--to=tj@kernel.org \
--cc=arighi@nvidia.com \
--cc=cgroups@vger.kernel.org \
--cc=changwoo@igalia.com \
--cc=emil@etsalapatis.com \
--cc=hannes@cmpxchg.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mkoutny@suse.com \
--cc=sched-ext@lists.linux.dev \
--cc=void@manifault.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.