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 1/8] cgroup: Add cgroup_task_notifier and task migration events
Date: Fri, 17 Jul 2026 22:17:20 -1000 [thread overview]
Message-ID: <20260718081727.582037-2-tj@kernel.org> (raw)
In-Reply-To: <20260718081727.582037-1-tj@kernel.org>
A subsystem can attach to the cgroup hierarchy itself, independent of which
controllers are enabled where - BPF hooks already behave this way and
sched_ext sub-schedulers do too. Controller callbacks can't track task
migrations for them: sched_ext must re-home a task whose migration crosses a
sub-scheduler boundary, but the cpu controller's attach callbacks fire only
when the task_group changes and miss moves whenever the controller topology
is coarser than the sub-scheduler topology.
Add cgroup_task_notifier with per-task migration events mirroring the
can_attach/attach/cancel_attach phases so that a consumer which prepares
per-task state can also veto a migration: CGROUP_TASK_MIGRATING fires
pre-commit, CGROUP_TASK_MIGRATED post-commit and
CGROUP_TASK_MIGRATE_CANCELED unwinds a failed migration. Only migrations
that change a task's dfl cgroup are reported.
Signed-off-by: Tejun Heo <tj@kernel.org>
---
include/linux/cgroup.h | 26 ++++++++++++
kernel/cgroup/cgroup.c | 93 ++++++++++++++++++++++++++++++++++++++----
2 files changed, 110 insertions(+), 9 deletions(-)
diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h
index f2aa46a4f871..aa92db5f05de 100644
--- a/include/linux/cgroup.h
+++ b/include/linux/cgroup.h
@@ -82,12 +82,38 @@ enum cgroup_lifetime_events {
CGROUP_LIFETIME_OFFLINE,
};
+/*
+ * Events on cgroup_task_notifier, data is struct cgroup_task_migrate_ctx.
+ * MIGRATING fires per task before the migration commits and an error return
+ * from the chain fails the migration, in which case tasks that were already
+ * notified receive MIGRATE_CANCELED. MIGRATED fires per task after the
+ * migration is committed and can't fail. Only migrations that change a task's
+ * dfl cgroup are reported.
+ */
+enum cgroup_task_events {
+ CGROUP_TASK_MIGRATING,
+ CGROUP_TASK_MIGRATED,
+ CGROUP_TASK_MIGRATE_CANCELED,
+};
+
+/*
+ * @src_dcgrp and @dst_dcgrp are @task's dfl cgroups before and after the
+ * migration. @src_dcgrp is NULL for CGROUP_TASK_MIGRATED as per-task sources
+ * are not tracked past the commit point.
+ */
+struct cgroup_task_migrate_ctx {
+ struct task_struct *task;
+ struct cgroup *src_dcgrp;
+ struct cgroup *dst_dcgrp;
+};
+
extern struct file_system_type cgroup_fs_type;
extern struct cgroup_root cgrp_dfl_root;
extern struct css_set init_css_set;
extern struct mutex cgroup_mutex;
extern spinlock_t css_set_lock;
extern struct blocking_notifier_head cgroup_lifetime_notifier;
+extern struct blocking_notifier_head cgroup_task_notifier;
#define SUBSYS(_x) extern struct cgroup_subsys _x ## _cgrp_subsys;
#include <linux/cgroup_subsys.h>
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 38f8d9df8fbc..2f6b634c84ca 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -88,6 +88,8 @@ EXPORT_SYMBOL_GPL(css_set_lock);
struct blocking_notifier_head cgroup_lifetime_notifier =
BLOCKING_NOTIFIER_INIT(cgroup_lifetime_notifier);
+struct blocking_notifier_head cgroup_task_notifier =
+ BLOCKING_NOTIFIER_INIT(cgroup_task_notifier);
DEFINE_SPINLOCK(trace_cgroup_path_lock);
char trace_cgroup_path[TRACE_CGROUP_PATH_LEN];
@@ -2676,14 +2678,27 @@ struct task_struct *cgroup_taskset_next(struct cgroup_taskset *tset,
return NULL;
}
+static void cgroup_migrate_notify_canceled(struct css_set *src_cset,
+ struct task_struct *task)
+{
+ struct cgroup_task_migrate_ctx ctx = {
+ .task = task,
+ .src_dcgrp = src_cset->dfl_cgrp,
+ .dst_dcgrp = src_cset->mg_dst_cset->dfl_cgrp,
+ };
+
+ blocking_notifier_call_chain(&cgroup_task_notifier,
+ CGROUP_TASK_MIGRATE_CANCELED, &ctx);
+}
+
/**
* cgroup_migrate_execute - migrate a taskset
* @mgctx: migration context
*
- * Migrate tasks in @mgctx as setup by migration preparation functions.
- * This function fails iff one of the ->can_attach callbacks fails and
- * guarantees that either all or none of the tasks in @mgctx are migrated.
- * @mgctx is consumed regardless of success.
+ * Migrate tasks in @mgctx as setup by migration preparation functions. This
+ * function fails iff one of the ->can_attach callbacks or CGROUP_TASK_MIGRATING
+ * notifications fails and guarantees that either all or none of the tasks in
+ * @mgctx are migrated. @mgctx is consumed regardless of success.
*/
static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
{
@@ -2691,6 +2706,7 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
struct cgroup_subsys *ss;
struct task_struct *task, *tmp_task;
struct css_set *cset, *tmp_cset;
+ bool dfl_migration = false;
int ssid, failed_ssid, ret;
/* check that we can legitimately attach to the cgroup */
@@ -2707,6 +2723,33 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
} while_each_subsys_mask();
}
+ /*
+ * Notify each task about the impending migration. An error return fails
+ * the migration. Only migrations on the default hierarchy are reported:
+ * a migration modifies either every moved task's dfl cgroup or, on
+ * cgroup1 or for subtree_control writes, none.
+ */
+ list_for_each_entry(cset, &tset->src_csets, mg_node) {
+ if (cset->dfl_cgrp == cset->mg_dst_cset->dfl_cgrp)
+ continue;
+ dfl_migration = true;
+ list_for_each_entry(task, &cset->mg_tasks, cg_list) {
+ struct cgroup_task_migrate_ctx ctx = {
+ .task = task,
+ .src_dcgrp = cset->dfl_cgrp,
+ .dst_dcgrp = cset->mg_dst_cset->dfl_cgrp,
+ };
+
+ ret = blocking_notifier_call_chain_robust(&cgroup_task_notifier,
+ CGROUP_TASK_MIGRATING,
+ CGROUP_TASK_MIGRATE_CANCELED,
+ &ctx);
+ ret = notifier_to_errno(ret);
+ if (ret)
+ goto out_cancel_migrating;
+ }
+ }
+
/*
* Now that we're guaranteed success, proceed to move all tasks to
* the new cgroup. There are no failure cases after here, so this
@@ -2750,9 +2793,41 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
} while_each_subsys_mask();
}
+ /*
+ * Notify each task after successful migration. The operation can no
+ * longer fail and the return value is ignored. The MIGRATING loop
+ * above explains why only dfl migrations are reported. Per-task
+ * sources are not tracked past the commit point, so src_dcgrp is
+ * NULL.
+ */
+ if (dfl_migration) {
+ list_for_each_entry(cset, &tset->dst_csets, mg_node) {
+ list_for_each_entry(task, &cset->mg_tasks, cg_list) {
+ struct cgroup_task_migrate_ctx ctx = {
+ .task = task,
+ .dst_dcgrp = cset->dfl_cgrp,
+ };
+
+ blocking_notifier_call_chain(
+ &cgroup_task_notifier,
+ CGROUP_TASK_MIGRATED, &ctx);
+ }
+ }
+ }
+
ret = 0;
goto out_release_tset;
+out_cancel_migrating:
+ list_for_each_entry_continue_reverse(task, &cset->mg_tasks, cg_list)
+ cgroup_migrate_notify_canceled(cset, task);
+ list_for_each_entry_continue_reverse(cset, &tset->src_csets, mg_node) {
+ if (cset->dfl_cgrp == cset->mg_dst_cset->dfl_cgrp)
+ continue;
+ list_for_each_entry_reverse(task, &cset->mg_tasks, cg_list)
+ cgroup_migrate_notify_canceled(cset, task);
+ }
+ failed_ssid = CGROUP_SUBSYS_COUNT;
out_cancel_attach:
if (tset->nr_tasks) {
do_each_subsys_mask(ss, ssid, mgctx->ss_mask) {
@@ -2976,11 +3051,11 @@ int cgroup_migrate_prepare_dst(struct cgroup_mgctx *mgctx)
* cgroup_migrate_prepare_dst() on the targets before invoking this
* function and following up with cgroup_migrate_finish().
*
- * As long as a controller's ->can_attach() doesn't fail, this function is
- * guaranteed to succeed. This means that, excluding ->can_attach()
- * failure, when migrating multiple targets, the success or failure can be
- * decided for all targets by invoking group_migrate_prepare_dst() before
- * actually starting migrating.
+ * As long as a controller's ->can_attach() or a CGROUP_TASK_MIGRATING
+ * notification doesn't fail, this function is guaranteed to succeed. This
+ * means that, excluding those failures, when migrating multiple targets,
+ * the success or failure can be decided for all targets by invoking
+ * group_migrate_prepare_dst() before actually starting migrating.
*/
int cgroup_migrate(struct task_struct *leader, bool threadgroup,
struct cgroup_mgctx *mgctx)
--
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 ` Tejun Heo [this message]
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 ` [PATCH 7/8] tools/sched_ext: scx_qmap - Consume cgroup weights through set_weight Tejun Heo
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-2-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