Real-Time Linux Kernel Configuration for Robot Controllers
Kernel 6.12 finally bakes real-time guarantees into Linux for robot control.

A general-purpose Linux kernel is built to move as much work through the system as it can, while keeping things fair across competing processes. Robot control asks for something different: not speed on average, but a guarantee that every control cycle finishes inside its deadline, every time. The standard kernel disables preemption during critical sections, holds spinlocks with interrupts turned off, and batches work to raise throughput. Every one of those choices is sound for a desktop or a server answering web requests. Every one of them can stall a control loop long enough to destabilize a robot arm or crash a drone. Real-time computing means code with a bounded worst-case execution time, and a standard kernel gives no such bound: a control thread can be ready to run and still sit waiting behind a lower-priority task that happens to be holding a lock the kernel itself needed. A scheduling study of UAV flight control workloads on a Raspberry Pi 5 shows what this looks like in practice. When you run the default scheduler on a standard kernel, latency can spike past 9 milliseconds, far beyond the control deadline the test used. The hardware had not slowed down. The kernel simply offered no guarantee about how long any given thread might wait, and on that occasion, it waited too long.
What PREEMPT_RT changes inside the kernel
PREEMPT_RT closes that gap with four structural changes, and each one targets one of the delays described above. The first targets spinlocks directly: spinlocks, which in a standard kernel block any other thread from running on that core while held, become sleeping rt-mutexes under PREEMPT_RT. That means a higher-priority thread can preempt a lower-priority one even while it executes kernel code, which was previously treated as off-limits to preemption. The second change goes after the problem of interrupt handling running outside the scheduler's view: hardware interrupt handlers are converted into schedulable kernel threads, so interrupt processing is no longer an invisible, unpriced cost that can displace a control thread at any moment. Instead, it competes for the CPU under the same priority rules as everything else. The third change solves priority inversion, the specific failure where a low-priority thread holds a lock that a high-priority thread needs, while a medium-priority thread that holds no such lock keeps running in between and starves both. The rt-mutexes introduced by PREEMPT_RT implement priority inheritance: the low-priority thread holding the contested lock temporarily borrows the high priority of the thread waiting on it, finishes quickly, and releases the lock. None of these changes make the kernel faster in the way a compiler optimization does. They make its timing behavior something a control engineer can reason about and bound, which is the actual requirement.
How kernel 6.12 removed the decade-long patching burden
For nearly two decades, using PREEMPT_RT meant finding a separately maintained patch file and applying it to a kernel source tree, with the patch version needing to match the target kernel version precisely. The patchset had been under development since 2005, and the Linux Foundation established the Real-Time Linux (RTL) Collaborative Project in 2015 specifically to coordinate the long effort of pushing it into mainline Linux and to fund the engineering work needed to sustain it. That effort reached its conclusion with kernel 6.12, released in November 2024, which merged PREEMPT_RT fully into the mainline source tree. Kernel 6.12 is also designated as that year's Long Term Support release, making it the stable baseline that distributions and integrators can build on for years. So anyone building a real-time Linux system today can start from the same kernel source everyone else uses. On kernel 6.12 and later, the option simply appears inside make menuconfig, under the preemption model settings, once CONFIG_EXPERT has been turned on to expose it. The patch-matching exercise that absorbed real engineering time for a decade is no longer part of the job.
Enabling the core kernel configuration options
Three kernel build options form the base layer that every real-time Linux deployment needs, and each one answers directly to a failure mode described above. CONFIG_PREEMPT_RT=y turns on the full preemptible kernel, activating all four of the structural changes covered earlier: rt-mutex spinlocks, threaded interrupts, priority inheritance, and the preemptible kernel code paths that let a high-priority thread cut in. CONFIG_HIGH_RES_TIMERS=y turns on the high-resolution timer subsystem, which a robot control loop needs if it wants to wake up at intervals measured in microseconds rather than the coarser ticks a default timer setup allows. CONFIG_NO_HZ_FULL=y stops the kernel from firing its periodic scheduler tick on CPUs that are idle or set aside for isolated use, removing a steady source of jitter that would otherwise interrupt a real-time thread at fixed intervals whether it needed interrupting or not.
Two further settings, handled outside menuconfig at the system level, close off the largest remaining sources of latency variation. CPU frequency scaling changes a core's clock speed on the fly to save power, but each transition takes execution time that varies unpredictably from one cycle to the next, and that is exactly the variance a control loop cannot tolerate. Disabling frequency scaling outright removes the problem, but where power draw still matters, setting the cpufreq governor to "performance" is a workable middle ground: the core runs at a fixed, known clock speed instead of hunting between states. Deep CPU idle states carry a similar cost. When a core drops into a C-state deeper than C1 to save power, waking it back up to execute a thread takes measurably longer than waking it from a shallow idle state, and that wake-up delay lands directly on the control loop's latency budget. Limiting the system to C1 is the standard fix: you trade a small amount of power efficiency for a core that's always close to ready.
Converting interrupt handlers to threads and setting scheduler priorities
Once PREEMPT_RT turns interrupt handlers into schedulable kernel threads, you still need to assign priorities to those threads, or the conversion buys nothing. The system designer sets priorities so that the interrupts the control loop actually depends on, the EtherCAT network interface interrupt is the typical example in industrial robotics, run above the priority of interrupts tied to non-critical devices. Without that step, a threaded interrupt handler can still be delayed behind unrelated work, just as the original unthreaded version could stall a control thread.
User-space real-time threads choose among several Linux scheduling policies, and the choice carries real trade-offs. SCHED_FIFO runs threads of equal priority in the order they became ready and keeps running the current thread until it blocks or a higher-priority thread appears, which makes it the most common choice for a hard real-time control thread that needs to run to completion once started. SCHED_RR behaves the same way but adds a time quantum, rotating among threads of equal priority, a useful choice when more than one equal-priority real-time thread has to share a single core without one of them starving the others. SCHED_DEADLINE takes a different approach entirely: the thread tells the kernel its period and deadline up front, and the kernel's admission control decides whether it can actually meet that promise before scheduling it. SCHED_OTHER, the default policy for ordinary processes, belongs nowhere in a hard real-time control path.
The UAV study on the Raspberry Pi 5 puts numbers on what these choices buy once PREEMPT_RT is active. SCHED_FIFO at priority 99 brought maximum latency down to 224 microseconds. SCHED_RR at the same priority reached 182 microseconds. SCHED_DEADLINE came in at 197 microseconds. All three sat comfortably within the test's control deadline, a result nowhere close to the standard kernel's unbounded latency from the earlier test. The gap between those three numbers matters less than the gap between any of them and the standard kernel's result: the policy chosen shapes the margin, while PREEMPT_RT makes any of the three viable.
Priority inheritance, one of PREEMPT_RT's four structural changes, only protects a real-time thread if the application's own code uses rt-mutexes, so you need pthread mutexes created with the correct protocol attribute set. A spinlock or a regular mutex in application code sidesteps the kernel's priority inheritance machinery entirely, and a low-priority thread can then block a high-priority one indefinitely at exactly the kind of shared lock the mechanism was built to protect against.
Isolating CPU cores so the real-time thread has exclusive access to compute
Correct scheduler priorities are not enough on their own. Kernel housekeeping tasks, RCU callback processing, and other routine OS activity can still land on the same core as a correctly prioritized real-time thread, and they consume cycles the control loop was counting on. CPU isolation removes that risk by taking specific cores out of general circulation entirely, and three boot parameters work together to do it. isolcpus= pulls the named CPUs out of the pool the general-purpose scheduler draws from, so no ordinary task gets placed there unless something explicitly pins it to that core. nohz_full= extends the tick-suppression behavior of NO_HZ_FULL to those named CPUs, stopping the scheduler tick when a single task is running there. rcu_nocbs= moves RCU callback processing off those same cores, removing a source of latency that is otherwise difficult to predict or bound.
Once isolated, a core needs the real-time application actually placed on it, which is done with taskset from the command line or, inside the application itself, with pthread_setaffinity_np. The interrupt the control loop depends on gets the same treatment: the EtherCAT or other fieldbus interrupt driving the loop is pinned to the identical isolated core the real-time thread runs on, so the path from hardware event to control loop execution never crosses a scheduling boundary between cores. An isolated core configured this way appears in /proc/interrupts and in a tool like htop as a core carrying almost nothing but the real-time thread and its one assigned IRQ, a visibly quiet core next to the normal churn on the rest of the machine.
Measuring whether the configuration meets deadlines with cyclictest
Every decision described so far, the kernel options, the scheduler policies, the isolated cores, earns its place only if it actually lowers the worst latency the system can produce. cyclictest is the standard tool for checking that. It creates high-priority threads that sleep for a fixed interval and then measures the gap between the requested wakeup time and the actual one, reporting the minimum, average, and maximum scheduling latency it observed over the run.
The maximum is the number that decides whether the configuration works. Average latency tells a control engineer almost nothing useful, because a hard real-time system fails the moment it misses a single deadline, not when its typical performance degrades. A test run also needs to last long enough to matter: a short run can easily miss the rare worst-case event that only appears under sustained operation, the kind of event that happens once every few hours. Even a kernel with PREEMPT_RT fully configured remains a general-purpose operating system underneath, and some of its other features can still introduce latency that no amount of scheduler tuning accounts for. Running cyclictest under the robot's actual application load, not on an idle system, is the only way to confirm that the complete configuration holds up together rather than just the kernel settings tested in isolation.
Franka, LinuxCNC, and XBot2 in Production
Three production systems, a research-grade robot arm, a CNC and robot controller suite, and a multi-platform middleware framework, show this configuration chain working outside the test bench, each one carrying a different piece of the argument.
Franka Robotics documents a PREEMPT_RT kernel as optional but recommended for its Panda arm generally, and the franka_ros2 repository states it as required specifically for the cartesian_pose, joint_position, and elbow_position command interfaces, the control modes where timing slips translate directly into motion error. A 2026 paper restoring ROS 2 support for the Panda ran its experiments on a workstation running Ubuntu 24.04 patched with PREEMPT_RT and ROS 2 Jazzy, connected to the robot over a dedicated Ethernet link. The architecture in that paper separates concerns in a way that matches the isolation principle running through this guide: the ros2_control thread and the real-time thread pass state between each other through mutex-protected buffers, so jitter on the ROS side of the system never propagates down to the robot itself. The real-time thread talks to the robot's onboard controller over UDP, and it has to match a slower, less predictable command stream up to the robot's required 1 kHz control rate through a dedicated rate-matching stage. The same paper shows that if you design that buffer isolation carefully, some control tasks can run correctly even on a non-real-time kernel, which points toward architectures where real-time guarantees live in a narrow, deliberately isolated path rather than depending on every layer of the operating system stack. Across a research arm built on ROS 2, a CNC controller suite, and a middleware framework spanning multiple robot platforms, the same underlying pattern holds: PREEMPT_RT, correct scheduler priorities, and CPU isolation form a chain, and a production robot controller is only as reliable as the weakest link in it.
Sources
- Keeping the Franka Emika Panda alive: a ROS 2 stack with a reliable position interface
Provided the Franka Panda ROS 2 case study details, including the Ubuntu 24.04 PREEMPT_RT setup, mutex-protected buffer architecture, and 1 kHz control rate requirement.
- Scheduling Analysis of UAV Flight Control Workloads on PREEMPT_RT Linux Using a Raspberry Pi 5
Provided the UAV flight control latency measurements on a Raspberry Pi 5, including the 9 ms standard-kernel spike and the SCHED_FIFO/RR/DEADLINE results under PREEMPT_RT.
- [LINK] Real-Time Linux is finally in the mainline kernel
Confirmed that PREEMPT_RT was merged into mainline Linux with kernel 6.12 in November 2024, ending the decades-long separate patching process.
- [LINK] Linux 6.12 is the new long term supported kernel
Confirmed that kernel 6.12 is designated as that year's Long Term Support release.
- Kernel configuration options - Real-time Ubuntu documentation
Provided reference for the core kernel configuration options including CONFIG_PREEMPT_RT, CONFIG_HIGH_RES_TIMERS, and CONFIG_NO_HZ_FULL.
- Theory of operation — The Linux Kernel documentation
Provided the theoretical basis for PREEMPT_RT's structural changes including rt-mutex spinlocks, threaded interrupts, and priority inheritance.


