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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox