Split View: sched_ext 서브 스케줄러 — 커널 7.1에 절반만 도착한 cgroup별 스케줄러
sched_ext 서브 스케줄러 — 커널 7.1에 절반만 도착한 cgroup별 스케줄러
- 들어가며 — 헤드라인과 커밋 로그 사이의 간극
- 복습 — sched_ext가 뭐였나
- 왜 서브 스케줄러인가 — cpuset의 한계
- 지금까지의 방법 — scx_flatcg는 계층을 평탄화한다
- 설계 — cgroup에 스케줄러를 붙인다
- 7.1에 실제로 들어간 것 — 그리고 빠진 것
- 타임라인 — 왜 이렇게 오래 걸리나
- 7.2에서 이어지는 작업
- 그래서 지금 뭘 해야 하나
- 마치며
- 참고 자료
들어가며 — 헤드라인과 커밋 로그 사이의 간극
리눅스 7.1이 2026년 6월 14일에 릴리스됐습니다. 스케줄러 쪽 헤드라인 하나는 이것이었습니다 — sched_ext에 cgroup 서브 스케줄러 지원이 들어갔다. 문장만 보면 꽤 큰 사건입니다. cgroup마다 다른 CPU 스케줄러를 붙일 수 있다는 뜻이니까요. 컨테이너를 굴리는 사람이라면 귀가 솔깃할 만합니다.
그런데 같은 소식을 LWN의 머지 윈도 요약에서 읽으면 톤이 확연히 다릅니다. Jonathan Corbet은 7.1 머지 윈도 정리에서 이렇게 썼습니다. 원문 그대로 옮기면 이렇습니다.
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.
핵심은 두 번째 문장입니다 — 7.1 기준으로 이 구현은 완성되지 않았고, 특히 enqueue 경로가 빠져 있습니다. 들어온 것은 "서브 스케줄러 지원의 시작"이지 서브 스케줄러가 아닙니다.
이 글은 그 간극에 관한 이야기입니다. 무엇을 하려는 기능이고, 왜 필요하고, 7.1에 실제로 뭐가 들어갔고, 뭐가 아직 없고, 그래서 지금 당신이 뭘 해야 하는지(대체로 "아무것도")를 정리합니다.
복습 — sched_ext가 뭐였나
CFS에서 EEVDF까지의 흐름은 따로 정리했으니 여기서는 sched_ext만 짧게 복습합니다.
sched_ext는 커널 6.12에 병합된 확장 스케줄러 클래스입니다(원래 6.11을 노렸다가 한 사이클 밀렸습니다). 스케줄링 정책을 BPF 프로그램으로 작성해서 런타임에 적재·교체할 수 있게 해 줍니다. struct sched_ext_ops에 콜백을 채워 넣고 로드하면, 그때부터 그 BPF 코드가 어떤 태스크를 어느 CPU에서 언제 돌릴지 결정합니다.
여기서 가장 중요한 설계 결정은 안전장치입니다. 커널 문서의 표현을 그대로 옮기면 이렇습니다.
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.
BPF 스케줄러가 무슨 짓을 하든 시스템 무결성은 유지되고, 에러가 감지되거나 실행 가능한 태스크가 멈추면 기본 스케줄링 동작으로 자동 복귀합니다. SysRq-S로 손수 걷어낼 수도 있습니다. 이 워치독이 있기 때문에 "커널 스케줄러를 사용자 코드로 갈아 끼운다"는 무모해 보이는 아이디어가 실제로 성립합니다.
왜 서브 스케줄러인가 — cpuset의 한계
문제 설정은 Tejun Heo가 올린 패치 시리즈 커버레터에 잘 나와 있습니다. 상황은 이렇습니다. 큰 머신 한 대에 성격이 다른 워크로드가 여럿 얹혀 있습니다. 각자 원하는 스케줄링 정책이 다릅니다. 지연에 민감한 서비스, 처리량이 중요한 배치 잡, 게임처럼 상호작용성이 중요한 것들이 한 상자 안에 섞여 있습니다.
지금까지의 답은 하드 파티셔닝이었습니다. 커버레터의 문장을 그대로 옮기면 이렇습니다.
Traditional approaches rely on hard partitioning via cpuset, but this approach lacks the dynamism required by modern workloads.
cpuset으로 CPU를 갈라 주는 방식은 현대 워크로드가 요구하는 동적인 성질이 없다는 것입니다. 왜 부족한지도 커버레터가 구체적으로 설명합니다.
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.
풀어 쓰면 이렇습니다. 사람들이 진짜 원하는 건 "이 워크로드는 3번부터 7번 코어"가 아닙니다. 큰 머신을 공유해야만 얻을 수 있는 것들 — 기회주의적 오버커밋, 대역폭 공정성은 지키면서 중요한 워크로드의 지연은 개선하기, 단순 CPU 시간을 넘어선 제어(메모리 대역폭 격리 같은), 캐시 지역성을 살리는 배치 — 을 원합니다. CPU를 미리 잘라 놓으면 이 중 어느 것도 못 합니다. 잘린 조각 안에서 놀 뿐이니까요.
그리고 왜 커널이 이걸 대신 못 해 주냐는 질문에 대한 답이, Phoronix가 인용한 두 번째 버전 커버레터의 문장입니다.
Applications often have domain-specific knowledge that generic schedulers cannot possess. Database systems understand query priorities and lock holder criticality.
애플리케이션은 범용 스케줄러가 가질 수 없는 도메인 지식을 갖고 있습니다. DB는 어떤 쿼리가 급한지, 어떤 스레드가 락을 쥐고 있어서 지금 안 돌리면 다 같이 막히는지 압니다. 커널은 모릅니다. sched_ext의 원래 논지가 "그럼 아는 쪽이 정책을 쓰게 하자"였고, 서브 스케줄러는 그 논지를 머신 전체가 아니라 cgroup 단위로 내리는 시도입니다.
지금까지의 방법 — scx_flatcg는 계층을 평탄화한다
오해를 피하기 위해 짚어야 할 게 있습니다. sched_ext가 cgroup을 아예 몰랐던 건 아닙니다. 예제 스케줄러 중에 scx_flatcg가 있고, 커널 문서는 이걸 이렇게 설명합니다.
implements hierarchical weight-based cgroup CPU control by compounding each cgroup's share at every level into a single flat scheduling layer
계층적 가중치 기반 cgroup CPU 제어를 구현하되, 각 cgroup의 몫을 레벨마다 곱해서 하나의 평탄한 스케줄링 레이어로 합쳐 버립니다. 이름의 flat이 그 뜻입니다.
이게 중요한 대비입니다. 기존 방식은 계층을 표현하되 마지막에 납작하게 눌러서 스케줄러 하나가 전부 처리합니다. 가중치는 지켜지지만, cgroup마다 다른 정책을 쓰는 건 여전히 불가능합니다. 결국 스케줄러는 하나니까요. 서브 스케줄러가 바꾸려는 게 정확히 이 지점입니다 — 계층을 누르지 말고, 계층의 각 지점에 진짜로 다른 스케줄러를 앉히자.
설계 — cgroup에 스케줄러를 붙인다
Corbet의 Sub-schedulers for sched_ext(2026년 1월 29일)가 v1 기준으로 구조를 잘 정리해 뒀습니다. 핵심 규칙은 한 문장입니다.
Each task in the system will be governed by the scheduler attached to its control group (or to the nearest ancestor group).
시스템의 모든 태스크는 자기 컨트롤 그룹에 붙은 스케줄러의 지배를 받습니다. 그 그룹에 스케줄러가 없으면 가장 가까운 조상 그룹의 것을 따릅니다. DNS 리졸브나 파일시스템 권한 상속과 같은 익숙한 패턴입니다.
커버레터의 표현으로는 이렇게 됩니다.
schedulers to attach anywhere in the cgroup hierarchy, with parent schedulers dynamically controlling CPU allocation to their children.
스케줄러는 cgroup 계층 어디에나 붙을 수 있고, 부모 스케줄러가 자식들에게 갈 CPU 할당을 동적으로 제어합니다. 여기서 역할 분담이 갈립니다. Corbet의 설명대로 CPU 컨트롤러는 여전히 "얼마나 많은 CPU 시간이 이들에게 주어지는가"를 담당하고, 서브 스케줄러는 그 시간 안에서 자기 그룹의 프로세스들을 어떻게 굴릴지를 담당합니다.
깊이에는 제한이 있습니다. 커버레터의 문장입니다.
The framework supports scheduling hierarchies up to SCX_SUB_MAX_DEPTH levels (currently set to 4).
SCX_SUB_MAX_DEPTH까지, 현재 값은 4입니다. 무한 중첩이 아니라 4단계입니다.
그림으로 그리면 이런 모양입니다.
root cgroup ── 루트 BPF 스케줄러 (자식들에게 CPU 배분)
│
├── /db ── scx 스케줄러 A (쿼리 우선순위·락 홀더를 아는 정책)
│
├── /web ── scx 스케줄러 B (지연 우선)
│ └── /web/batch ── 스케줄러 없음 -> 가장 가까운 조상(B)을 따름
│
└── /batch ── 스케줄러 없음 -> 루트 스케줄러를 따름
깊이 제한: SCX_SUB_MAX_DEPTH = 4
메커니즘 하나가 더 인상적입니다. Corbet이 짚은 보안 모델인데, kfunc에 암묵적 인자를 붙여서 bpf_prog_aux 구조체에 접근하게 만들었습니다. 덕분에 이렇게 됩니다.
BPF programs themselves need never specify which scheduler they are operating on.
BPF 프로그램이 자기가 어느 스케줄러 소속인지 스스로 밝힐 필요가 없습니다. 밝히게 두면 거짓말할 수 있으니까요 — 남의 스케줄러 큐를 건드리는 BPF 프로그램을 상상해 보면 왜 이게 필요한지 바로 보입니다. 커널이 프로그램 자체에서 소속을 읽어 냅니다.
7.1에 실제로 들어간 것 — 그리고 빠진 것
여기가 이 글의 본론입니다.
7.1에 들어간 것은 dispatch 경로입니다. dispatch는 Corbet의 정의로 "스케줄러에게 다음에 돌릴 태스크를 골라서 특정 CPU의 디스패치 큐에 넣으라고 지시하는" 콜백입니다. 즉 "이미 돌릴 준비가 된 태스크들 중에 뭘 언제 올릴까"를 계층적으로 처리하는 뼈대가 들어갔습니다.
빠진 것이 더 중요합니다. Corbet이 v1을 리뷰하며 짚은 미구현 항목은 이렇습니다.
select_cpu()콜백 — 태스크가 깨어날 때 어느 CPU로 보낼지 고르는 경로enqueue()콜백 — 태스크를 큐에 넣는 경로
그리고 bypass 모드는 재설계가 필요하다고 지적됐습니다. 이유가 구체적입니다.
allowing one sub-scheduler to toss processes into a global FIFO queue could lead to interference with other sub-schedulers
서브 스케줄러 하나가 프로세스를 전역 FIFO 큐에 던져 넣게 두면 다른 서브 스케줄러들과 간섭할 수 있다는 것입니다. 격리가 목적인 기능에서 격리가 새는 경로라 그냥 넘어갈 수 없는 문제입니다. 참고로 이 문제는 흔적을 남겼습니다 — 현재 커널 문서에서 서브 스케줄러가 언급되는 곳은 사실상 SCX_EV_SUB_BYPASS_DISPATCH 이벤트 카운터 설명 한 줄이고, 내용은 "tasks dispatched from sub-scheduler bypass DSQs (only relevant with CONFIG_EXT_SUB_SCHED)"입니다. 기능 문서가 아니라 진단 카운터로만 존재합니다.
커버레터 자신도 이 상태를 숨기지 않습니다.
While the enqueue path and other components require further development, this patchset establishes the core mechanisms for nested scheduler operation.
enqueue 경로와 다른 구성 요소는 추가 개발이 필요하지만, 이 패치셋은 중첩 스케줄러 동작의 핵심 메커니즘을 세운다. 정직한 문장입니다.
kernelnewbies의 7.1 변경사항 정리도 같은 선을 지킵니다 — sched_ext가 서브 스케줄러 지원을 추가했고, 이 기능의 목적은 "향후 릴리스에서" cgroup마다 다른 스케줄러를 쓸 수 있게 하는 것이라고 씁니다. 원문은 "The purpose of this feature is to eventually allow (in future releases) using different schedulers in different cgroups"입니다. eventually와 in future releases가 핵심 단어입니다.
정리하면 이렇습니다.
서브 스케줄러 — 무엇이 어디까지 왔나 (2026-07-16 기준)
dispatch 경로 [ 7.1에 병합됨 ]
enqueue 경로 [ 미완 — 후속 패치셋 개발 중 ]
select_cpu() 경로 [ v1 리뷰 시점 미구현 ]
bypass 모드 [ 재설계 필요 (전역 FIFO 간섭 문제) ]
cgroup별 다른 스케줄러 [ 아직 불가 — "향후 릴리스" ]
타임라인 — 왜 이렇게 오래 걸리나
시간 순서로 놓으면 이 기능의 속도가 보입니다.
2024-11 sched_ext 자체가 커널 6.12에 병합 (6.11 목표였다가 밀림)
2025-09-19 Tejun Heo, cgroup 서브 스케줄러 지원 RFC 패치셋 게시
2026-01-29 LWN "Sub-schedulers for sched_ext" — v1 리뷰
2026-02-25 v2 게시, sched_ext/for-7.1 브랜치에 표시
2026-03-04 7.1에 들어간 시리즈의 커버레터
2026-04-27 LWN 7.1 머지 윈도 정리 — "구현이 완성되지 않았다"
2026-06-14 리눅스 7.1 릴리스 (dispatch 경로 포함)
2026-06-18 LWN 7.2 머지 윈도 — "서브 스케줄러 지원 추가가 계속된다"
2026-07-16 (오늘) 메인라인은 7.2-rc3, 안정판은 7.1.3
RFC부터 부분 병합까지 9개월, 그리고 아직 쓸 수 없습니다. 여기서 Corbet이 v1 리뷰 때 남긴 예측을 다시 볼 필요가 있습니다.
One should thus not expect to see sub-scheduler support in the kernel for some time yet.
당분간 커널에서 서브 스케줄러 지원을 보리라 기대하지 말라. 1월에 쓴 이 문장은, 5개월 뒤 7.1에 "지원의 시작"이 들어왔음에도 여전히 유효합니다. 뼈대가 들어온 것과 기능을 쓸 수 있는 것은 다른 이야기이기 때문입니다. 커널에서 큰 기능이 조각으로 나뉘어 여러 사이클에 걸쳐 들어오는 건 정상이고, 오히려 건강한 방식입니다. 다만 그 조각 중 첫 번째가 병합됐다는 소식을 기능 출시로 읽으면 곤란합니다.
7.2에서 이어지는 작업
7.2 머지 윈도에서도 작업은 계속됩니다. Corbet의 7.2 머지 윈도 전반부 정리는 짧게 한 줄입니다 — "The addition of support for sub-schedulers in sched_ext continues."
Phoronix의 7.2 sched_ext 정리(2026년 6월 22일)가 조금 더 구체적입니다. 7.2에 들어간 것은 토폴로지 기반 CPU ID(CID), cmask 인프라, BPF arena 통합, 그리고 새 arena·CID 인터페이스를 실습하도록 다시 쓴 scx_qmap 예제입니다. 전부 인프라입니다.
그리고 풀 리퀘스트에 이 문장이 있습니다.
A follow-up patchset in development will complete enqueue-path support for hierarchical scheduling.
개발 중인 후속 패치셋이 계층적 스케줄링의 enqueue 경로 지원을 완성할 것이다. 미래 시제라는 점에 주의하십시오. 7.2에도 enqueue 경로는 없습니다. 7.2는 오늘 기준 아직 rc3이고, 릴리스도 안 됐습니다.
그래서 지금 뭘 해야 하나
정직하게 답하면 — 대체로 아무것도.
지금 할 수 없는 것
- cgroup마다 다른 BPF 스케줄러 붙이기. 이게 헤드라인이지만 아직 안 됩니다.
- 7.1로 커널을 올려서 이 기능의 이득을 보기. dispatch 경로 뼈대만으로는 워크로드에 아무 변화가 없습니다.
- 이 기능을 전제로 한 용량 계획이나 아키텍처 결정. enqueue 경로가 언제 완성될지, 완성된 형태가 지금 설계와 같을지 아무도 확답하지 않았습니다. 커버레터에는 upstream의 제대로 된 해법을 기다리는 임시 BPF 우회 조치도 포함돼 있었습니다.
지금 할 수 있는 것
- sched_ext 자체는 6.12부터 쓸 수 있습니다. 머신 전체에 스케줄러 하나를 붙이는 방식이고, 워치독 폴백이 있어서 실험 비용이 낮습니다.
- cgroup 가중치 기반 CPU 제어가 필요하면
scx_flatcg가 이미 있습니다. 계층을 평탄화하는 방식이라 정책은 하나지만, 많은 경우 이걸로 충분합니다. - 멀티테넌트 격리가 지금 당장 급하면 여전히 cpuset·cgroup CPU 컨트롤러가 답입니다. 커버레터가 지적한 한계(동적이지 않음)를 감수하는 대신, 오늘 동작합니다.
언제 다시 볼 것인가
enqueue 경로가 병합됐다는 소식이 나올 때입니다. 그전까지 이 기능은 로드맵이지 도구가 아닙니다. 그리고 병합된 뒤에도, sched_ext의 역사가 말해 주듯 — 6.11 목표가 6.12로 밀렸고, 서브 스케줄러 RFC가 9개월 만에 절반 왔습니다 — 여유 있게 잡는 편이 맞습니다.
마치며
정리하면 이렇습니다. sched_ext 서브 스케줄러는 좋은 아이디어입니다. 문제 설정이 정확하고(cpuset 하드 파티셔닝은 동적이지 않다), 논지가 일관되고(정책은 도메인 지식이 있는 쪽이 써야 한다), 설계가 합리적입니다(cgroup 계층에 스케줄러를 붙이고, 가장 가까운 조상을 상속하고, 4단계로 제한하고, 소속은 커널이 판별한다).
그리고 7.1에는 그중 dispatch 경로가 들어갔습니다. 그게 전부입니다. enqueue 경로가 없고, select_cpu()도 없고, bypass 모드는 재설계 대상이고, 커널 문서는 이 기능을 이벤트 카운터 한 줄로만 언급합니다. "각 컨트롤 그룹이 자기만의 커스텀 CPU 스케줄러로 돌 수 있다"는 문장은 지금 시제가 아니라 의도의 서술입니다.
이건 비판이 아닙니다. 커널이 일하는 방식이 원래 이렇고, 커버레터와 LWN과 kernelnewbies 모두 자기 상태를 정확하게 적어 뒀습니다 — "further development", "not complete", "eventually", "in future releases". 부정확했던 건 소스가 아니라 이걸 기능 출시로 읽는 독법입니다.
커널 릴리스 노트를 읽을 때 제가 쓰는 기준은 이겁니다. 병합됐다와 쓸 수 있다는 다른 사건이고, 큰 기능일수록 그 사이가 멉니다. 두 사건 사이의 거리를 재는 가장 싼 방법은 머지 윈도 요약에서 "not complete" 같은 단어를 찾는 것입니다. 대개 거기 적혀 있습니다.
참고 자료
- Sub-schedulers for sched_ext — Jonathan Corbet, LWN, 2026-01-29
- sched_ext: Implement cgroup sub-scheduler support — Tejun Heo의 패치셋 게시(LWN 사본), 2025-09-19
- The rest of the 7.1 merge window — Jonathan Corbet, LWN, 2026-04-27
- The 7.1 kernel has been released — LWN, 2026-06-14
- The first half of the 7.2 merge window — Jonathan Corbet, LWN, 2026-06-18
- Linux 7.1 변경사항 — kernelnewbies
- Sub-Scheduler Support Could Be One Of The Most Exciting Features To Come For Linux 7.1 — Phoronix, 2026-02-25
- Linux 7.2 sched_ext Continues Working Toward Sub-Scheduler Support — Phoronix, 2026-06-22
- Extensible Scheduler Class — 커널 공식 문서
- Sched_ext Merged For Linux 6.12 — Phoronix
- The Linux Kernel Archives — 현재 릴리스 현황
- 리눅스 CPU 스케줄러의 진화 — CFS에서 EEVDF까지(관련 글)
sched_ext Sub-Schedulers — the Per-cgroup Scheduler That Arrived Half-Done in Kernel 7.1
- Introduction — The Gap Between the Headline and the Commit Log
- Recap — What sched_ext Was
- Why Sub-Schedulers — The Limits of cpuset
- The Existing Approach — scx_flatcg Flattens the Hierarchy
- The Design — Attaching a Scheduler to a cgroup
- What Actually Landed in 7.1 — and What Didn't
- Timeline — Why This Is Taking So Long
- The Work Continuing in 7.2
- So What Should You Do Right Now
- Closing
- References
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_flatcgalready 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
- Sub-schedulers for sched_ext — Jonathan Corbet, LWN, 2026-01-29
- sched_ext: Implement cgroup sub-scheduler support — Tejun Heo's patchset posting (LWN copy), 2025-09-19
- The rest of the 7.1 merge window — Jonathan Corbet, LWN, 2026-04-27
- The 7.1 kernel has been released — LWN, 2026-06-14
- The first half of the 7.2 merge window — Jonathan Corbet, LWN, 2026-06-18
- Linux 7.1 changes — kernelnewbies
- Sub-Scheduler Support Could Be One Of The Most Exciting Features To Come For Linux 7.1 — Phoronix, 2026-02-25
- Linux 7.2 sched_ext Continues Working Toward Sub-Scheduler Support — Phoronix, 2026-06-22
- Extensible Scheduler Class — official kernel documentation
- Sched_ext Merged For Linux 6.12 — Phoronix
- The Linux Kernel Archives — current release status
- The Evolution of the Linux CPU Scheduler — From CFS to EEVDF (related post)