当前位置:首页>Linux>Linux实时系统里最常见的 10 个坑:优先级、锁、中断、绑核到底怎么避?

Linux实时系统里最常见的 10 个坑:优先级、锁、中断、绑核到底怎么避?

  • 2026-10-11 06:05:56
Linux实时系统里最常见的 10 个坑:优先级、锁、中断、绑核到底怎么避?

很多人第一次做实时系统,容易有一个错觉:

把实时补丁打上,线程优先级拉高,再跑一遍 cyclictest,事情就差不多了。

真正到了现场,很快会发现不是这样。

实时系统最麻烦的地方,不是“某一个机制不会用”,而是很多小问题单独看都不致命,但叠在一起,就足够把关键线程拖垮。

比如:

• 优先级关系乱

• 锁拿得太久

• 中断打在关键核上

• 网卡和控制线程混跑

• 关键路径里动态分配内存

• 热路径里同步打日志

• CPU 绑了,但环境并不干净

这些问题每一个都可能只带来几十微秒、几百微秒的抖动。

但工业现场最怕的,正是这些抖动叠加。

这一篇不讲抽象原理,直接讲实时系统里最常见的 10 个坑,以及怎么避。

一句话先讲明白

实时系统最怕的,不是某个参数没调好,而是关键线程前面同时堆了优先级错误、锁等待、中断干扰、CPU 混跑、内存抖动、驱动慢路径。

避坑的核心,也不是猛调某一个参数,而是顺着关键线程,把“唤醒、调度、执行”这条主路径一层层清干净。

第一坑:把“高优先级”当成万能药

最常见的错误是:

关键线程不稳,那就继续加优先级。

问题是,优先级不是奖励,而是抢占权。

如果系统里一堆线程都被提成高优先级,最后通常会变成:

• 关键线程互相抢

• 后台线程被压死

• 日志、喂狗、恢复线程跑不起来

• 问题看起来更随机

很多现场问题不是关键线程优先级不够高,而是高优先级线程太多,层级关系乱了。

怎么避?

• 按 deadline 排,不按“感觉重要”排

• 高优先级线程数量要少

• 明确谁必须高于谁,谁可以低一点

• 不要让一堆线程挤在同一个实时优先级

• 给日志、恢复、喂狗线程留下运行空间

实时系统里,优先级不是越高越好,而是关系越清楚越好。

第二坑:只看单个线程优先级,不看整体优先级关系

很多人会说:

这个线程我已经给很高优先级了。

但真正决定系统稳不稳的,不是某个线程“高不高”,而是它和其他线程的相对顺序对不对。

尤其在 PREEMPT_RT 系统里,很多中断会线程化。

这时不只要看用户态控制线程,还要看 IRQ 线程、通信线程、采样线程、后台线程。

比如控制线程优先级很高,但它依赖网卡收包。

如果网卡 IRQ 线程优先级太低,数据迟迟进不来,控制线程再高也只能等。

怎么避?

• 列出所有关键线程

• 把控制链、采样链、通信链、后台链分清楚

• 把 IRQ 线程优先级也纳入排序

• 看清关键线程依赖谁,谁又可能打断它

• 不要只排用户线程,忽略内核线程和 IRQ 线程

调优第一步,不是调参数,而是把优先级关系画清楚。

第三坑:锁用得太重

实时系统里,锁不是不能用。

真正危险的是:

• 临界区太大

• 锁里做复杂逻辑

• 锁里做 I/O

• 锁里打日志

• 高低优先级线程共用同一把重锁

这类问题最容易造成一个假象:

高优先级线程明明已经被调度了,但就是推进不下去。

原因不是它没拿到 CPU,而是它卡在锁前面。

怎么避?

• 锁里只放最小必要动作

• 不在锁里做日志、I/O、复杂计算和长循环

• 热路径尽量少共享资源

• 关键线程尽量不要和后台线程共用重锁

• 关键锁优先使用支持优先级继承的机制

锁的问题不在于“有没有锁”,而在于高优先级线程会不会被低优先级路径挡住。

第四坑:忽略优先级反转

很多实时问题表面看像“线程偶发超时”,根因其实是优先级反转。

典型场景是:

低优先级线程拿着锁。

高优先级线程需要这把锁,于是等待。

中优先级线程不断运行,把低优先级线程压住。

低优先级线程迟迟不能释放锁,高优先级线程也就一直等。

最麻烦的是,它表面看起来不像“低优先级线程影响了高优先级线程”。

因为真正占 CPU 的,可能是中优先级线程。

怎么避?

• 缩短持锁时间

• 减少跨优先级共享资源

• 关键路径使用支持优先级继承的锁

• 避免低优先级线程持有高优先级线程需要的资源

• trace 时重点看高优先级线程是不是卡在锁前

• 不要只看谁在跑,还要看谁在等谁

很多“偶发卡顿”不是 CPU 不够,而是等待关系错了。

第五坑:中断路径太重

很多驱动和系统抖动问题,最后都能落到一句话:

中断里做太多。

中断路径太重,常见后果是:

• 周期线程晚醒

• 调度延迟尖峰变大

• 实时线程被中断前置工作挡住

• 平均负载不高,但最大延迟很难看

尤其是网卡、采集卡、串口、CAN、USB、存储设备,一旦中断频率高或者后续处理重,就容易影响实时线程。

PREEMPT_RT 会把很多中断线程化,但不等于中断没有影响。

线程化以后,它仍然要占 CPU,仍然要参与优先级竞争。

怎么避?

• ISR 只做最少必要动作

• 大量处理后移到线程上下文

• 高频中断源重点盯

• 检查 /proc/interrupts,看中断是否集中打在关键 CPU 上

• 对 IRQ 线程设置合理优先级

• 出现尖峰时,优先排查中断路径是否过长

中断不是背景噪声,它经常就是延迟尖峰的来源。

第六坑:把网卡和实时线程放在同一个核上

这是现场特别常见的一类坑。

系统里只要有:

• 高频收包

• TCP/UDP 通信

• SSH 远程访问

• 日志上传

• 监控服务

• NAPI poll 活跃

如果它们和关键实时线程跑在同一个 CPU 上,实时性通常会明显变差。

很多人只看到实时线程绑在 CPU2 上,却没看网卡 IRQ、软中断、NAPI 处理是不是也在 CPU2 上。

结果就是:

线程是绑住了,但干扰也绑过来了。

怎么避?

• 实时线程和高频网卡 IRQ 分核

• 检查 /proc/interrupts,确认网卡中断落在哪些 CPU 上

• 迁走网卡 IRQ 和相关高频干扰

• 注意软中断和 NAPI poll 对 CPU 的影响

• 不要只绑线程,不管 IRQ

• 实时控制核尽量不要承担普通网络业务

很多抖动不是控制线程的问题,而是网络路径把 CPU 打乱了。

第七坑:只做任务绑核,不做 IRQ 绑核

很多人会把关键线程绑到某个 CPU 上。

这一步没错,但只做这一步远远不够。

因为线程虽然固定了,中断可能还在这个 CPU 上乱打。

最后就会出现:

关键线程没有迁移,调度策略也没问题,优先级也不低,但延迟还是抖。

原因可能就是:

你只管了任务,没有管 IRQ。

怎么避?

• 任务绑核和 IRQ 绑核一起做

• 看关键 CPU 上到底有哪些 IRQ

• 把网卡、存储、USB、串口等高频 IRQ 移出关键核

• 必要时停掉 irqbalance,再手工规划 IRQ affinity

• 调整后用 /proc/interrupts 验证,不要凭感觉

绑核不是把线程固定住就结束了。

真正要做的是让关键 CPU 少被无关工作打扰。

第八坑:绑核了,但 CPU 其实并不干净

很多人把实时线程绑到 CPU2,就以为 CPU2 已经是“实时核”了。

但这个 CPU 上可能还在跑:

• 周期 tick

• RCU 回调

• 内核 workqueue

• 普通服务线程

• 软中断

• 定时器回调

所以线程虽然没有迁移,CPU 环境仍然不干净。

从应用层看,线程 affinity 是对的。

但从系统层看,这个 CPU 仍然在承载杂活。

怎么避?

• 先做任务绑核

• 再做 IRQ 迁移

• 检查关键 CPU 上还有哪些内核线程和后台任务

• 必要时配置 isolcpus、nohz_full、rcu_nocbs

• 把普通服务线程移出关键 CPU

• 不要只看 affinity,要看这个 CPU 上到底还在跑什么

实时核不是“绑了一个线程的核”,而是“尽量只服务关键路径的核”。

第九坑:在关键路径里动态分配内存、打大量日志

这类问题特别像:

平时正常,偶发变慢。

动态分配内存可能走到不可预测的慢路径。

日志可能引入字符串格式化、锁竞争、缓冲区等待、I/O 阻塞。

大量数据访问还可能带来 cache 抖动和内存带宽竞争。

放到实时关键路径里,这些都会变成延迟尖峰。

尤其是同步日志,非常危险。

很多现场问题最后发现,不是控制算法慢,而是关键线程在打印、刷盘、抢日志锁。

怎么避?

• 关键路径预分配内存

• 关键线程不频繁 malloc/new

• 热路径少打日志,尤其不要同步打印

• 日志尽量异步化、降频、限流

• 把调试信息和实时路径解耦

• 关键线程启动后尽量避免触发缺页和大块内存申请

实时路径里最贵的,不一定是算法,而是那些“顺手写上去”的辅助动作。

第十坑:一上来就拍脑袋查问题,没有顺着关键线程走

这是最大的一类坑。

实时系统一出问题,很多人会马上到处看:

• CPU 负载

• 调度策略

• 中断数量

• 网卡流量

• 锁竞争

• PREEMPT_RT 配置

• 内核参数

• 驱动实现

这些都要看,但不能一上来乱看。

越是复杂系统,越要先抓主线:

先盯关键线程。

先确认它到底卡在哪一段:

• 没按时醒

• 醒了但没拿到 CPU

• 拿到 CPU 但执行推进太慢

这三个问题,对应的排查方向完全不同。

• 没按时醒:看定时器、中断、唤醒路径

• 醒了没跑:看调度、优先级、CPU 是否被更高优先级任务占住

• 跑了还慢:看锁、内存、I/O、cache、驱动路径和共享资源

怎么避?

• 永远从关键线程倒推

• 先分清问题发生在唤醒、调度还是执行阶段

• 不要一上来就认定是 CPU 或调度器

• 用 trace 看关键线程的实际时间线

• 先看主路径,再看旁路噪声

• 每次只验证一个假设,不要同时乱改一堆参数

实时问题最怕凭感觉查。

先把关键线程的时间线拉出来,很多问题自然会变清楚。

最实用的一套避坑顺序

如果只想记一套现场可用的方法,可以按这个顺序来:

1. 先把关键线程找出来

不要一上来优化全系统。

先明确谁是真正有 deadline 的线程,谁只是辅助线程,谁只是后台线程。

2. 把优先级关系排清楚

不要只问“这个线程优先级高不高”。

要问:

谁绝不能被谁挡住?

谁依赖谁?

谁可以慢一点?

IRQ 线程应该排在哪一层?

3. 把线程和 IRQ 分开

关键线程不要和高频中断共核。

尤其是网卡、采集卡、存储、USB 这类设备,要重点检查 IRQ affinity。

只绑任务不绑 IRQ,很多时候等于只做了一半。

4. 把锁和共享资源收紧

沿着关键路径查一遍:

有没有重锁?

有没有跨优先级共享资源?

有没有锁里打日志、做 I/O、跑复杂逻辑?

高优先级线程是不是在等低优先级线程释放资源?

5. 把 CPU 环境清干净

如果关键线程对延迟很敏感,就不要让它所在的 CPU 同时承担一堆杂活。

必要时做:

• CPU affinity

• IRQ affinity

• isolcpus

• nohz_full

• rcu_nocbs

• 后台服务迁移

目标不是参数好看,而是让关键 CPU 尽量少被打扰。

6. 再看内存、DMA、cache 和驱动路径

前面几层清完以后,如果还有尖峰,再往更深处看:

• 动态内存分配

• 缺页

• DMA 同步

• cache miss

• 驱动慢路径

• 总线竞争

• 设备固件响应时间

这一步通常不是解决大方向问题,而是收尾尖峰。

最后怎么一句话记住?

实时系统最常见的坑,不是某一个参数没配,而是优先级、锁、中断、绑核、内存和驱动路径一起把关键线程拖慢。

避坑的核心也不是头痛医头,而是顺着关键线程,把“唤醒、调度、执行”这条主路径一层层清干净。

先清主路径,再清噪声。

最新文章

随机文章