跳转至

嵌入式 Linux 实时性优化入门:PREEMPT_RT 到底解决了什么问题?

适合人群: 有 Linux 基础、想入门实时嵌入式开发的工程师


先从一个问题说起

假设你在做一个工业机械臂控制系统,要求每 1ms 精确执行一次控制循环。你用普通的 Linux 跑了一段时间,发现大多数时候都没问题,但偶尔会出现 20ms、甚至 50ms 的"卡顿"——机械臂抖了一下。

这不是你的代码有 bug,而是普通 Linux 内核本身的设计决定了它无法保证实时性

今天这篇文章,就来聊清楚:什么是实时性、普通内核为什么做不到、PREEMPT_RT 如何解决这个问题,以及你怎么快速上手验证。


一、什么是"实时性"?

很多人第一次听到"实时",以为是"很快"。其实不对。

实时性的核心不是,而是可预测

一个实时系统的要求是:在规定时间内,一定能完成任务

普通系统 实时系统
关注指标 平均响应时间 最坏响应时间(WCET)
典型场景 网页服务器 电机控制、音频采集
偶尔延迟 可以接受 不可接受

实时系统还分软实时和硬实时。工业控制、汽车 ECU 这类场景要求硬实时——错过 deadline 就是故障。


二、普通 Linux 为什么无法保证实时性?

普通 Linux 的调度器设计目标是整体吞吐量最大化,不是最坏延迟最小化。主要有三个"拦路虎":

① 内核不可抢占区域

普通内核里有大量使用自旋锁(spinlock)保护的临界区。当一个低优先级任务持有自旋锁时,高优先级任务只能等着——哪怕你把任务优先级设再高也没用。这段等待时间完全不可控。

② 中断会阻塞所有任务

外设中断(比如网卡、USB)触发时,当前 CPU 会停下来执行中断服务程序(ISR)。ISR 执行期间,任何任务都被阻塞,包括你的"高优先级实时任务"。

③ 内核线程优先级混乱

普通内核里的 softirq、workqueue 等机制会在低优先级内核线程中处理大量工作,调度顺序难以精确控制。

这三个问题加在一起,导致普通 Linux 的最坏调度延迟可能高达几十毫秒,而且无法预测。


三、PREEMPT_RT 做了什么?

PREEMPT_RT 是一组内核补丁(现在已逐步合入主线),核心思路是:让内核的几乎每一个地方都可以被高优先级任务抢占

它主要做了三件事:

① 把自旋锁换成可睡眠的互斥锁

原本持有自旋锁时 CPU 会忙等(spin),现在换成 rt_mutex,持有锁的任务可以被挂起,让更高优先级的任务先跑。这是最关键的改动。

② 中断线程化(Threaded IRQ)

把绝大多数中断处理程序变成普通的内核线程(irqthread),这样它们就有了优先级,实时任务可以抢占中断处理线程。

③ 全面可抢占内核

通过更细粒度的锁和调度点,内核态代码的大部分路径都变得可抢占,大大缩短了高优先级任务被阻塞的时间窗口。

效果是什么?在打了 PREEMPT_RT 补丁的内核上,经过合理配置,最坏调度延迟可以控制在几十到几百微秒,而且是可预测的。


四、动手验证:用 cyclictest 测延迟

光说不练没意义,下面带你跑一个最经典的实时延迟测试工具:cyclictest

Step 1:安装工具

# Ubuntu / Debian
sudo apt install rt-tests

Step 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 跑一跑,感受一下数字的变化。