Skip to content

필사 모드: sched_ext Sub-Schedulers — the Per-cgroup Scheduler That Arrived Half-Done in Kernel 7.1

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

Introduction — The Gap Between the Headline and the Commit Log

Linux 7.1 was released on June 14, 2026. One of the scheduler headlines was this — sched_ext gained cgroup sub-scheduler support. Read as a sentence alone, that's a fairly big deal. It means you can attach a different CPU scheduler to each cgroup. If you run containers, that should catch your ear.

But read the same news in LWN's merge-window summary, and the tone is markedly different. Jonathan Corbet wrote this in his summary of the 7.1 merge window. Quoting it verbatim:

The extensible scheduler class (sched_ext) subsystem now has the beginning of support for sub-schedulers, meaning that each control group can run with its own custom CPU scheduler. As of 7.1, this implementation is not complete (it lacks the enqueue path in particular), but much of the needed core structure is there.

The key is the second sentence — as of 7.1, this implementation is not complete, and the enqueue path in particular is missing. What landed is "the beginning of sub-scheduler support," not sub-schedulers themselves.

This post is about that gap. What the feature is trying to do, why it's needed, what actually landed in 7.1, what's still missing, and therefore what you should do right now (mostly, "nothing").

Recap — What sched_ext Was

The evolution from CFS to EEVDF has its own separate writeup, so here we'll just recap sched_ext briefly.

sched_ext is the extensible scheduler class merged into kernel 6.12 (it originally targeted 6.11 and slipped one cycle). It lets you write scheduling policy as a BPF program and load or swap it at runtime. Fill in the callbacks in struct sched_ext_ops and load it, and from that point the BPF code decides which task runs on which CPU and when.

The single most important design decision here is the safety net. To quote the kernel documentation directly:

System integrity is maintained no matter what the BPF scheduler does. The default scheduling behavior is restored anytime an error is detected, a runnable task stalls, or on invoking the SysRq key sequence SysRq-S.

No matter what the BPF scheduler does, system integrity is preserved, and if an error is detected or a runnable task stalls, the kernel automatically falls back to default scheduling behavior. You can also trigger the fallback by hand with SysRq-S. It's this watchdog that makes the seemingly reckless idea of "swap out the kernel scheduler for user code" actually hold up.

Why Sub-Schedulers — The Limits of cpuset

Tejun Heo's patch series cover letter lays out the problem well. The setup is this: a single large machine carries several workloads with different characters. Each wants a different scheduling policy. Latency-sensitive services, throughput-oriented batch jobs, interactivity-sensitive things like games — all mixed in one box.

The answer so far has been hard partitioning. Quoting the cover letter directly:

Traditional approaches rely on hard partitioning via cpuset, but this approach lacks the dynamism required by modern workloads.

Splitting up CPUs with cpuset lacks the dynamism that modern workloads require. The cover letter is also specific about why it falls short.

Users typically care less about specific CPU assignments and more about optimizations available on larger machines: opportunistic over-commit, improved latency for critical workloads while preserving bandwidth fairness, control mechanisms beyond simple CPU time (such as memory bandwidth isolation), and intelligent placement to optimize cache locality.

In other words: what people actually want isn't "this workload gets cores 3 through 7." They want the things you can only get by sharing a large machine — opportunistic over-commit, improving latency for important workloads while still preserving bandwidth fairness, control beyond plain CPU time (like memory bandwidth isolation), and placement that's smart about cache locality. Slice up the CPUs ahead of time and you get none of this — you're just playing inside your assigned slice.

And the answer to why the kernel can't just do this for you is in the second-version cover letter, as quoted by Phoronix:

Applications often have domain-specific knowledge that generic schedulers cannot possess. Database systems understand query priorities and lock holder criticality.

Applications have domain-specific knowledge a generic scheduler simply cannot have. A database knows which query is urgent, and which thread is holding a lock that's about to stall everything else if it doesn't run now. The kernel doesn't. sched_ext's original thesis was "then let the side that knows write the policy," and sub-schedulers are an attempt to push that thesis down to the cgroup level, not the whole machine.

The Existing Approach — scx_flatcg Flattens the Hierarchy

To avoid a misreading, one thing needs pointing out. It's not that sched_ext knew nothing about cgroups before this. Among the example schedulers there's scx_flatcg, and the kernel docs describe it like this:

implements hierarchical weight-based cgroup CPU control by compounding each cgroup's share at every level into a single flat scheduling layer

It implements hierarchical, weight-based cgroup CPU control, but it compounds each cgroup's share at every level down into a single flat scheduling layer. That's what "flat" in the name means.

This is the contrast that matters. The existing approach represents the hierarchy but flattens it at the end so a single scheduler handles everything. Weights are respected, but running a different policy per cgroup is still impossible — there's only ever one scheduler, in the end. This is exactly the point sub-schedulers are trying to change — don't flatten the hierarchy, put a genuinely different scheduler at each point in it.

The Design — Attaching a Scheduler to a cgroup

Corbet's Sub-schedulers for sched_ext (January 29, 2026) lays out the structure well, as of v1. The core rule is one sentence:

Each task in the system will be governed by the scheduler attached to its control group (or to the nearest ancestor group).

Every task in the system is governed by the scheduler attached to its own control group. If that group has no scheduler, it follows the nearest ancestor group's. It's a familiar pattern — like DNS resolution or filesystem-permission inheritance.

In the cover letter's phrasing:

schedulers to attach anywhere in the cgroup hierarchy, with parent schedulers dynamically controlling CPU allocation to their children.

Schedulers can attach anywhere in the cgroup hierarchy, and parent schedulers dynamically control how much CPU goes to their children. This is where the division of labor splits. As Corbet explains it, the CPU controller still owns "how much CPU time these get," while the sub-scheduler owns "how to run this group's processes within that time."

There's a depth limit. From the cover letter:

The framework supports scheduling hierarchies up to SCX_SUB_MAX_DEPTH levels (currently set to 4).

Up to SCX_SUB_MAX_DEPTH, currently 4. Not infinite nesting — four levels.

Drawn out, it looks like this.

root cgroup  ── root BPF scheduler (allocates CPU to children)
   ├── /db          ── scx scheduler A (policy aware of query priority and lock holders)
   ├── /web         ── scx scheduler B (latency-first)
   │      └── /web/batch   ── no scheduler -> follows nearest ancestor (B)
   └── /batch       ── no scheduler -> follows root scheduler

depth limit: SCX_SUB_MAX_DEPTH = 4

There's one more mechanism worth noting. It's the security model Corbet points out — an implicit argument attached to a kfunc gives it access to the bpf_prog_aux struct. As a result:

BPF programs themselves need never specify which scheduler they are operating on.

A BPF program never needs to declare, on its own word, which scheduler it belongs to. Let it declare that itself and it could lie — picture a BPF program reaching into another scheduler's queue and it's immediately obvious why this matters. The kernel reads the affiliation out of the program itself.

What Actually Landed in 7.1 — and What Didn't

This is where the post gets to its point.

What landed in 7.1 is the dispatch path. By Corbet's definition, dispatch is the callback that "tells the scheduler to select the next task to run and place it on a specific CPU's dispatch queue." In other words, the skeleton for hierarchically handling "of the tasks already ready to run, what goes next and when" has landed.

What's missing matters more. Here's what Corbet flagged as unimplemented while reviewing v1:

  • the select_cpu() callback — the path that picks which CPU to send a task to when it wakes
  • the enqueue() callback — the path that puts a task onto a queue

And bypass mode was flagged as needing a redesign. The reason given is specific.

allowing one sub-scheduler to toss processes into a global FIFO queue could lead to interference with other sub-schedulers

Letting one sub-scheduler toss processes into a global FIFO queue could interfere with other sub-schedulers. In a feature whose whole point is isolation, a path that leaks isolation isn't something you can just wave off. Worth noting, this problem left a trace — the one place the current kernel docs mention sub-schedulers at all is, in effect, a single line describing the SCX_EV_SUB_BYPASS_DISPATCH event counter, which reads "tasks dispatched from sub-scheduler bypass DSQs (only relevant with CONFIG_EXT_SUB_SCHED)." It exists as a diagnostic counter, not as documented feature.

The cover letter itself doesn't hide this state.

While the enqueue path and other components require further development, this patchset establishes the core mechanisms for nested scheduler operation.

While the enqueue path and other components need further development, this patchset establishes the core mechanisms for nested scheduler operation. An honest sentence.

kernelnewbies' summary of the 7.1 changes holds the same line — it writes that sched_ext added sub-scheduler support, and that the purpose of this feature is to let different cgroups use different schedulers "in future releases." The original reads "The purpose of this feature is to eventually allow (in future releases) using different schedulers in different cgroups." Eventually and in future releases are the key words.

To sum it up:

Sub-schedulers — where things stand (as of 2026-07-16)

dispatch path                    [ merged in 7.1 ]
enqueue path                     [ incomplete — being developed in a follow-up patchset ]
select_cpu() path                [ unimplemented as of the v1 review ]
bypass mode                      [ needs a redesign (global FIFO interference issue) ]
different scheduler per cgroup   [ not yet possible — "future releases" ]

Timeline — Why This Is Taking So Long

Laid out in order, you can see the pace of this feature.

2024-11     sched_ext itself merged into kernel 6.12 (targeted 6.11, slipped)
2025-09-19  Tejun Heo posts the RFC patchset for cgroup sub-scheduler support
2026-01-29  LWN "Sub-schedulers for sched_ext" — v1 review
2026-02-25  v2 posted, flagged on the sched_ext/for-7.1 branch
2026-03-04  cover letter for the series that landed in 7.1
2026-04-27  LWN's 7.1 merge-window summary — "the implementation is not complete"
2026-06-14  Linux 7.1 released (dispatch path included)
2026-06-18  LWN's 7.2 merge window — "support for sub-schedulers continues"
2026-07-16  (today) mainline is at 7.2-rc3, stable is 7.1.3

Nine months from RFC to partial merge, and it's still unusable. Worth revisiting Corbet's prediction from the v1 review here.

One should thus not expect to see sub-scheduler support in the kernel for some time yet.

Don't expect to see sub-scheduler support in the kernel for a while yet. Written in January, this sentence is still valid five months later, even with the "beginning of support" landing in 7.1. The skeleton landing and the feature being usable are different things. It's normal, and actually healthy, for a big kernel feature to arrive in pieces spread across multiple cycles. The trouble is reading news that the first of those pieces merged as if the feature has shipped.

The Work Continuing in 7.2

Work continues through the 7.2 merge window too. Corbet's summary of the first half of the 7.2 merge window has it in one short line — "The addition of support for sub-schedulers in sched_ext continues."

Phoronix's summary of sched_ext in 7.2 (June 22, 2026) is a bit more specific. What landed in 7.2 is topology-based CPU IDs (CID), cmask infrastructure, BPF arena integration, and a rewritten scx_qmap example that exercises the new arena/CID interfaces. All of it is infrastructure.

And the pull request has this line:

A follow-up patchset in development will complete enqueue-path support for hierarchical scheduling.

A follow-up patchset in development will complete enqueue-path support for hierarchical scheduling. Note the future tense. 7.2 doesn't have the enqueue path either. And 7.2 itself is still rc3 as of today — it hasn't even been released.

So What Should You Do Right Now

Honestly — mostly nothing.

What you cannot do right now

  • Attach a different BPF scheduler per cgroup. That's the headline, but it's not possible yet.
  • Upgrade to kernel 7.1 to get this feature's benefit. The dispatch-path skeleton alone changes nothing about your workload.
  • Make capacity planning or architecture decisions that assume this feature. Nobody has committed to when the enqueue path will land, or whether it'll look like the current design when it does. The cover letter itself even mentions a temporary BPF workaround while waiting for a proper upstream solution.

What you can do right now

  • sched_ext itself has been usable since 6.12. It attaches one scheduler to the whole machine, and the watchdog fallback keeps the cost of experimenting low.
  • If you need cgroup-weight-based CPU control, scx_flatcg already exists. It flattens the hierarchy so you only get one policy, but that's enough for a lot of cases.
  • If multi-tenant isolation is urgent right now, cpuset and the cgroup CPU controller are still the answer. You accept the limitation the cover letter calls out (not dynamic), in exchange for something that works today.

When to check back

When you hear the enqueue path has merged. Until then, this feature is a roadmap, not a tool. And even after it merges, sched_ext's own history — the 6.11 target that slipped to 6.12, the sub-scheduler RFC that's nine months in and still half-done — suggests giving it a generous timeline.

Closing

To sum up: sched_ext sub-schedulers are a good idea. The problem statement is accurate (cpuset hard partitioning isn't dynamic), the thesis is coherent (policy should be written by whoever has the domain knowledge), and the design is sound (attach a scheduler to the cgroup hierarchy, inherit from the nearest ancestor, cap it at four levels, let the kernel determine affiliation).

And in 7.1, the dispatch path — one part of that — landed. That's it. There's no enqueue path, no select_cpu(), bypass mode is slated for a redesign, and the kernel docs mention this feature in only a single event-counter line. The sentence "each control group can run its own custom CPU scheduler" describes intent, not present tense.

This isn't a criticism. This is just how kernel work goes, and the cover letter, LWN, and kernelnewbies have all been precise about their own state — "further development," "not complete," "eventually," "in future releases." What was inaccurate wasn't the sources; it was reading this as a feature launch.

Here's the standard I use when reading kernel release notes: merged and usable are different events, and the gap between them widens with the size of the feature. The cheapest way to measure that gap is to search the merge-window summary for a word like "not complete." Usually, it's right there.

References

현재 단락 (1/95)

Linux 7.1 was [released on June 14, 2026](https://lwn.net/Articles/1077758/). One of the scheduler h...

작성 글자: 0원문 글자: 15,059작성 단락: 0/95