sched_ext: Introduce cgroup sub-sched support

A system often runs multiple workloads especially in multi-tenant server
environments where a system is split into partitions servicing separate
more-or-less independent workloads each requiring an application-specific
scheduler. To support such and other use cases, sched_ext is in the process
of growing multiple scheduler support.

When partitioning a system in terms of CPUs for such use cases, an
oft-taken approach is hard partitioning the system using cpuset. While it
would be possible to tie sched_ext multiple scheduler support to cpuset
partitions, such an approach would have fundamental limitations stemming
from the lack of dynamism and flexibility.

Users often don't care which specific CPUs are assigned to which workload
and want to take advantage of optimizations which are enabled by running
workloads on a larger machine - e.g. opportunistic over-commit, improving
latency critical workload characteristics while maintaining bandwidth
fairness, employing control mechanisms based on different criteria than
on-CPU time for e.g. flexible memory bandwidth isolation, packing similar
parts from different workloads on same L3s to improve cache efficiency,
and so on.

As this sort of dynamic behaviors are impossible or difficult to implement
with hard partitioning, sched_ext is implementing cgroup sub-sched support
where schedulers can be attached to the cgroup hierarchy and a parent
scheduler is responsible for controlling the CPUs that each child can use
at any given moment. This makes CPU distribution dynamically controlled by
BPF allowing high flexibility.

This patch adds the skeletal sched_ext cgroup sub-sched support:

- sched_ext_ops.sub_cgroup_id and .sub_attach/detach() are added. Non-zero
  sub_cgroup_id indicates that the scheduler is to be attached to the
  identified cgroup. A sub-sched is attached to the cgroup iff the nearest
  ancestor scheduler implements .sub_attach() and grants the attachment. Max
  nesting depth is limited by SCX_SUB_MAX_DEPTH.

- When a scheduler exits, all its descendant schedulers are exited
  together. Also, cgroup.scx_sched added which points to the effective
  scheduler instance for the cgroup. This is updated on scheduler
  init/exit and inherited on cgroup online. When a cgroup is offlined, the
  attached scheduler is automatically exited.

- Sub-sched support is gated on CONFIG_EXT_SUB_SCHED which is
  automatically enabled if both SCX and cgroups are enabled. Sub-sched
  support is not tied to the CPU controller but rather the cgroup
  hierarchy itself. This is intentional as the support for cpu.weight and
  cpu.max based resource control is orthogonal to sub-sched support. Note
  that CONFIG_CGROUPS around cgroup subtree iteration support for
  scx_task_iter is replaced with CONFIG_EXT_SUB_SCHED for consistency.

- This allows loading sub-scheds and most framework operations such as
  propagating disable down the hierarchy work. However, sub-scheds are not
  operational yet and all tasks stay with the root sched. This will serve
  as the basis for building up full sub-sched support.

- DSQs point to the scx_sched they belong to.

- scx_qmap is updated to allow attachment of sub-scheds and also serving
  as sub-scheds.

- scx_is_descendant() is added but not yet used in this patch. It is used by
  later changes in the series and placed here as this is where the function
  belongs.

Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Andrea Righi <arighi@nvidia.com>
This commit is contained in:
Tejun Heo
2026-03-06 07:58:03 -10:00
parent dbd542a8fa
commit ebeca1f930
7 changed files with 596 additions and 36 deletions
+4
View File
@@ -17,6 +17,7 @@
#include <linux/refcount.h>
#include <linux/percpu-refcount.h>
#include <linux/percpu-rwsem.h>
#include <linux/sched.h>
#include <linux/u64_stats_sync.h>
#include <linux/workqueue.h>
#include <linux/bpf-cgroup-defs.h>
@@ -624,6 +625,9 @@ struct cgroup {
#ifdef CONFIG_BPF_SYSCALL
struct bpf_local_storage __rcu *bpf_cgrp_storage;
#endif
#ifdef CONFIG_EXT_SUB_SCHED
struct scx_sched __rcu *scx_sched;
#endif
/* All ancestors including self */
union {
+3
View File
@@ -78,6 +78,7 @@ struct scx_dispatch_q {
u64 id;
struct rhash_head hash_node;
struct llist_node free_node;
struct scx_sched *sched;
struct rcu_head rcu;
};
@@ -157,6 +158,8 @@ struct scx_dsq_list_node {
.priv = (__priv), \
}
struct scx_sched;
/*
* The following is embedded in task_struct and contains all fields necessary
* for a task to be scheduled by SCX.
+4
View File
@@ -1176,6 +1176,10 @@ config EXT_GROUP_SCHED
endif #CGROUP_SCHED
config EXT_SUB_SCHED
def_bool y
depends on SCHED_CLASS_EXT
config SCHED_MM_CID
def_bool y
depends on SMP && RSEQ
+504 -28
View File
File diff suppressed because it is too large Load Diff
+61 -6
View File
@@ -28,6 +28,8 @@ enum scx_consts {
SCX_BYPASS_LB_DONOR_PCT = 125,
SCX_BYPASS_LB_MIN_DELTA_DIV = 4,
SCX_BYPASS_LB_BATCH = 256,
SCX_SUB_MAX_DEPTH = 4,
};
enum scx_exit_kind {
@@ -38,6 +40,7 @@ enum scx_exit_kind {
SCX_EXIT_UNREG_BPF, /* BPF-initiated unregistration */
SCX_EXIT_UNREG_KERN, /* kernel-initiated unregistration */
SCX_EXIT_SYSRQ, /* requested by 'S' sysrq */
SCX_EXIT_PARENT, /* parent exiting */
SCX_EXIT_ERROR = 1024, /* runtime error, error msg contains details */
SCX_EXIT_ERROR_BPF, /* ERROR but triggered through scx_bpf_error() */
@@ -62,6 +65,7 @@ enum scx_exit_kind {
enum scx_exit_code {
/* Reasons */
SCX_ECODE_RSN_HOTPLUG = 1LLU << 32,
SCX_ECODE_RSN_CGROUP_OFFLINE = 2LLU << 32,
/* Actions */
SCX_ECODE_ACT_RESTART = 1LLU << 48,
@@ -213,7 +217,7 @@ struct scx_exit_task_args {
bool cancelled;
};
/* argument container for ops->cgroup_init() */
/* argument container for ops.cgroup_init() */
struct scx_cgroup_init_args {
/* the weight of the cgroup [1..10000] */
u32 weight;
@@ -236,12 +240,12 @@ enum scx_cpu_preempt_reason {
};
/*
* Argument container for ops->cpu_acquire(). Currently empty, but may be
* Argument container for ops.cpu_acquire(). Currently empty, but may be
* expanded in the future.
*/
struct scx_cpu_acquire_args {};
/* argument container for ops->cpu_release() */
/* argument container for ops.cpu_release() */
struct scx_cpu_release_args {
/* the reason the CPU was preempted */
enum scx_cpu_preempt_reason reason;
@@ -250,9 +254,7 @@ struct scx_cpu_release_args {
struct task_struct *task;
};
/*
* Informational context provided to dump operations.
*/
/* informational context provided to dump operations */
struct scx_dump_ctx {
enum scx_exit_kind kind;
s64 exit_code;
@@ -261,6 +263,18 @@ struct scx_dump_ctx {
u64 at_jiffies;
};
/* argument container for ops.sub_attach() */
struct scx_sub_attach_args {
struct sched_ext_ops *ops;
char *cgroup_path;
};
/* argument container for ops.sub_detach() */
struct scx_sub_detach_args {
struct sched_ext_ops *ops;
char *cgroup_path;
};
/**
* struct sched_ext_ops - Operation table for BPF scheduler implementation
*
@@ -721,6 +735,20 @@ struct sched_ext_ops {
#endif /* CONFIG_EXT_GROUP_SCHED */
/**
* @sub_attach: Attach a sub-scheduler
* @args: argument container, see the struct definition
*
* Return 0 to accept the sub-scheduler. -errno to reject.
*/
s32 (*sub_attach)(struct scx_sub_attach_args *args);
/**
* @sub_detach: Detach a sub-scheduler
* @args: argument container, see the struct definition
*/
void (*sub_detach)(struct scx_sub_detach_args *args);
/*
* All online ops must come before ops.cpu_online().
*/
@@ -762,6 +790,10 @@ struct sched_ext_ops {
*/
void (*exit)(struct scx_exit_info *info);
/*
* Data fields must comes after all ops fields.
*/
/**
* @dispatch_max_batch: Max nr of tasks that dispatch() can dispatch
*/
@@ -796,6 +828,12 @@ struct sched_ext_ops {
*/
u64 hotplug_seq;
/**
* @cgroup_id: When >1, attach the scheduler as a sub-scheduler on the
* specified cgroup.
*/
u64 sub_cgroup_id;
/**
* @name: BPF scheduler's name
*
@@ -900,6 +938,8 @@ struct scx_sched {
struct scx_dispatch_q **global_dsqs;
struct scx_sched_pcpu __percpu *pcpu;
s32 level;
/*
* Updates to the following warned bitfields can race causing RMW issues
* but it doesn't really matter.
@@ -907,6 +947,18 @@ struct scx_sched {
bool warned_zero_slice:1;
bool warned_deprecated_rq:1;
struct list_head all;
#ifdef CONFIG_EXT_SUB_SCHED
struct list_head children;
struct list_head sibling;
struct cgroup *cgrp;
char *cgrp_path;
struct kset *sub_kset;
bool sub_attached;
#endif /* CONFIG_EXT_SUB_SCHED */
atomic_t exit_kind;
struct scx_exit_info *exit_info;
@@ -916,6 +968,9 @@ struct scx_sched {
struct irq_work error_irq_work;
struct kthread_work disable_work;
struct rcu_work rcu_work;
/* all ancestors including self */
struct scx_sched *ancestors[];
};
enum scx_wake_flags {
+8 -1
View File
@@ -41,6 +41,7 @@ const volatile u32 dsp_batch;
const volatile bool highpri_boosting;
const volatile bool print_dsqs_and_events;
const volatile bool print_msgs;
const volatile u64 sub_cgroup_id;
const volatile s32 disallow_tgid;
const volatile bool suppress_dump;
@@ -862,7 +863,7 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
struct bpf_timer *timer;
s32 ret;
if (print_msgs)
if (print_msgs && !sub_cgroup_id)
print_cpus();
ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
@@ -892,6 +893,11 @@ void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
UEI_RECORD(uei, ei);
}
s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
{
return 0;
}
SCX_OPS_DEFINE(qmap_ops,
.select_cpu = (void *)qmap_select_cpu,
.enqueue = (void *)qmap_enqueue,
@@ -907,6 +913,7 @@ SCX_OPS_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,
.sub_attach = (void *)qmap_sub_attach,
.cpu_online = (void *)qmap_cpu_online,
.cpu_offline = (void *)qmap_cpu_offline,
.init = (void *)qmap_init,
+12 -1
View File
@@ -10,6 +10,7 @@
#include <inttypes.h>
#include <signal.h>
#include <libgen.h>
#include <sys/stat.h>
#include <bpf/bpf.h>
#include <scx/common.h>
#include "scx_qmap.bpf.skel.h"
@@ -67,7 +68,7 @@ int main(int argc, char **argv)
skel->rodata->slice_ns = __COMPAT_ENUM_OR_ZERO("scx_public_consts", "SCX_SLICE_DFL");
while ((opt = getopt(argc, argv, "s:e:t:T:l:b:PMHd:D:Spvh")) != -1) {
while ((opt = getopt(argc, argv, "s:e:t:T:l:b:PMHc:d:D:Spvh")) != -1) {
switch (opt) {
case 's':
skel->rodata->slice_ns = strtoull(optarg, NULL, 0) * 1000;
@@ -96,6 +97,16 @@ int main(int argc, char **argv)
case 'H':
skel->rodata->highpri_boosting = true;
break;
case 'c': {
struct stat st;
if (stat(optarg, &st) < 0) {
perror("stat");
return 1;
}
skel->struct_ops.qmap_ops->sub_cgroup_id = st.st_ino;
skel->rodata->sub_cgroup_id = st.st_ino;
break;
}
case 'd':
skel->rodata->disallow_tgid = strtol(optarg, NULL, 0);
if (skel->rodata->disallow_tgid < 0)