No description
  • C 63.9%
  • Nix 29.5%
  • Makefile 6.6%
Find a file
Max Fritz 1173ff8d92 feat: Phase 2 — per-process overrides, CLI subcommands, preset loading
Add BPF maps and cascade lookup for per-PID, per-comm, and per-cgroup
scheduling overrides.  Comm/PID/cgroup rules all take priority over the
global profile in that order.

BPF (scx_littlebig.bpf.c / scx_littlebig.h):
- struct task_policy { profile, flags } in shared header
- pid_overrides  (hash, u32 → task_policy, 4096 entries)
- process_overrides (hash, char[16] → task_policy, 256 entries)
- cgroup_policies (hash, u64 → task_policy, 256 entries)
- get_task_profile_p(): PID → comm → cgroupv2-id → global cascade,
  called from both select_cpu and enqueue

Userspace (src/scx_littlebig.c):
- pin_maps() / unpin_maps(): pin override maps to /sys/fs/bpf/scx_littlebig/
  so CLI subcommands can open them without running the daemon
- open_pinned_map(): bpf_obj_get() helper for subcommands
- cmd_rule(): rule add/list/remove subcommands
- cmd_pid():  pid <pid> --profile <name>
- cmd_cgroup(): cgroup <path> --profile <name> (reads cgroup.id)
- load_preset_file() / load_preset(): parse .conf format and populate
  process_overrides; -P <file> flag loads a preset at daemon start

Presets:
- presets/gaming.conf    — performance for cs2, gamescope, wine, steam …
- presets/compile.conf   — all-cores for cc1, rustc, cargo, make, ninja …
- presets/background.conf — powersave for baloo, tracker-miner, journald …

Tests (flake.nix):
- TEST 4a: comm rule add — scx_test_task → powersave lands on E-cores
- TEST 4b: rule list shows the active comm rule
- TEST 4c: rule remove restores P-core placement
- TEST 4d: PID override — performance pin in powersave mode
- TEST 4e: -P preset file populates comm rules at startup
- All five subtests pass in the NixOS VM (verified)
2026-02-28 12:17:07 +01:00
bpf feat: Phase 2 — per-process overrides, CLI subcommands, preset loading 2026-02-28 12:17:07 +01:00
presets feat: Phase 2 — per-process overrides, CLI subcommands, preset loading 2026-02-28 12:17:07 +01:00
src feat: Phase 2 — per-process overrides, CLI subcommands, preset loading 2026-02-28 12:17:07 +01:00
.gitignore feat: Phase 0 — skeleton, topology detection, NixOS VM test 2026-02-27 23:14:53 +01:00
CLAUDE.md feat: Phase 0 — skeleton, topology detection, NixOS VM test 2026-02-27 23:14:53 +01:00
flake.lock feat: Phase 0 — skeleton, topology detection, NixOS VM test 2026-02-27 23:14:53 +01:00
flake.nix feat: Phase 2 — per-process overrides, CLI subcommands, preset loading 2026-02-28 12:17:07 +01:00
Makefile feat: Phase 0 — skeleton, topology detection, NixOS VM test 2026-02-27 23:14:53 +01:00
README.md feat: Phase 2 — per-process overrides, CLI subcommands, preset loading 2026-02-28 12:17:07 +01:00

scx_littlebig

A sched_ext BPF scheduler for heterogeneous CPU architectures — Intel P/E-core (Alder Lake+) and ARM big.LITTLE — that optimises power consumption and latency through topology-aware task placement and user-configurable scheduling profiles.

Status: Phase 2 — per-process overrides implemented. Add per-comm, per-PID, and per-cgroup scheduling rules at runtime without reloading the scheduler. Preset files for gaming, compile, and background workloads included.


Why scx_littlebig?

Modern CPUs ship with non-uniform cores:

  • Intel 12th gen+ mixes high-frequency P-cores with power-efficient E-cores.
  • ARM big.LITTLE (Cortex-A7x + A5x, Snapdragon X, Apple Silicon) has been doing this for years.

The default Linux scheduler (EEVDF) has some topology awareness but offers no user-facing controls for power/performance trade-offs and cannot adapt per-application. scx_littlebig uses the sched_ext framework (Linux ≥ 6.11) to implement a fully topology-aware scheduler with:

  • Four switchable profiles — powersave, balanced, performance, all-cores — changeable at runtime without rebooting.
  • Per-process overrides — pin specific apps (games, compilers) to a different profile.
  • Automatic workload detection — classifies tasks by burst pattern and adjusts placement without configuration.
  • Safe fallback — sched_ext guarantees a return to the default kernel scheduler on any error or Ctrl-C.

Requirements

Runtime

The machine running the scheduler needs:

Requirement Minimum
Linux kernel 6.11+ with CONFIG_SCHED_CLASS_EXT=y
BTF /sys/kernel/btf/vmlinux must exist (CONFIG_DEBUG_INFO_BTF=y)
Privileges CAP_SYS_ADMIN (typically: run as root)

Check your kernel:

uname -r                        # should be >= 6.11
cat /sys/kernel/sched_ext/state # should exist
ls /sys/kernel/btf/vmlinux      # BTF must be available

Ubuntu 24.10, Fedora 41+, and NixOS with linuxPackages_latest all ship sched_ext-enabled kernels.

Build

Tool Minimum
clang 16+ (for BPF CO-RE compilation)
bpftool Matching kernel version
libbpf 1.3+
Kernel sched_ext headers tools/sched_ext/include from kernel source
vmlinux with BTF For generating vmlinux.h at build time

With Nix all of this is provided automatically — see below.


Building

The repo ships a flake.nix that pins all build dependencies — kernel with BTF, bpftool, libbpf, clang — so the build is fully reproducible.

nix build

The binary is available at result/bin/scx_littlebig.

For an interactive dev shell (sets VMLINUX_BTF, KERNEL_SCX_INCLUDE, CLANG automatically):

nix develop
make

Without Nix

Requires clang ≥ 16, bpftool, libbpf-dev, and kernel sched_ext headers:

make VMLINUX_BTF=/sys/kernel/btf/vmlinux \
     KERNEL_SCX_INCLUDE=/path/to/linux/tools/sched_ext/include

Quick start

# Load with default profile (balanced)
sudo ./result/bin/scx_littlebig

# Powersave — prefer E-cores, spill to P-cores only when saturated
sudo ./result/bin/scx_littlebig -p powersave

# Performance — prefer P-cores for everything
sudo ./result/bin/scx_littlebig -p performance

# All-cores — equal weight, single shared queue (good for compiling)
sudo ./result/bin/scx_littlebig -p all-cores

# Load with gaming rules pre-configured (cs2, gamescope, ... → performance)
sudo ./result/bin/scx_littlebig -P gaming

# Verbose: print per-CPU topology table on startup
sudo ./result/bin/scx_littlebig -v

# Switch profile at runtime without reloading
sudo kill -USR1 $(pidof scx_littlebig)

# Stop: Ctrl-C or SIGTERM — returns to default kernel scheduler

Per-process overrides (Phase 2)

While the daemon is running, control commands update BPF maps directly via pinned file descriptors under /sys/fs/bpf/scx_littlebig/ — no reload needed.

# Add a per-comm rule: cs2 always uses the performance profile
sudo scx_littlebig rule add performance cs2 gamescope

# List all active comm rules
sudo scx_littlebig rule list

# Remove a rule
sudo scx_littlebig rule remove cs2

# Pin a specific PID to performance regardless of global profile
sudo scx_littlebig pid 12345 --profile performance

# Apply a policy to an entire cgroup (cgroupv2)
sudo scx_littlebig cgroup /sys/fs/cgroup/user.slice --profile powersave

Priority cascade

When selecting a CPU for a task the scheduler walks:

1. pid_overrides     keyed by tgid        ← highest priority
2. process_overrides keyed by task->comm
3. cgroup_policies   keyed by cgroupv2 ID
4. global_profile                          ← lowest priority

The first match wins; unmatched tasks follow the global profile.

Preset files

Three bundled presets are installed to share/scx_littlebig/presets/:

Preset Profile Processes
gaming performance cs2, gamescope, mangohud, steam, lutris, wine, proton
compile all-cores cc1, cc1plus, rustc, cargo, make, ninja, javac, ld
background powersave tracker-miner, baloo, packagekitd, journald

Load at daemon startup: sudo scx_littlebig -P gaming

You can also write your own .conf files:

# myapp.conf
profile=performance
myapp
myapp-worker

Then: sudo scx_littlebig -P /path/to/myapp.conf

On a hybrid CPU you will see something like:

Topology: 16 CPUs — 8 P-core(s), 8 E-core(s)
scx_littlebig loaded. Profile: balanced (weighted-vtime). Press Ctrl-C to stop.
[balanced] local=1423 pcores=612 ecores=260 shared=0
[balanced] local=2891 pcores=987 ecores=417 shared=0
Profile switched to: powersave
[powersave] local=3102 pcores=12 ecores=1834 shared=0
^C

Profiles

Profile P-core policy E-core policy Use case
balanced Interactive tasks Background/batch Default daily driver
powersave Overflow only All tasks Laptop on battery
performance All tasks Overflow only Sustained workloads
all-cores Equal weight Equal weight Compiling, rendering

Architecture

┌─────────────────────────────────────────┐
│              User Space                  │
│                                          │
│  scx_littlebig [-f] [-v] [-q]           │
│       │                                  │
│  1. detect_topology()                    │
│     read /sys/devices/system/cpu/        │
│     → cpu_capacity per CPU               │
│     → classify P-core / E-core           │
│       │                                  │
│  2. populate core_topology BPF map       │
│       │                                  │
│  3. SCX_OPS_LOAD / ATTACH                │
│       │                                  │
│  4. stats loop (sleep 1s, read maps)     │
├───────┼──────────────────────────────────┤
│       │         Kernel / BPF             │
│  ┌────▼─────────────────────────────┐    │
│  │  scx_littlebig.bpf.c             │    │
│  │                                  │    │
│  │  select_cpu() → idle CPU search  │    │
│  │  enqueue()    → SHARED_DSQ       │    │
│  │  dispatch()   → local DSQ pull   │    │
│  │  running()    → vtime + stats    │    │
│  │  stopping()   → vtime charge     │    │
│  │  enable()     → init task vtime  │    │
│  │  disable()    → cleanup stats    │    │
│  └──────────────────────────────────┘    │
│                                          │
│  BPF Maps:                               │
│    core_topology  [array]  cpu → type    │
│    task_stats     [hash]   pid → stats   │
│    stats          [percpu] counters      │
└─────────────────────────────────────────┘

Topology detection

On startup, the userspace binary reads:

  1. /sys/devices/system/cpu/cpuN/cpu_capacity — populated by the kernel on heterogeneous systems for both Intel HFI and ARM big.LITTLE.
  2. /sys/devices/system/cpu/cpuN/cache/indexM/ — to determine LLC sharing domains.
  3. /sys/devices/system/cpu/cpuN/node* symlinks — for NUMA node assignment.

CPUs with cpu_capacity ≥ 80% of the system maximum are classified as P-cores; those below as E-cores. On uniform systems (all capacities equal or the file absent) all CPUs are marked CORE_TYPE_UNKNOWN and the scheduler behaves like a plain global weighted-vtime scheduler.

The results are written into the core_topology BPF array map before the scheduler attaches, so the BPF program can use them without sysfs access.

Scheduling (Phase 0)

Phase 0 implements the same logic as scx_simple with topology data pre-loaded:

  • select_cpu: scx_bpf_select_cpu_dfl() finds an idle CPU respecting affinity and LLC locality. If found → dispatch to its SCX_DSQ_LOCAL (zero-copy fast path).
  • enqueue: tasks not dispatched in select_cpu go to SHARED_DSQ using weighted virtual time.
  • dispatch: each CPU pulls from SHARED_DSQ when its local queue is empty.

Phase 1 will replace select_cpu with topology-aware core-type selection driven by the active profile.


Roadmap

Phase Status Description
0 Done Skeleton, topology detection, global weighted-vtime scheduling
1 Done Four switchable profiles (powersave / balanced / performance / all-cores)
2 Done Per-process overrides (by comm, PID, cgroup); preset configs; pinned BPF maps
3 Planned Automatic workload classification (latency-sensitive, throughput, background)
4 Planned Polish, packaging (Fedora COPR, AUR, NixOS flake), TUI dashboard
5 Stretch GPU-aware CPU placement via DRM fdinfo

Testing

Boots linuxPackages_latest (which has CONFIG_SCHED_CLASS_EXT and CONFIG_DEBUG_INFO_BTF) in a QEMU VM, loads the scheduler, runs a brief stress workload, and verifies /sys/kernel/sched_ext/state transitions — no root on the host required.

nix build .#checks.x86_64-linux.vm -L

The test is defined in flake.nix under checks.x86_64-linux.vm using the NixOS testing library.


File structure

scx_littlebig/
├── Makefile                   Build system
├── README.md                  This file
├── flake.nix                  Nix build + dev shell + VM integration tests
├── bpf/
│   ├── scx_littlebig.bpf.c   BPF scheduler (profiles + per-task cascade)
│   └── scx_littlebig.h        Shared types (BPF ↔ userspace)
├── src/
│   └── scx_littlebig.c        Userspace daemon + CLI subcommands
└── presets/
    ├── gaming.conf            performance profile: cs2, gamescope, …
    ├── compile.conf           all-cores profile: cc1, rustc, cargo, …
    └── background.conf        powersave profile: tracker-miner, baloo, …

References


License

GPL-2.0-only — matching the sched_ext framework and the Linux kernel.