3. Linux内核与实时性
在机器人实时控制中,我们常把“抖动”视为头号敌人。一个 1 kHz 的控制环,周期是 1 ms,如果某次唤醒晚了 300 μs,机械臂末端就可能出现肉眼可见的抖动,甚至触发力控失稳。要理解这种抖动从何而来,必须回到 Linux 内核的调度与抢占机制。标准 Linux 为吞吐量优化,而 PREEMPT_RT 的目标,是把它改造成一个可预测的实时内核。
一、调度器演进:从 O(n) 到 CFS,再到实时类
Linux 调度器经历了三次重要演进。早期 2.4 的 O(n) 调度器每次选进程都要遍历全部就绪队列,进程越多开销越大;2.6 引入 O(1) 调度器,用活动/过期数组把选择复杂度降到常数;2007 年 2.6.23 引入 CFS(完全公平调度器),用红黑树按虚拟运行时间 vruntime 排序,追求公平而非低延迟。
但机器人控制真正依赖的是实时调度类。Linux 调度类优先级从高到低为:STOP > DEADLINE > RT(SCHED_FIFO / SCHED_RR)> CFS(SCHED_OTHER)> IDLE。SCHED_FIFO 的线程一旦运行,除非主动让出或被更高优先级 RT 线程抢占,否则一直运行。控制线程通常配置为 SCHED_FIFO,优先级 80 左右,配合 CPU 亲和性绑定到隔离核。
# 查看线程调度策略与优先级
chrt -p $(pgrep -f robot_control)
# 以 SCHED_FIFO 优先级 80 启动控制进程
chrt -f 80 ./robot_control
# 绑定到 CPU2,并设置内存锁定
taskset -c 2 chrt -f 80 ./robot_control
二、抢占模型:决定延迟上限的关键开关
内核抢占模型决定了“一个高优先级任务最多等多久才能拿到 CPU”。Linux 提供四种配置,从低到高依次是:
| 抢占模型 | 配置项 | 内核态可抢占性 | 典型最坏延迟 |
|---|---|---|---|
| 无抢占 | PREEMPT_NONE | 内核态不可抢占 | 数十毫秒 |
| 自愿抢占 | PREEMPT_VOLUNTARY | 仅在显式抢占点 | 数毫秒 |
| 完全抢占 | PREEMPT | 内核态大部分可抢占 | 数百微秒 |
| 实时抢占 | PREEMPT_RT | 几乎全部可抢占 | 数十微秒 |
可以看到,仅开启 PREEMPT 仍不足以满足 1 kHz 控制环。因为内核中大量自旋锁、中断禁用区和软中断上下文仍会阻塞高优先级任务。PREEMPT_RT 正是为消除这些阻塞点而生。
三、内核延迟来源:抖动到底从哪里来
实时延迟可拆成四段:中断响应延迟、中断处理延迟、调度延迟、以及任务自身执行延迟。常见来源包括:
- 中断禁用区:spin_lock_irqsave 期间中断被关闭,高优先级中断无法及时响应。
- 软中断与 tasklet:网络收包、定时器等软中断会在不可抢占上下文中批量执行。
- 自旋锁:持有自旋锁的代码段不可被抢占,长临界区直接拉高延迟。
- 页错误与内存分配:缺页会触发磁盘 I/O,造成毫秒级停顿。
- CPU 频率调节与 C-state:降频和深度睡眠唤醒需要额外时间。
- 缓存与 TLB 抖动:其他任务污染缓存,导致控制线程执行时间波动。
四、PREEMPT_RT 的核心思想
PREEMPT_RT 补丁的核心可以概括为三句话:把不可抢占的临界区变成可抢占的、把中断处理线程化、把高精度定时器交给实时任务。具体手段包括:
- 自旋锁睡眠化:将 spinlock 替换为可睡眠的 rt_mutex,使临界区可被抢占,缩短阻塞时间。
- 中断线程化:把大部分硬中断处理程序转为内核线程,赋予实时优先级,可被更高优先级任务抢占。
- 软中断线程化:ksoftirqd 以线程方式运行,避免在中断返回路径中长时间占用 CPU。
- 高精度定时器:hrtimer 提供纳秒级定时精度,配合 clock_nanosleep 实现稳定周期唤醒。
- 优先级继承:rt_mutex 支持优先级继承,缓解优先级反转问题。
下图展示了标准 Linux 与 PREEMPT_RT 在“高优先级控制任务被阻塞”这一场景下的信号链差异:
五、案例与复盘:从 800 μs 抖动到 40 μs
某六轴协作机器人项目,控制周期 1 ms,最初使用标准 Ubuntu 内核 + SCHED_FIFO。用 cyclictest 测得最大延迟 820 μs,力控模式下末端抖动明显。排查发现:网卡中断频繁触发软中断,且内核未开启 PREEMPT_RT。改造步骤:
- 更换为 PREEMPT_RT 内核,配置 CONFIG_PREEMPT_RT=y。
- 将控制线程绑定到隔离核(isolcpus=2,3),并设置 nohz_full。
- 控制线程使用 SCHED_FIFO 优先级 80,mlockall 锁定内存。
- 网卡中断通过 IRQ affinity 迁移到 CPU0,避免干扰控制核。
- 关闭 CPU 频率调节,固定为 performance 模式。
# 验证实时延迟
cyclictest -t1 -p 80 -n -i 1000 -l 100000
# 典型输出
T: 0 ( 1234) P:80 I:1000 C: 100000 Min: 3 Act: 5 Avg: 6 Max: 41
改造后最大延迟从 820 μs 降到 41 μs,控制环抖动显著改善。复盘要点:实时性不是单一开关,而是内核抢占模型 + 调度策略 + CPU 隔离 + 中断迁移 + 内存锁定的系统工程。只改其中一项,往往收效有限。
理解 Linux 调度器演进、抢占模型与延迟来源,是掌握 PREEMPT_RT 的前提。PREEMPT_RT 并没有创造新的调度算法,而是通过中断线程化、自旋锁睡眠化和高精度定时器,把内核中原本不可抢占的路径逐一打开,让高优先级实时任务能够以可预测的微秒级延迟获得 CPU。这正是机器人实时控制能够稳定运行在 1 kHz 甚至更高频率的基础。