Linux cgroups development
 help / color / mirror / Atom feed
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


  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