2026-05-19
Everyone reaches for nice when a background job hammers the CPU. nice is a hint. The scheduler weighs it against runtime, sleep history, and a dozen heuristics, then does roughly whatever it wants. If you need the kernel to actually care, you need chrt.
chrt (from util-linux, already on every Linux box) sets a process's scheduling policy, not just its weighting inside one policy. The policies are the levers the kernel actually pulls.
SCHED_IDLE — runs only when nothing else wants the CPU. Stronger than nice 19: an idle-class task can sit on a busy core forever and never preempt a regular task. Perfect for rsync backups, video encodes, or anything that should disappear until the box is bored.
chrt -i 0 ffmpeg -i source.mkv -c:v libx264 output.mkv
SCHED_FIFO / SCHED_RR — soft real-time. The process preempts every normal task on its CPU until it blocks. Audio engines (jackd, pipewire), trading loops, and ROS nodes live here. Priority 1–99; higher beats lower.
sudo chrt -f 50 jackd -dalsa
Find out what a running process actually is:
$ chrt -p 12345
pid 12345's current scheduling policy: SCHED_OTHER
pid 12345's current scheduling priority: 0
Move a runaway rebuild to the idle class without killing it:
sudo chrt -i -p 0 $(pgrep -f "cargo build")
That's the wizard move — your build keeps going, your tmux pane stays responsive, your video call doesn't stutter, and you didn't have to kill -STOP anything.
Scheduling policy controls CPU contention. taskset controls which CPUs the task can touch. ionice controls disk contention. They're orthogonal, and together they carve out actual cores and bandwidth.
# Pin to CPU 7, idle scheduling, idle I/O class
taskset -c 7 chrt -i 0 ionice -c3 restic backup /home
That backup will not perceptibly slow anything you're doing. It will also finish, eventually, because SCHED_IDLE still runs — it just yields to anything else that wants the cycles.
nice -n 19 still gets meaningful CPU time when the system is loaded, because CFS gives every runnable task some share. SCHED_IDLE gets nothing while other tasks are runnable. The difference shows up the moment you have a CPU-bound foreground task — nice 19 stutters, chrt -i 0 doesn't.
For real-time the comparison is even more lopsided: no nice level will preempt a regular task on a busy core. Only a real-time policy can do that, and chrt is how you opt in without writing C and calling sched_setscheduler(2) yourself.
CAP_SYS_NICE. Without it you'll get EPERM. Either run as root or grant the cap: setcap cap_sys_nice+ep ./your-binary.sleep() or blocking syscall will lock up a core. The kernel's RT throttler saves you (default: 950ms per second of wall clock), but don't rely on it.chrt -m prints the valid priority ranges for each policy on your running kernel — useful when a script assumes 1–99 and you're on something exotic.chrt -i 0 idles every grandchild too — usually what you want, occasionally a foot-gun.nice asks the scheduler politely; chrt picks the policy class the scheduler actually enforces, and that's the only knob that matters once the system is loaded.
