关注+星标公众号,不容错过精彩

作者:HywelStar
假设你在做一个工业机械臂控制系统,要求每 1ms 精确执行一次控制循环。你用普通的 Linux 跑了一段时间,发现大多数时候都没问题,但偶尔会出现 20ms、甚至 50ms 的"卡顿"——机械臂抖了一下。
这不是你的代码有 bug,而是普通 Linux 内核本身的设计决定了它无法满足实时。
今天这篇文章,就来聊清楚:什么是实时性、普通内核为什么做不到、PREEMPT_RT 如何解决这个问题,以及你怎么快速上手验证。
很多人第一次听到"实时",以为是"很快"。其实不对。
实时性的核心不是快,而是可预测。
一个实时系统的要求是:在规定时间内,一定能完成任务。
实时系统还分软实时和硬实时。工业控制、汽车 ECU 这类场景要求硬实时——错过 deadline 就是故障。
普通 Linux 的调度器设计目标是整体吞吐量最大化,不是最坏延迟最小化。主要有三个"拦路虎":
① 内核不可抢占区域
普通内核里有大量使用自旋锁(spinlock)保护的临界区。当一个低优先级任务持有自旋锁时,高优先级任务只能等着——哪怕你把任务优先级设再高也没用。这段等待时间完全不可控。
② 中断会阻塞所有任务
外设中断(比如网卡、USB)触发时,当前 CPU 会停下来执行中断服务程序(ISR)。ISR 执行期间,任何任务都被阻塞,包括你的"高优先级实时任务"。
③ 内核线程优先级混乱
普通内核里的 softirq、workqueue 等机制会在低优先级内核线程中处理大量工作,调度顺序难以精确控制。
4. 锁机制导致优先级反转
低优先级任务持有临界区锁,高优先级任务等待锁,而中等优先级任务抢占低优先级任务,导致高优先级任务被无限阻塞,这就是致命的优先级反转(Priority Inversion),标准 Linux 原生没有完善的优先级继承机制。
这四个问题加在一起,导致普通 Linux 的最坏调度延迟可能高达几十毫秒,而且无法预测。
PREEMPT_RT 是一组内核补丁(现在已逐步合入主线),核心思路是:让内核的几乎每一个地方都可以被高优先级任务抢占。
它主要做了三件事:
① 把自旋锁换成可睡眠的互斥锁
原本持有自旋锁时 CPU 会忙等(spin),现在换成 rt_mutex,持有锁的任务可以被挂起,让更高优先级的任务先跑。这是最关键的改动。
② 中断线程化(Threaded IRQ)
把绝大多数中断处理程序变成普通的内核线程(irqthread),这样它们就有了优先级,实时任务可以抢占中断处理线程。
③ 全面可抢占内核
通过更细粒度的锁和调度点,内核态代码的大部分路径都变得可抢占,大大缩短了高优先级任务被阻塞的时间窗口。
效果是什么?在打了 PREEMPT_RT 补丁的内核上,经过合理配置,最坏调度延迟可以控制在几十到几百微秒,而且是可预测的。
光说不练没意义,下面带你跑一个最经典的实时延迟测试工具:cyclictest。
Step 1:安装工具
# Ubuntu / Debiansudo apt install rt-testsStep 2:跑基准测试(普通内核)
sudo cyclictest -l 100000 -m -S -p 90 -i 200 -h 400参数说明:
-l 100000:循环 10 万次-p 90:任务优先级设为 90(实时优先级)-i 200:每 200 微秒唤醒一次-h 400:统计延迟直方图Step 3:观察输出
你会看到类似这样的输出:
T: 0 ( 1234) P:90 I:200 C:100000 Min: 8 Act: 12 Avg: 14 Max: 876关注 Max 这一列——这是最坏延迟。普通内核上,这个数字可能飙到几百甚至上千微秒,而且每次跑结果不一样。
Step 4:换 RT 内核后对比
如果你的系统装了 PREEMPT_RT 内核(比如 Ubuntu 的 linux-image-rt-amd64),切换过去重跑同样命令,你会发现 Max 稳定很多,通常控制在 100~300 微秒以内。
Q:我用树莓派可以跑 PREEMPT_RT 吗?
可以。树莓派有社区维护的 RT 内核镜像,搜索 raspberry pi preempt-rt 即可找到。延迟比 x86 平台高一些,但做软实时任务完全够用。
Q:打了补丁是不是系统就变慢了?
整体吞吐量会略有下降(通常 5%~15%),因为更多的上下文切换开销。嵌入式场景一般可以接受。
Q:光打补丁够吗?
不够。还需要配合:隔离 CPU(isolcpus)、关闭 CPU 节能模式、设置合适的调度策略(SCHED_FIFO),以及避免在实时线程里使用动态内存分配。这些是后续文章的内容。
普通 Linux 的调度延迟不可预测,根本原因是内核中存在不可抢占的临界区和无优先级的中断机制。PREEMPT_RT 通过将自旋锁互斥化、中断线程化和全面可抢占内核三大改动,把最坏延迟压缩到可预测的微秒级。
动手的第一步很简单:装好 rt-tests,用 cyclictest 跑一跑,感受一下数字的变化。
往期推荐
STM32 擦除失败,解析Option Bytes陷阱
嵌入式里的NPU它到底能干什么?
Linux 中的 buffer 与cache
嵌入式工程师都该懂一点安全
面试技巧-2.注意要点
嵌入式中动态库路径的那些坑
嵌入式开发中常用存储认识
为什么Linux驱动会自动匹配?
设备树:每一个 Linux 驱动工程师的起点
嵌入式软件开发求职指南
A/B 分区 OTA 升级机制与 U-Boot 实现
嵌入式系统 OTA 固件升级
戳“阅读原文”一起来充电吧!