Linux 7.3: A BPF Scheduler Per Cgroup
Linux 7.3 completes nested sched_ext, so a cgroup subtree can run its own BPF scheduler. Here is how sub-schedulers work and how to try sched_ext on a lab box.
BY ASHLIN DARIUS GOVINDASAMY
What if the kernel's CPU scheduler were not one fixed algorithm but a program you could swap at runtime, and what if every container could bring its own? That is the direction Linux 7.3 pushes: sched_ext, the extensible BPF scheduler class, now has cgroup sub-scheduler support that is described as feature complete. In this guide we explain what nested sched_ext means, how a parent scheduler hands a cgroup subtree to a child, and how to try sched_ext safely on a lab machine.
What shipped in Linux 7.3
Linus Torvalds released Linux 7.3 on 18 October 2026. Coverage of the release lists, among other things, a reworked way of splitting CPU time between groups of applications and completed support for nested sched_ext schedulers. Phoronix reports that the enqueue path for hierarchical sub-scheduling is now finished, and that parents can grant and revoke per-CPU capabilities for enqueueing, preemption and CPU frequency control. Before this, only dispatching could be delegated.
The LTS question is still open. OMG Ubuntu expects 7.3 to be the year's long term kernel, but other reporting says the community is weighing 7.3 against 7.4. Treat LTS as unconfirmed until the stable maintainers say so.
A 90 second refresher on sched_ext
sched_ext lets you write a scheduling policy as a BPF program. The kernel verifies it, loads it through the struct_ops mechanism and runs it as its own scheduling class. If your scheduler crashes, stalls a task for too long or misbehaves, the kernel aborts it and falls back to the default scheduler, so a bad experiment does not brick the box. That safety net is why people are comfortable trying exotic policies, from latency-first desktop schedulers to cache-topology-aware ones for servers.
A BPF scheduler is mostly a handful of callbacks. Tasks that become runnable are enqueued, a CPU that runs dry is asked to dispatch, and in between they wait on dispatch queues (DSQs). The simplest policy is one global FIFO queue shared by all CPUs.
The problem: one scheduler for everyone
A single machine often runs very different tenants: a latency-sensitive game server, a batch build farm, an AI inference worker pinned to a few cores. One policy cannot be optimal for all of them. Until now sched_ext gave you one BPF scheduler for the whole system. Nested sched_ext lets a parent scheduler own a cgroup subtree's CPUs and delegate decisions inside that subtree to a different scheduler, just like cgroups already delegate memory and IO limits down a hierarchy.
How sub-schedulers attach
The design is visible in Tejun Heo's patch series on the kernel mailing list. These details come from the v3 series posted for the 7.1 cycle, as mirrored on ratatoskr.run, so check the final 7.3 source before you depend on any name. In that series a scheduler declares which cgroup it wants through a sub_cgroup_id field in its ops. A non-zero ID marks it as a sub-scheduler. It only attaches if the nearest ancestor scheduler implements a sub_attach callback and agrees, and a matching sub_detach callback handles teardown. Nesting depth is capped by a constant named SCX_SUB_MAX_DEPTH.
- Each cgroup carries a pointer to its effective scheduler, so the kernel knows which program decides for a given task.
- If a scheduler exits, its descendants exit with it, and a cgroup that goes offline has its scheduler exited automatically.
- The feature is behind CONFIG_EXT_SUB_SCHED, which in that series is enabled automatically when both sched_ext and cgroups are on.
- The sample scx_qmap scheduler was expanded into a demo of hierarchical scheduling: a parent splits its exclusive CPU IDs among itself and its children according to cpu.weight, and the leftovers form a time-shared pool.
- A July 2026 patchset fixes tasks that move across a sub-scheduler boundary, which previously could keep a stale scheduler and risk a use-after-free.
QUICK CHECK
In nested sched_ext, what decides whether a sub-scheduler is allowed to attach to a cgroup?
Hands on: try sched_ext on a lab box
Do this on a disposable machine or VM, not on production. You need a kernel built with sched_ext and BTF. First check what you have.
uname -r
zcat /proc/config.gz 2>/dev/null | grep -E 'SCHED_CLASS_EXT|EXT_SUB_SCHED|DEBUG_INFO_BTF=' \
|| grep -E 'SCHED_CLASS_EXT|EXT_SUB_SCHED|DEBUG_INFO_BTF=' /boot/config-$(uname -r)
# empty or "disabled" until a scheduler is loaded
cat /sys/kernel/sched_ext/state
ls /sys/kernel/sched_ext/The kernel source ships example schedulers under tools/sched_ext. The simplest, scx_simple, is a few dozen lines and is the best thing to read first. Building needs clang, libbpf, bpftool and a recent toolchain.
git clone --depth 1 --branch v7.3 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
make -C tools/sched_ext
sudo ./tools/sched_ext/build/bin/scx_simple # Ctrl-C returns to the default scheduler
# in another terminal, while it runs
cat /sys/kernel/sched_ext/state # enabled
cat /sys/kernel/sched_ext/root/ops # the loaded scheduler's nameBelow is the shape of a minimal global-FIFO scheduler. It is a sketch modelled on scx_simple, and the helper names have been renamed between kernel releases, so diff it against tools/sched_ext/scx_simple.bpf.c in your tree before compiling.
#include <scx/common.bpf.h>
char _license[] SEC("license") = "GPL";
#define SHARED_DSQ 0
s32 BPF_STRUCT_OPS_SLEEPABLE(mysched_init)
{
return scx_bpf_create_dsq(SHARED_DSQ, -1);
}
void BPF_STRUCT_OPS(mysched_enqueue, struct task_struct *p, u64 enq_flags)
{
scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, enq_flags);
}
void BPF_STRUCT_OPS(mysched_dispatch, s32 cpu, struct task_struct *prev)
{
scx_bpf_dsq_move_to_local(SHARED_DSQ);
}
SCX_OPS_DEFINE(mysched_ops,
.init = (void *)mysched_init,
.enqueue = (void *)mysched_enqueue,
.dispatch = (void *)mysched_dispatch,
.name = "mysched");Gotchas before you go per-cgroup
- Cgroup v2 only. Put the workloads you want to isolate in their own subtrees first, for example with systemd-run --slice, and keep the hierarchy shallow.
- Review reports on the patch series flagged races between attaching, detaching and CPU partition computation, so run recent stable point releases of 7.3 rather than the first tag you find.
- A parent that hands out exclusive CPUs reduces what everyone else can use. Measure the whole machine, not just the favoured tenant.
- Observability matters more than cleverness. Start from a schedule you can reason about, then compare tail latency before and after.
›Deep dive: why delegate scheduling at all?
Cgroup controllers such as CPU weight already shape how much CPU a group receives, but the policy inside the group is still the system-wide one. With sub-schedulers, a tenant that knows its workload, for example a game server that wants its simulation threads to run together or an inference service that wants to avoid migrating across cache domains, can encode that knowledge in its own scheduler without forcing it on its neighbours. It also makes scheduler research safer, because an experiment can be confined to a subtree while the rest of the system keeps a well-tested policy.
Takeaway
Linux 7.3 turns sched_ext from one system-wide experiment into something that follows the cgroup hierarchy. You do not need to write a scheduler to benefit, but it is now worth understanding the model, because the next wave of container and AI-serving tools will likely lean on it. Build scx_simple on a throwaway VM this weekend and read the code; it takes ten minutes and demystifies the scheduler for good.
SOURCES
- Sched_ext's Sub-Scheduler Support Now Feature Complete With Linux 7.3 — Phoronix
- Linux 7.3 features — OMG Ubuntu
- Linux 7.3 release: Main Changes, Arm, RISC-V, and MIPS architectures — CNX Software
- [PATCHSET v3 sched_ext/for-7.1] sched_ext: Implement cgroup sub-scheduler support — Linux kernel mailing list (mirror)
- [PATCHSET sched_ext/for-7.3] sched_ext: Cgroup migration and op delivery for sub-schedulers — Linux kernel mailing list (mirror)
Frequently asked questions
What is nested sched_ext in Linux 7.3?
It is support for running a BPF scheduler inside a cgroup subtree under a parent BPF scheduler. The parent delegates CPUs and decisions for that subtree to the child, and Linux 7.3 is reported to make this feature complete.
Do I need to write a scheduler to benefit from sched_ext?
No. The kernel tree ships example schedulers such as scx_simple, and the community maintains ready-made ones you can load without writing BPF yourself. Writing your own is optional.
Is it safe to try sched_ext on a real machine?
It is designed to fail safe, because the kernel aborts a misbehaving BPF scheduler and falls back to the default one. Still, try it on a lab machine or VM first.
Will Linux 7.3 be an LTS kernel?
Not confirmed. Some outlets expect it, while others say the community is weighing 7.3 against 7.4, so wait for the stable kernel maintainers to announce it.
NEWSLETTER
Get the engineering notes in your inbox.
Kernels, GPUs, networks, AI infrastructure — and the things we broke along the way. No spam, one click to leave.
We store only your email address, to send you new articles. See our privacy policy.
TAGS
RELATED
Linux 7.2 Cache-Aware Scheduling: Keep Threads Close
Linux 7.2 added cache-aware scheduling that packs a process's threads onto one LLC. See how SCHED_CACHE works, the debugfs knobs, and how to benchmark it.
Cap CPU and RAM with cgroup v2 and systemd-run
Learn how Linux cgroup v2 limits CPU, memory and process counts, and use systemd-run to cap a runaway job in one command, with the raw files to inspect.