FreeBSD rtprio Operations: Use Realtime and Idle Classes Without Starvation
Understand FreeBSD rtprio and idprio classes, inspect process priority, test bounded workloads, and roll back changes that harm system responsiveness.
FreeBSD exposes several ways to influence CPU scheduling, and they are not interchangeable. The ordinary time-sharing policy is adjusted with nice values; rtprio(1) selects realtime scheduling priority; idprio(1) selects an idle-time class that runs only when ordinary runnable work is absent. A process in the realtime class is not simply “a process with a nicer priority.” It can preempt time-sharing work and can make a host less responsive if it consumes CPU continuously.
Use the least powerful mechanism that meets a measured goal. If a batch job should yield more readily under competition, a positive nice value is usually a lower-risk experiment than realtime scheduling. If a process should consume otherwise idle CPU, idprio may express that intent. Realtime priority is for workloads with a justified, bounded latency requirement and an explicit starvation and recovery plan.
Record the current scheduler and workload
Capture host context, target identity, and a baseline before changing scheduling:
freebsd-version -kru
sysctl kern.sched.name
ps -axo pid,ppid,user,state,pri,nice,comm
vmstat 1 5
The ULE scheduler is the usual FreeBSD scheduler and includes interactivity heuristics, CPU topology awareness, and per-CPU run queues. Verify the active scheduler rather than assuming it, especially on a custom kernel. Record the process tree and actual command because a shell wrapper, supervisor, and worker may have different scheduling classes.
Define the metric before testing: latency of a specific service, completion time of a batch, dropped requests, CPU utilization, or response time under a repeatable competing load. “It feels faster” is not a sufficient acceptance measure. Collect the same interval before and after, and keep a separate administrative session so a runaway process can be stopped.
Distinguish nice from realtime and idle time
The nice and renice interfaces change ordinary scheduling priority. Lower numeric nice values are more favorable; ordinary users have restricted authority to increase priority. This affects a process’s relationship to other time-sharing work, not its class. A positive nice value can make a CPU-heavy batch job yield more readily without promoting it over interactive work.
The rtprio command assigns a process to realtime scheduling. Its integer priority ranges from 0 through RTP_PRIO_MAX, usually 31; 0 is the highest realtime priority. A realtime process is not subject to ordinary priority degradation and is preempted only by a process of equal or higher realtime priority. This can starve time-sharing processes if a realtime workload fails to block or yield.
The idprio command assigns idle-time scheduling. An idle-priority process runs only when no other process is runnable and then according to its idle priority relative to other idle-class processes. This is useful for opportunistic work, but it does not guarantee a completion deadline: sustained normal load can prevent it from running.
The classes also differ from CPU affinity. cpuset(1) limits where a process or interrupt may run; rtprio and idprio affect scheduler class and priority. Pinning a process to a CPU does not make it realtime, and realtime priority does not reserve a CPU or guarantee I/O completion. Treat scheduler class, CPU set, NUMA placement, and I/O latency as separate variables.
Inspect and test with a bounded command
The rtprio utility without arguments reports the current process priority; with a PID, it reports that process’s realtime priority. Check the installed manual for exact output on the release being used:
rtprio
rtprio 842
idprio 842
Replace 842 with a verified PID. The PID can be reused, so correlate it with command, user, start time, and parent process before changing anything. A process can have several threads; inspect application behavior and available process tools before assuming that a process-level query describes every thread’s work.
For a short, finite test command, invoke the lowest realtime priority in a controlled environment:
rtprio 31 /usr/bin/sha256 /var/tmp/test-image
This is an example only; use a noncritical test file and confirm the command exists on the target system. Priority 31 is the lowest level in the realtime class, not equivalent to ordinary timesharing or harmless under every workload. Even a low realtime priority can outrank normal time-sharing work. Do not wrap a daemon or infinite loop for an initial test.
To test opportunistic work, use idprio with a finite command:
idprio 31 /usr/bin/sha256 /var/tmp/test-image
If the command is delayed while the host has runnable normal work, that is expected behavior of the class. Do not use idprio for a job with a hard deadline.
Change a running process only with rollback
The manual supports modifying a process by PID. A syntax template for setting a short-lived worker to realtime priority 31 is:
rtprio 31 -842
rtprio 842
This affects the target process and may require superuser privilege. Replace the PID only after verifying process identity. Confirm the reported class immediately and monitor the host from the separate management session. Do not use priority zero as a routine tuning value.
The documented -t form runs a command as a normal non-realtime process or returns a process to the normal class. If an approved test must restore a process, verify the exact rtprio(1) syntax and use its documented target form, for example:
rtprio -t -842
rtprio 842
Treat rollback as a planned step rather than a recovery guess. Keep the previous class and priority in the change record. If the target is an rc-managed service, use its service lifecycle and configuration instead of repeatedly applying a transient PID command that will be lost at restart.
Prevent starvation and hidden restart loops
Realtime work must have a bounded CPU profile, a blocking strategy, and an operator-visible stop path. A tight loop that polls a condition can consume the CPU and leave time-sharing shells, monitoring, and services with little opportunity to run. Avoid promoting whole process trees when only one timing-sensitive thread needs attention. Do not combine realtime priority with a CPU mask that isolates the process onto a CPU required by interrupts or host management.
Before testing, define a maximum duration, a CPU utilization threshold, and a rollback trigger. Use a short batch workload with a known input. Observe CPU distribution and service latency while competing work is active. If remote responsiveness degrades, stop the test from the out-of-band session. Never experiment first on a production system with no console, watchdog, or recovery access.
An application restart policy can recreate a process with its default class after a supervisor restarts it. Conversely, a wrapper may reapply an unsafe class on every restart. Inspect the service’s rc.d script and supervisor configuration to determine where priority is set. Document whether the setting belongs to the process launcher or an operator command.
Diagnose why a priority change appears ineffective
If observed CPU time does not change, verify that the command modified the intended PID and that the process remained the same instance. A command may have forked a child before adjustment, or a supervisor may have replaced it. Compare process start times and command lines rather than trusting a recycled PID.
If a process remains delayed after realtime promotion, investigate blocking I/O, locks, sleep states, and dependencies. Scheduler class affects runnable CPU scheduling; it cannot make a disk, network peer, mutex owner, or remote service respond faster. Check process state and application traces before escalating priority.
If a batch task no longer runs under idprio, compare host load and runnable work. The idletime class is intended to yield to runnable processes; its lack of progress during sustained normal load is not proof that the command is broken. If completion must happen by a deadline, schedule and budget the work as normal managed capacity rather than relying on idle opportunities.
Acceptance criteria
A scheduling adjustment is accepted when the workload and target process are identified, the selected class matches the actual requirement, the change is bounded, and a repeatable test improves the named metric without starving system services. The original policy and rollback command must be recorded, and the process class should be rechecked after restart or reboot.
FreeBSD provides powerful scheduling controls, but priority is not a substitute for fixing blocking I/O, CPU affinity mistakes, or overloaded work queues. Start with measurement, prefer the least intrusive class, and reserve realtime scheduling for a narrowly justified use case with active safeguards.
Related:
- FreeBSD CPU Power Management: Driver-Aware Frequency Tuning in Production
- FreeBSD CPU Sets and NUMA: Affinity Without False Isolation
Sources: