A custom Linux CPU scheduler implemented as eBPF programs via the kernel's sched_ext framework (kernel >= 6.12; developed on 7.0.0, Ubuntu 26.04 ARM64). It classifies tasks as interactive vs. batch at runtime and prioritizes interactive tasks under CPU contention.
sched_ext is a scheduling class whose policy is supplied by BPF programs loaded from userspace. The kernel verifies the programs, JIT-compiles them, and migrates all tasks to the new policy; on exit, crash, or a 30s stall the kernel reverts everything to the default scheduler (EEVDF).
Task lifecycle through this scheduler:
select_cpu— on wakeup, search for an idle CPU; if found, dispatch the task directly to that CPU's local queue (fast path, zero queueing).enqueue— otherwise insert into a shared queue ordered by virtual time (vtime). Two policy levers apply here:- Credit clamp: a waking task keeps up to 4 slices of vtime credit if interactive, 1 if batch — interactive tasks insert ahead.
- Slice sizing: interactive tasks get 1/4-length slices (low latency for others), batch tasks full 20ms slices (cache warmth).
- Preemption kick: interactive enqueue kicks the target CPU
(
SCX_KICK_PREEMPT) so priority takes effect immediately.
dispatch— an idle CPU pulls the lowest-vtime task from the shared queue.stopping— the task is charged vtime = runtime x 100 / weight (weighted fairness: heavier tasks age slower, receiving more CPU).quiescent— voluntary sleeps are counted per task (BPF task storage);= 10 voluntary sleeps/sec over a 1s window => classified interactive.
Workload: 8 stress-ng CPU hogs on 4 vCPUs (2:1 oversubscription). Probe: a task nanosleeping 1ms in a loop, 5000 samples/run; overshoot beyond 1ms = scheduling (wakeup) latency. One warm-up run discarded, 3 measured runs. UTM VM on Apple M3; host scheduler noise applies equally to both configs.
| Scheduler | p50 | p99 (3 runs) |
|---|---|---|
| EEVDF (default) | 59 us | 302 / 426 / 599 us |
| scx_interactive v1 (no preemption) | 26,764 us | 47,985 us |
| scx_interactive v2 (preemption kick) | 63–67 us | 440 / 1,656 / 2,306 us |
Findings:
- v1 exposed the core lesson: vtime ordering without a preemption path is inert — a waking task waited out the incumbent's slice (median ~= half a 20ms slice). Adding a preemption kick on interactive enqueue improved median wakeup latency ~400x (26.7ms -> 63us).
- A warm-up experiment isolated the remaining tail: with a cold probe, p99 ~= 38ms, dominated by the ~1s classification lag before the task earns interactive status; once classified, p99 drops to 0.4–2.3ms.
- Steady state: parity with EEVDF at p50; EEVDF retains a ~4x better p99. Plausible contributors: single shared queue contention, kick targeting only the task's previous CPU, and 1s reclassification granularity.
# kernel >= 6.12 with CONFIG_SCHED_CLASS_EXT=y; clang >= 16, pahole >= 1.25
make all # from repo root
sudo ./build/scheds/c/scx_interactive # terminal 1
cat /sys/kernel/sched_ext/root/ops # -> "interactive"
stress-ng --cpu 8 --timeout 200 & # terminal 2
./latprobe # see scheds/c/ for source
Per-CPU or per-LLC queues to cut shared-queue contention; smarter kick target selection (lowest-priority running CPU); exponentially-decayed sleep rate to shrink classification lag; parameter sweep of slice sizes and thresholds.
Based on scx_simple (Meta Platforms, GPL-2.0).