今天这篇文章,带着大家系统性吃透 Linux 实时调度全体系。下面按调度类、内核抢占、锁、中断、截止时间调度和测量方法,把 Linux 实时调度这套东西从头到尾捋一遍。
一、为什么需要 Linux 实时调度?
1.1 普通 Linux 调度的短板:通用场景 vs 实时场景差异
普通服务器最关心吞吐、公平和整体响应:几十个线程竞争 CPU,内核尽量让每个线程都得到合理份额,牺牲某一次请求的唤醒时刻也未必影响总体成绩。实时系统的考题却是“第 N 次任务能不能在截止时间前完成”。把一段计算缩短 30%,如果同时出现一次长尾阻塞,控制环仍可能失稳。区别不在 CPU 算力是否足够,而在最坏情况下,任务从事件到达、进入可运行队列、真正拿到 CPU,到完成计算的整条路径能否受控。
工程里常见一种误判:top 显示 CPU 使用率只有 15%,于是认定系统很空,线程不可能迟到。可那 15% 是一段时间的平均值;目标 CPU 上一段不可抢占的内核路径、某次缺页、一个占锁的低优先级线程,足以制造尖峰。普通分时调度优化的是多个任务共享资源的总体体验,不能单靠“CPU 还剩很多”推导单次响应上界。

1.2 实时系统核心诉求:确定性、低延迟、可预测性
实时不是“跑得最快”,而是满足时间约束。假设传感器每 1 毫秒产生一次数据,执行器要求在采样后 600 微秒内更新输出,CPU 处理平均只花 80 微秒,剩下的预算还得覆盖中断响应、线程唤醒、锁等待、迁移、缓存失效与输出调用。软实时允许极少数迟到并以质量下降为代价;硬实时则要求对超期后果和上界做严格论证。Linux 可以构建很强的实时能力,但上界声明必须连硬件、驱动、应用、负载和测量证据一起讨论,不能看见 PREEMPT_RT 四个字就签保证书。
评价实时性通常同时看唤醒延迟、周期抖动和端到端完成时间。唤醒延迟是计划唤醒时刻到实际运行的差;抖动描述多次周期事件的时间变化;完成时间还包括应用自己的计算、I/O 和等待。最小值好看没什么意义,均值也会掩盖尾部,真正要盯的是业务期限、超期次数、尾部分布和测到的最大值。测到最大值也只是“本次压力场景下观测到的最大值”,不是数学证明的绝对上界。

二、基础铺垫:Linux 调度核心前置知识
2.1 进程与线程调度基本概念、调度实体与调度域
Linux 真正交给调度器选择的是任务 task_struct,用户眼中的一个进程可以包含多个线程,每个线程可单独设策略、优先级与 CPU 亲和性。任务进入可运行状态之后,先挂到相应 CPU 的运行队列;调度类再从自己的队列挑选候选者。公平类有 sched_entity 和虚拟运行时间等信息,实时类使用 sched_rt_entity,Deadline 类使用 sched_dl_entity;“调度实体”这个词描述的是参与对应调度器管理的数据与层级,不能理解成另起一个用户进程。
每 CPU 一个运行队列能减少全局锁竞争,但带来负载均衡和迁移问题:线程从 CPU 2 跑到 CPU 5,除了重新选择运行队列,还可能丢掉热缓存。调度域描述多核拓扑与负载平衡范围,和线程自己的 CPU 亲和掩码不是一个概念;后者限定“允许在哪些 CPU 跑”,前者帮助内核在可选 CPU 之间做平衡。看延迟日志时先确认发生了唤醒、排队、迁移中的哪一步,别把三个阶段都叫“调度慢”。

2.2 普通分时调度 CFS 机制核心原理与局限性
经典 CFS 用 vruntime 记录任务按权重折算的已获 CPU 时间,nice 值通过权重影响份额;选取虚拟运行时间靠前的实体,目标是维持长期公平。它不是为用户线程签下一张“X 微秒必定运行”的合同。某个线程刚醒来,公平类仍要平衡别的可运行任务;内核里不可抢占的片段、IRQ 和锁等待,也不会因为 CFS 的公平性自动消失。
这里必须更新一处老文章常写错的说法:Linux 从 6.6 开始向 EEVDF 演进,当前讨论 SCHED_OTHER 不宜概括为“永远用最小 vruntime 的红黑树节点”。EEVDF 仍在公平调度类里考虑虚拟运行时间,同时利用 lag 与虚拟截止时间改善响应;但它的虚拟截止时间不是 SCHED_DEADLINE 的业务截止时间,也不承诺硬实时。老 CFS 模型适合解释公平分时的出发点,分析具体内核行为要看正在运行的版本。

2.3 调度优先级、时间片、抢占机制基础定义
优先级先分层再分数字:实时类 SCHED_FIFO / SCHED_RR 常用静态优先级 1~99,数字越大优先级越高;普通类的 nice 范围是 -20~19,数值语义与 RT 优先级不可直接相减比较。Linux 的调度类选择顺序通常是 stop、deadline、rt、fair、idle;把一个线程设成 FIFO 50,意味着它在需要运行且没有更高类或更高优先级竞争时,可以压过普通公平类线程,不意味着打断一切内核代码。
时间片也要分清:RR 同优先级轮转有时间量子,FIFO 没有固定的时间片到点轮换规则;公平类的运行时间分配是另一套机制。抢占发生在高优先级任务就绪、当前上下文允许切换且调度器有机会执行时;若当前 CPU 暂时关中断、关抢占或卡在特殊低层路径,高优先级线程仍要等。使用 sched_yield() 会主动让出 CPU,但它不是实现稳定周期的定时器,忙循环更不能代替合格的等待机制。

2.4 实时任务与普通任务的内核区分规则
内核首先按调度策略把任务交给不同调度类:SCHED_OTHER、SCHED_BATCH、SCHED_IDLE 属于普通调度范畴,SCHED_FIFO 与 SCHED_RR 归实时调度类,SCHED_DEADLINE 自成一类。把线程 nice 设成 -20,依然没有进入 FIFO/RR;把线程设成 FIFO 也不会自动锁住内存或把 IRQ 迁到同一颗核。可通过 chrt -p <TID> 查看策略和优先级,通过 ps -L -p <PID> -o pid,tid,cls,rtprio,pri,comm 查线程级状态。SCHED_DEADLINE 的 sched_priority 为 0,其调度依据是运行预算与截止时间,不在 1~99 这把标尺里。
三、Linux 实时调度整体架构与内核配置
3.1 两大实时调度体系:POSIX 标准实时调度
先纠一下标题中的潜在歧义:“两大体系”更适合理解为传统固定优先级实时策略与 Linux 的 Deadline 策略,而不能把它们统称为“两套 POSIX 标准”。POSIX 所定义的 SCHED_FIFO 与 SCHED_RR 都按静态优先级竞争 CPU,同优先级时分别遵守 FIFO 排队和 RR 时间量子;Linux 提供 SCHED_DEADLINE 作为额外的截止时间调度策略,通过 Linux 专有的 sched_setattr() 设置三项时间参数。这种区分关系到可移植性:一段只依赖 POSIX pthread 调度接口的代码,与使用 Linux Deadline 系统调用的代码,迁移成本不一样。
策略是否“实时”也不能脱离应用作业模型。FIFO 擅长明确的优先顺序,却要求开发者审计每个高优先级任务最长连续运行时间;RR 只改善相同优先级任务的分享;Deadline 则把每期预算与期限定量写给内核。它们管 CPU 排队,无法为驱动、内存和外部网络的不可控等待打包票。
3.2 内核实时模块组成:调度类、抢占模型、延迟机制
把实时内核想成一套分层系统更清楚:kernel/sched/rt.c 管 FIFO/RR 的固定优先级选取,kernel/sched/deadline.c 管 EDF/CBS,kernel/sched/core.c 等负责通用切换与唤醒路径;抢占模型决定运行在内核态时多大范围能被更高优先级任务打断;锁、IRQ、定时器、软中断与 RCU 的执行上下文决定最长不可抢占区间。还要叠加 RT 带宽控制、CPU 隔离、任务权限和用户态内存管理。只编译了调度类而没有处理临界区,并不会凭空缩短所有最坏延迟。
一条实际链路是网卡 IRQ 到来、驱动搬运数据、唤醒应用线程、线程抢 CPU、拿锁、计算、发出控制指令。perf sched 看到的排队时间只是其中一截;cyclictest 主测定时唤醒到执行,真实业务还要自己在输入与输出处打时间戳。定位时画清这条链,哪个环节耗掉 500 微秒,才有资格谈优化。

3.3 实时内核必备配置:PREEMPT 抢占模式详解
可以把内核配置理解成从 CONFIG_PREEMPT_NONE、CONFIG_PREEMPT_VOLUNTARY、CONFIG_PREEMPT 到 CONFIG_PREEMPT_RT 的不同取舍:前者更偏吞吐与少抢占,自愿抢占在明确点位让路,完全抢占允许更多内核代码被抢占,RT 模式进一步改变锁和中断上下文。不同版本、架构、发行版可能使用 CONFIG_PREEMPT_DYNAMIC 与不同的菜单名称,列出“四种模式”是认知框架,绝不是所有机器都能在运行时直接切换四档。先检查当前构建与运行状态,再谈调参。
uname -r
zcat /proc/config.gz 2>/dev/null | grep -E '^CONFIG_PREEMPT|^# CONFIG_PREEMPT'
grep -E '^CONFIG_PREEMPT|^# CONFIG_PREEMPT' /boot/config-$(uname -r) 2>/dev/null
cat /sys/kernel/realtime 2>/dev/null || true
上面两种配置文件路径不保证都有;/sys/kernel/realtime 是否存在也依内核构建而定。确认时优先以当前启动的 uname -r 对应配置、启动参数和实际延迟表现交叉验证,不要拿安装目录里另一份内核配置截图当证据。
3.4 标准内核 vs RT 补丁内核的核心差异
标准内核也支持 FIFO、RR 和 Deadline,SCHED_FIFO 绝不是 RT 补丁装上才出现。PREEMPT_RT 的关键是让过去可能长时间阻挡调度器的路径可以被抢占:把许多 spinlock_t 变成基于 rtmutex 的可睡眠锁并提供优先级继承,强制大多数中断线程化,调整软中断、定时器和 RCU 的执行场景;底层调度器、原始自旋锁、少量 IRQ 硬处理仍保留严格上下文。代价可能是锁路径开销与吞吐特征改变,目标是缩小尾部延迟,而非让所有工作负载吞吐翻倍。
历史文章常教人“下载任意内核源码,再打同版本 RT 补丁”,这个说法到了今天要修正:自 Linux 6.12 起,受支持架构可在主线内核启用 PREEMPT_RT;某些目标版本、架构或发行版仍需匹配的 RT 补丁树,具体按供应商支持矩阵处理,不能跨版本乱打。

四、核心机制解析:Linux 两大经典实时调度算法
4.1 SCHED_FIFO 先来先服务调度机制
FIFO 首先比静态优先级:高优先级可运行线程压过低优先级,低优先级不因等待够久就自动超过它。相同优先级形成 FIFO 队列;正在运行的线程若一直不阻塞、不主动 sched_yield()、不被更高优先级抢占,也不受普通时间量子驱动而退到队尾;阻塞后再就绪通常排入对应优先级队尾。遇到更高优先级任务它会被抢占,等后者阻塞或结束后按内核队列规则恢复竞争。sched_setscheduler() 等主动修改参数的排队细节还有具体规则,不宜一概说“谁先创建就永远先运行”。
它很适合短小、事件驱动、处理完立即阻塞的控制线程,比如 GPIO 事件处理后写执行器,再等待下一次事件。危险的却是写成 while (1) { poll_device(); },只要它一直可运行,同核普通任务几乎拿不到机会,甚至让记录日志、SSH 救援变困难。内核默认有 RT 带宽保留机制,但它是防饿死的兜底,不是替应用证明任务能按时完成。想实际观察,可在测试环境跑 chrt -f 30 ./worker,同时通过 ps 检查这个线程的类和优先级。

4.2 SCHED_RR 时间片轮转实时调度机制
RR 与 FIFO 一样先比较静态优先级;只有同优先级竞争的可运行 RR 任务,才在量子耗尽时把当前任务移到该优先级队列尾部。同级 FIFO/RR 混用时都归实时优先级队列管理,具体入队时机和 yield、唤醒顺序会影响谁先执行;RR 不能确保“每个线程固定每 10 毫秒拿到 10 毫秒”,因为更高优先级任务随时可抢占,IRQ 与内核开销也会占时间。量子大小取决于内核设置,用接口查询最靠谱。
struct timespec q;
if (sched_rr_get_interval(0, &q) == 0)
printf("RR quantum: %ld.%09ld s\n", q.tv_sec, q.tv_nsec);
例如几个同优先级的音频辅助处理线程,每个工作片段时长近似、偶有短暂计算高峰,RR 可以避免其中一个线程长期霸着 CPU;但如果其中一个是必须先完成的数据采集任务,把它们全设同优先级,再指望 RR 自己理解业务前后依赖,就有点玄学了。上面代码需要包含 <sched.h>、<time.h> 与 <stdio.h>,放在完整程序里调用。

4.3 实时优先级层级规则与权限控制
固定优先级值 1~99 是 Linux 常见范围,用 sched_get_priority_min() 与 sched_get_priority_max() 获取当前系统实际边界更稳妥。无特权用户能否设置 FIFO/RR 及最高能设多高,受到 RLIMIT_RTPRIO、进程的能力集、服务管理器配置等约束;SCHED_DEADLINE 则要求 CAP_SYS_NICE。生产环境应按服务分配允许的上限,让采集 IRQ 线程、硬实时控制环和辅助日志线程形成经过测量的优先级关系,而不是把所有业务统一配置成 99。否则当两个“最重要”的线程碰头时,谁也没有余地给对方让路。
还要审视 RLIMIT_RTTIME、RT throttling 与 cgroup 限制:系统允许创建高优先级线程,不代表它可以永远不受预算制约地执行。某服务看似偶尔每秒卡一次,先读 /proc/sys/kernel/sched_rt_runtime_us 和 /proc/sys/kernel/sched_rt_period_us,对照系统日志与调度事件,再判定是不是消耗了实时运行额度。权限问题报 EPERM,Deadline 准入失败可能报 EBUSY,两个故障修法完全不同。

4.4 实时任务调度队列内核实现原理
从实现看,每 CPU 的 rq 维护相应调度类的可运行状态,RT 类的 rt_rq 按优先级组织多个任务链表,并用位图快速找到当前最高的非空优先级。任务唤醒时按策略与优先级入队,任务阻塞时出队;调度点触发时先看更高类是否有可运行任务,然后在 RT 类内选中最高优先级队列里的队首。RR 的时间量子耗尽会影响同级队列中的位置,FIFO 没有这种定时轮换。多核场景还要处理任务推拉、过载和亲和性约束,所以“一个全局 FIFO 大队列”只是入门比喻,并非真实数据结构。
调代码时从 task_struct 的 policy、prio、rt_priority,沿着 enqueue_task_rt()、pick_next_task_rt() 与时钟更新路径读,会比背抽象定义有效。别把内核的内部数值方向和用户态优先级方向混在一起:用户态 99 比 1 高,内部 prio 数值的大小关系有自己的映射。遇到奇怪的“看起来优先级反过来”,先把打印字段名称和单位查清楚。

五、进阶核心:PREEMPT 抢占机制与实时延迟优化
5.1 Linux 四种抢占模式:No PREEMPT / Voluntary / Full PREEMPT / RT PREEMPT
PREEMPT_NONE 主要在返回用户态或明确的调度点给其他线程机会,适合偏吞吐的配置;PREEMPT_VOLUNTARY 在内核中安排自愿抢占点;PREEMPT 让更多普通内核代码能够在合适的抢占点被切走;PREEMPT_RT 进一步把锁持有和中断处理这类难抢占区间改成调度器更能掌控的形式。四种模式说的是内核态抢占能力,不是 FIFO/RR 是否存在。可以在标准内核上给线程设实时策略,也可以在 RT 内核上运行一堆普通策略的进程;两组选择正交,却会共同影响实际响应。
抢占模型与动态抢占还受编译时依赖关系、启动参数及运行时接口影响。测试时别只记“这台机器是 RT”,至少记录 uname -a、内核 .config、启动参数、目标线程策略、IRQ 亲和性与 CPU 负载。否则两次测到的 P99 差了一倍,回头连使用的内核模式都说不清,排查会非常难受。

5.2 抢占阈值、临界区屏蔽对实时性的影响
“抢占阈值”在这里宜理解为系统允许抢占的实际边界,不是 Linux 默认有一枚可随意设置的统一“阈值旋钮”。某段代码调用 preempt_disable(),调度器即使发现有更高优先级线程要跑,也得等它恢复可抢占;local_irq_disable() 会进一步推迟本地中断;底层 raw_spinlock_t 的临界区仍需要特殊处理。把这些时间段堆起来,就得到最坏唤醒延迟的一部分。延迟尾巴大,第一反应应是寻找最长的关中断、关抢占和锁持有片段,而非盲调 FIFO 优先级。
工程上可以启用 ftrace 的 irqsoff、preemptoff 等 tracer 或使用 rtla timerlat 抓尖峰发生时的调用栈。注意 tracer 自身有开销,测量前后要保持配置一致。还得区分硬件占用 CPU 的 SMI 等无法被 Linux 线程优先级直接打断的情况;对这种延迟,改应用的 sched_param 完全不对症。
5.3 内核延迟来源:中断、软中断、锁竞争、任务阻塞
唤醒延迟可以粗略拆成事件到达中断处理、定时器和软中断处理、任务入队、等待调度、上下文切换几个阶段;真正业务时延还要加上锁竞争、缺页、内存分配、设备 I/O 与应用计算。一个线程已经进入可运行状态却迟迟没跑,优先看同 CPU 的更高优先级任务、IRQ 负担或不可抢占区;线程压根没进入可运行状态,就看它在等什么锁、事件或 I/O。top 上“R”状态是一张瞬时照片,不能替代时间轴。
十多年前我在一台工控机上做过一次 1 毫秒周期控制环的排查:实验室里隔三差五出现 3 毫秒尖峰,我们连着改了三轮线程优先级,数据一点也不动。后来把 IRQ 计数、ftrace 时间线和磁盘日志时间戳对上,才发现一轮日志刷盘牵动了同核的设备中断和锁竞争。凌晨两点多盯着那几列时间戳,我真有点恼火——“实时”两个字被我们当成了提优先级的代名词。最后把非关键日志移出控制环、处理 IRQ 布局,尖峰才收敛。这里是经历式的工程场景叙述,不应当把这个数值当成任何设备的通用指标。

5.4 RT 补丁如何解决内核非确定性延迟问题
PREEMPT_RT 的核心思路是让原本“谁也打断不了”的工作进入线程上下文:可线程化的 IRQ 有自己的优先级,可睡眠 spinlock_t 等待时允许让出 CPU,rtmutex 的优先级继承帮助被高优先级线程阻塞的锁持有者先完成工作,软中断和部分定时器也以可抢占方式执行。这样高优先级线程就不必替每一段普通内核工作无限排队。但真正的硬 IRQ 顶半部、raw_spinlock_t、硬件管理中断与驱动里的不良设计仍可能制造尾部;RT 降低不可控区间,不是把世界变成零延迟。
六、新版内核演进:SCHED_DEADLINE 截止时间调度
6.1 传统 FIFO/RR 的痛点与调度瓶颈
固定优先级的好处是直观:采集线程比日志线程重要,就让前者优先级更高。但几个不同周期的任务挤在一起时,人工制定优先级、分析最坏干扰并给每个线程控制运行预算会变复杂;一个高优先级死循环还可能让低优先级业务一直掉队。RR 只能在同优先级层内轮流跑,不会替开发者认识“每 10 毫秒必须完成一次”的期限。Deadline 正是把任务的周期、预算和期限定量交给内核的一条路径,它更适合能量化执行需求的周期或准周期作业。
别把它神化:Deadline 解决 CPU 上的调度与带宽管理问题,但如果作业最长执行时间估错、持锁太久、依赖的设备回包不可控,EDF 算法再聪明也得面对迟到。先测出合理的 WCET 估计与抖动,再讨论参数,顺序不能反。
6.2 Deadline 调度核心原理:周期、运行时长、截止时间
三个数字写作 runtime / deadline / period:一个作业每个周期大致拥有 runtime 的 CPU 执行预算,应在相对 deadline 内完成,下一周期的间隔为 period。例如 2 ms / 8 ms / 10 ms 表示期望每 10 毫秒释放一次工作、最多请求 2 毫秒 CPU 预算、在释放后的 8 毫秒内完成,带宽占比 2/10=20%。必须满足 0 < runtime <= deadline <= period,并承认“2 毫秒预算”不包含任意外部 I/O 等待时间。Linux 使用 EDF 挑选较早绝对截止时间的候选任务,同时结合 CBS 控制 CPU 预算;预算耗尽的作业通常要等补给,因而某一次执行时间意外变长会出现节流和期限错过。
下面命令用于支持相关选项的 chrt 版本,单位是纳秒,执行前先用 chrt --help 确认本机参数:
sudo chrt -d -T 2000000 -D 8000000 -P 10000000 -- ./periodic_worker
其中 -T 对应运行预算、-D 对应截止时间、-P 对应周期。

命令只负责设置策略,不会替 periodic_worker 写好“每 10 毫秒按绝对时间醒来”的应用循环;若每轮用 nanosleep(10 ms) 相对睡眠,处理时间会累积成漂移。

6.3 带宽调度机制与任务准入规则
Deadline 的准入测试会依据任务 runtime/period 及可用 CPU 带宽判断新预约是否能加入;过量申请可能由 sched_setattr() 返回 EBUSY。假设同一约束范围内,A 申请 2/10=0.2、B 申请 3/10=0.3,合计至少需要 50% 的 CPU 预算;这还得与系统保留、RT/公平类预算、CPU 亲和及内核具体实现一起核算,不能只把所有 CPU 总容量一加就算完成准入。多核、root domain、任务迁移的边界尤其要实际验证,任务准入通过也不是整个端到端链路按期交付的保证。
有时调度日志里看到“任务超时”,根因是开发时把平均执行 1.8 毫秒直接设成 2 毫秒预算,负载一高就被 CBS 限住。参数表最容易骗人的地方恰恰是那个漂亮的均值:预算要覆盖经测量的峰值并留安全裕度,期限要反映业务真实需要,超时计数应成为监控指标。

6.4 Deadline 与 FIFO/RR 性能、场景对比
FIFO 适合优先级稳定、任务短且会主动阻塞的事件处理;RR 适合同级并行工作者需要轮换 CPU;Deadline 适合能够说明周期、预算和期限的任务集合,尤其在多个周期共存时便于做容量分析。这里的“性能”至少有吞吐、唤醒延迟、超期率和管理成本四个维度,没有哪一种策略在四项上通吃。Deadline 的准入与预算能遏制某线程无限吃 CPU,却多了参数测量和运行中处理预算不足的复杂度;FIFO 更简单,但最容易被无边界循环坑惨。
实际选型先写业务时序图:输入什么时候到、计算最长多久、可延迟多长时间、是否依赖其他线程。线程若多数时间等事件、唤醒后很快完成,FIFO 加完善的系统配置就可能足够;如果是许多周期任务争一组 CPU,Deadline 可能更有点东西。两者都需要测量锁、IRQ 和 I/O;同一机器上混用时要注意 Deadline 类的选择顺序在 RT 类之前,别用“FIFO 99 最大”作全局优先级假设。

七、实时调度关键配套内核机制
7.1 实时任务的中断绑定、CPU 亲和性配置原理
绑定实时线程到某几个 CPU,目的是减少迁移与争用、使测量条件可复现;IRQ 也要跟着数据路径设计,不能把采集 IRQ 丢在过载核,控制线程却绑到另一核上再抱怨跨核唤醒慢。taskset -cp 2 <TID> 限制线程的允许 CPU;具体 IRQ 号可在 /proc/interrupts 查到,写 /proc/irq/<IRQ>/smp_affinity_list 能调整部分 IRQ 的亲和性。是否生效取决于设备、驱动、IRQ 类型和 irqbalance,中断并非都能按你写入的掩码自由迁移。
从整体看,保留 housekeeping CPU 运行系统管理线程与非关键 IRQ,让控制 CPU 尽量少受打扰,是常见架构;isolcpus、nohz_full、rcu_nocbs 则要结合内核版本与 cpuset 使用,并通过 ftrace 证实收益。单纯把关键线程锁死在某核,若它的数据生产者也被饿住,反而会加重迟到。要一起记录线程、IRQ、NUMA 内存位置和设备拓扑,先画数据流,再画 CPU 表。

7.2 内核锁机制:实时化 mutex、spinlock 改造逻辑
普通内核里 spinlock_t 的典型语义是短时忙等并关抢占;PREEMPT_RT 上它多数转换成基于 rtmutex 的可睡眠锁,锁等待者可以阻塞,持锁者通过优先级继承临时得到更高的调度优先级。raw_spinlock_t 仍用于真正不能睡眠的底层小临界区,不能为了省事把驱动的所有锁都改成 raw,那等于把长尾又请回来。代码审查时要确认每把锁运行在哪个上下文、保护哪些资源,以及锁内有没有设备访问、分配内存或等待事件。
这里有个很阴的兼容性坑:以前驱动可能默认认为持有 spinlock_t 就“顺便禁止了抢占”或“本地 CPU 变量自动安全”,RT 上这个假设不成立。文档推荐按需求使用 local_lock_t 等明确的同步机制,必要时保留真正的 raw 区,但长度必须可控。别一边说追求确定性,一边在锁里 printk 打满串口。

7.3 软中断线程化对实时延迟的优化作用
网卡收包、定时器回调等软中断工作若一口气挤在同一 CPU 上,会拖住待唤醒的实时线程。PREEMPT_RT 将更多处理移到线程上下文,使其能被更重要的任务抢占;但软中断工作不会因此消失,ksoftirqd 等线程过忙仍会影响数据交付和系统吞吐。有些设备处理链并不完全线程化,仍有需要及时完成的硬 IRQ 部分,因此“软中断线程化 = 任何时候都只有普通线程”也不准确。
观测时一起看 /proc/softirqs、/proc/interrupts、对应 IRQ 线程的策略与亲和性,以及实时线程的 wakeup trace。如果提高控制线程优先级后,网络包反倒进不来,很可能是控制线程饿住了数据生产者。优化得围绕因果链,不是看哪个线程名字带 IRQ 就把它提到 90。

7.4 内存管理对实时任务的支撑:避免缺页中断抖动
首次访问代码页、匿名页按需分配、页表建立、文件映射回填、交换等都会把毫秒级不确定性带进关键路径。用户态常用 mlockall(MCL_CURRENT | MCL_FUTURE) 锁住当前和以后映射的页,再预触碰实时循环要使用的栈与缓冲区,并在循环前完成线程创建、文件打开、动态链接与内存分配。它有 RLIMIT_MEMLOCK 和权限边界,而且锁页并不能保证所有后续内存行为都零开销:未来映射失败、超大页面、NUMA 远端访问以及应用自己临时 malloc 都需要测。
一个简单原则是初始化阶段做脏活,周期阶段只做有界工作。比如提前建立固定大小对象池、预热代码路径、在真实压力下查看进程 minflt/majflt 计数;如果每次循环都格式化大段日志、扩容容器,锁住内存也救不了它。外部数据库、磁盘和网络的尾部更不受 mlockall() 控制,必要时把非确定性路径移出控制环。
八、实操落地:实时调度配置、调试与性能观测
8.1 内核实时编译与 PREEMPT_RT 补丁部署
先决定目标发行版和硬件是否已有供应商维护的 RT 内核;若已有,优先保持驱动、工具链与安全更新来源一致。自行编译时下载与目标架构匹配的受支持内核源码,检查 CONFIG_PREEMPT_RT 的 Kconfig 依赖,设置所需高精度定时器、调试选项和驱动,编译模块及内核并安装启动项;旧版本如需补丁,只选与源码精确匹配的 RT 补丁版本。下面是以已有、适配当前架构的源码树为起点的示意流程,不代表所有发行版的引导安装命令相同:
make olddefconfig
make menuconfig # 选择可用的 Fully Preemptible Kernel (Real-Time)
grep -E '^CONFIG_PREEMPT_RT=' .config
make -j"$(nproc)"
sudo make modules_install
sudo make install
重启后用 uname -r 和当前内核配置复核,再在目标硬件上跑原负载与压力测试;如果发行版采用 Secure Boot、专用 initramfs 或独立 bootloader 工具链,还需按其文档完成签名与启动项生成。千万保留可回退的旧内核。
8.2 实时任务优先级、调度策略代码配置示例
下面 C 程序展示一个可独立编译的周期线程:先锁内存并预触碰一小块缓冲区,随后把当前线程设成 FIFO 30,用 CLOCK_MONOTONIC 的绝对时间睡眠避免每轮处理时间叠加漂移。它打印出的 late 只是“从计划唤醒到应用检测时间”的演示指标,不包括设备动作完成时间;mlockall 或调度设置失败就退出,这能防止以普通策略跑着却误以为实验有效。
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
static void add_ms(struct timespec *t, long ms) {
t->tv_nsec += ms * 1000000L;
t->tv_sec += t->tv_nsec / 1000000000L;
t->tv_nsec %= 1000000000L;
}
int main(void) {
volatile unsigned char pages[64 * 1024];
for (size_t i = 0; i < sizeof pages; i += 4096) pages[i] = 0;
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) { perror("mlockall"); return 1; }
struct sched_param p = { .sched_priority = 30 };
if (sched_setscheduler(0, SCHED_FIFO, &p) != 0) {
perror("sched_setscheduler"); return 1;
}
struct timespec due;
if (clock_gettime(CLOCK_MONOTONIC, &due) != 0) return 1;
for (int i = 0; i < 1000; ++i) {
add_ms(&due, 10);
int rc;
do { rc = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &due, NULL); }
while (rc == EINTR);
if (rc != 0) { fprintf(stderr, "clock_nanosleep: %s\n", strerror(rc)); return 1; }
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) return 1;
long long late = (now.tv_sec - due.tv_sec) * 1000000000LL
+ (now.tv_nsec - due.tv_nsec);
if (i % 100 == 0) printf("sample=%d late=%lld ns\n", i, late);
/* 在这里执行有界业务逻辑,不要阻塞等待日志或磁盘。 */
}
return 0;
}
用 cc -O2 -Wall -Wextra -pthread rt_demo.c -o rt_demo 编译,再以具备相应权限的方式执行;某些环境须调整 RLIMIT_MEMLOCK,-pthread 对该样例不是必需链接项但为后续多线程改造预留。这个演示中的 printf 每百轮执行一次,做严肃测量时也应改成预分配的内存记录并在循环结束后输出,否则采样自身会污染尾部;栈预触碰范围、锁页成功与未来新增栈页的处理亦须针对生产线程栈另行设计。
8.3 常用观测工具:top、htop、chrt、taskset 实操
第一轮定位要回答四个问题:目标线程策略是什么、实际落在哪个 CPU、有没有更高优先级竞争者、系统是否触发 RT 节流。top -H -p <PID> 展开线程观察,htop 可设置显示线程和调度字段;chrt -p <TID> 读或改策略;taskset -cp <TID> 查看允许 CPU;ps -L -p <PID> -o tid,cls,rtprio,psr,stat,comm 给出可保存的快照。线程 ID 常能从 /proc/<PID>/task/ 查到,命令中的 TID 不要误写成只含主线程的进程号。
chrt -p 1234
sudo chrt -f -p 30 1234
taskset -cp 1234
sudo taskset -cp 2 1234
ps -L -p 1234 -o tid,cls,rtprio,psr,stat,comm
cat /proc/sys/kernel/sched_rt_period_us /proc/sys/kernel/sched_rt_runtime_us
修改正在运行的生产线程前,先确认它的 TID、依赖线程及救援通道。taskset 改的是允许 CPU 集合,没承诺“这个 CPU 从此只有我一个线程”;chrt 改了策略也没替你分配独占核。需要微秒级解释时,再上 ftrace、perf sched、rtla timerlat,交叉看调度事件和 IRQ 时间线。
8.4 延迟测试工具 cyclictest 原理与数据解读
cyclictest 通过周期定时唤醒线程,比较计划时间与实际运行时间,输出最小、平均、最大等统计量;它在测“这类定时线程何时跑起来”,不能直接代替应用端到端交付测试。先查 cyclictest --help,用目标发行版的 rt-tests 工具选择线程数、优先级、间隔与持续时间,在隔离核与非隔离核、轻负载与最坏压力负载下分别记录原始数据。版本间参数短选项可能不同,别不核对就粘贴一段老博客命令;运行数小时仍未见尖峰,也不等于现场跑一年不会出现。
建议至少记录内核版本、BIOS 电源选项、CPU 频率策略、测试时长、并发压力、最大值及超阈次数,再比较 P99.9 和尾部形态;均值低但 Max 偶尔高几十倍,就沿着 timerlat、irqsoff、preemptoff 追对应时刻。rtla timerlat 能把 IRQ 定时器阶段与线程运行阶段分开观察,从而帮助识别是定时器到点晚,还是线程醒了却没抢到 CPU。
8.5 实时调度常见异常:优先级反转、调度抖动排查
排查顺序不要反过来:先定位“作业是什么时候释放、什么时候 ready、什么时候开始跑、什么时候完成”;再排高优先级占用、共享锁、IRQ/软中断、缺页、RT 节流、Deadline 预算、CPU 迁移和硬件电源状态。若持续慢,查预算和资源饱和;若只是罕见尖峰,抓当时栈与 IRQ 时间线。对照 /proc/<PID>/sched、/proc/interrupts 和 tracepoint,能避免把外部设备未送数据误诊为调度器故障。
另一个真实场景是我们给日志线程和控制线程共用了一个普通 mutex:控制线程优先级 70,日志线程优先级 10,结果控制线程在一把锁上等了许久。有人当时说“把 70 改 95”,我听完脑袋嗡的一下——锁还在人家手里,改 99 也无法让锁自动释放。后来把日志缓冲改为异步并缩小临界区,问题比任何“神奇内核参数”都好解。这个例子引出下一章:优先级反转。
九、经典问题剖析:优先级反转与解决方案
9.1 优先级反转问题产生的底层逻辑
经典场景里,低优先级线程 L 先拿到共享锁,高优先级线程 H 随后醒来并请求同一把锁,于是 H 阻塞;如果中优先级线程 M 不断运行,M 会抢占 L,使 L 没机会释放锁,结果 H 的有效进度反而被 M 间接拖住。用优先级数字看,H 明明大于 M 大于 L,时间线上却成了 M 在跑、H 在等、L 没法完成。若再叠加 IRQ、日志和 I/O,阻塞时间甚至由一个看似无关的中优先级工作决定,这就是反转的危险。
它和普通“锁竞争”不完全一样。普通竞争可能是两个同级线程短暂排队,反转则破坏了高优先级任务的服务顺序,且延迟上界往往取决于中间线程数量和执行长度。代码里最容易埋雷的是跨优先级共享的普通 pthread_mutex、内核驱动中的长临界区,以及把复杂业务放进一个“为了方便”而存在的全局锁。先画锁依赖图,再谈策略;不画图只盯线程名,十有八九会漏掉第二层锁。
9.2 内核解决方案:优先级继承、优先级天花板
优先级继承(PI)在高优先级线程阻塞于锁时,临时把它的调度参数传给持锁者。L 得到 H 的优先级后可以抢过 M,尽快跑到解锁点,H 再继续执行;如果 L 同时持有多把锁,继承链还可能沿着锁依赖传播,所以实现必须处理嵌套与解除继承。PREEMPT_RT 的 rtmutex 采用这类优先级继承思路,目的正是把阻塞时间从“可能被任意中间任务放大”收敛到持锁者可完成临界区的范围。
优先级天花板(PCP)给每把锁设置可能访问它的最高优先级,线程拿锁时提升到天花板,并在不满足系统条件时阻止新的锁获取。它能减少死锁和多重反转风险,但需要提前知道资源访问关系,配置和验证成本更高。用户态 POSIX 接口可以请求 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT,不过具体实现和权限、库版本有关,必须检查返回值,不能看到编译通过就默认协议已生效:
pthread_mutexattr_t a;
pthread_mutex_t m;
int e = pthread_mutexattr_init(&a);
if (e == 0) e = pthread_mutexattr_setprotocol(&a, PTHREAD_PRIO_INHERIT);
if (e == 0) e = pthread_mutex_init(&m, &a);
if (e != 0) fprintf(stderr, "PI mutex setup failed: %s\n", strerror(e));
pthread_mutexattr_destroy(&a);
部分 Linux 环境对 PTHREAD_PRIO_PROTECT 的支持并不完整,pthread_mutexattr_setprotocol() 或初始化可能返回 ENOTSUP / EOPNOTSUPP;生产代码应有降级或直接拒绝启动的策略。若不能使用 PI,优先考虑缩短共享区、单写者队列、无锁环形缓冲或把低优先级持锁者改造成专门的高优先级服务线程,而不是默默吞掉超时。
9.3 不同场景下的方案选型与优劣对比
单机实时控制里,锁保护的数据很小、访问关系稳定,PI 往往是成本和效果比较平衡的方案;资源图固定且对上界要求极严时,PCP 更适合做静态分析。音视频管线通常更喜欢无锁队列、双缓冲和预分配,避免让音频回调去等编码线程;服务器里的短临界区则可以接受普通 mutex,把精力放在吞吐和尾延迟监控上。优先级协议只解决“等待锁的线程怎样让持锁者尽快结束”,不能解决持锁者自己发起不可控磁盘 I/O 或在锁里循环百万次。
选型时把三个问题写在设计评审里:共享资源的最大持有时间是多少?是否允许阻塞与上下文切换?锁依赖是否会跨进程、跨 CPU 或跨设备?回答不清时,先重构资源所有权,再决定 PI/PCP。很多项目最后的最佳优化并非换一种 mutex,而是让实时线程只发送固定大小消息,把复杂状态更新放到非实时线程处理。这种架构调整通常比调度参数更有效,也更容易通过测试。
十、场景落地:各行业实时调度最佳实践
10.1 工业控制、机器人:硬实时调度方案选型
工业控制要先按控制周期划分任务:高频电流环、较低频位置环、状态估计、通信和日志不应挤在同一优先级。周期短、执行时间稳定的环路可以放在专用 CPU 上,用 FIFO 或 Deadline;输入输出应采用 DMA、预分配缓冲和固定长度消息,避免周期内分配和文件操作。机器人系统还要把感知、规划和伺服的截止时间分开,规划迟到可能降低路径质量,伺服迟到却可能直接触发保护,这两者不能用同一“实时线程”概念覆盖。
如果业务宣称硬实时,必须做最坏执行时间、总线传输和故障降级分析,必要时将最关键的安全环放到 MCU、FPGA 或专用 RTOS,Linux 负责上层协调。PREEMPT_RT 很适合把 Linux 侧的抖动压低,但它不会替外部总线提供确定的仲裁,也不会自动验证电机驱动器的响应。现场验收要用真实传感器、最大负载、温度变化和异常通信重放,而不是只在开发板上跑一个空循环。
10.2 车载系统、自动驾驶:高可靠实时调度架构
车载系统往往同时承载安全关键域和信息娱乐域,优先级设置之外更看重隔离、监控与降级。可以按 CPU 集群或容器/cgroup 划分资源,让控制、感知、记录和诊断拥有清晰的带宽预算;对高频控制任务固定亲和性,给非关键服务留 housekeeping 核,监控 watchdog、Deadline 超期、RT 线程异常退出与温度降频。安全规范要求可追溯证据,因而每次内核、固件和调度参数变更都应重新跑延迟和故障注入测试。
自动驾驶链路是端到端流水线:摄像头 DMA、ISP、感知推理、融合、规划、执行器之间任何一个队列阻塞都会让“最后的控制线程”失去意义。实践中应为消息附加采样时间和截止时间,消费端检查数据新鲜度,过期就进入明确的降级路径;不要让一个高优先级线程拿着旧数据忙算,最后输出一个时间上已经无效的结果。
10.3 音视频、流媒体:低延迟实时调度优化
音频 I/O 回调通常是软实时:每个 buffer 到点前填充即可,偶发丢帧比永久饿死系统更可接受。可以让音频线程使用 RR/FIFO 的适度优先级,绑定到合适 CPU,预分配环形缓冲,禁止回调里分配内存、等待锁、访问磁盘或调用不可控日志。视频编码、网络发送和 UI 刷新应使用不同队列与预算,利用背压和丢帧策略隔离突发负载。把所有线程提成 RT,往往只会让音频正常、后台网络和桌面彻底卡住。
流媒体的延迟指标还包括采集到播放的端到端缓冲;仅看调度唤醒延迟可能漏掉编码器内部排队和网络重传。测量时在采集、编码完成、发送、接收、渲染各处打单调时钟时间戳,区分调度抖动与协议缓冲。对无法满足期限的帧,应优雅降级并记录原因,别让一个异常帧拖住整个队列。
10.4 服务器通用场景:软实时调度调优思路
服务器的软实时需求常见于交易行情、在线推理、数据库日志和高频网络服务。先用普通公平调度和 CPU 亲和性把尾延迟压到目标,再评估是否需要 FIFO/RR;很多服务只需提高 timer 分辨率、减少锁争用、使用 io_uring、预热内存和隔离 noisy neighbor,就能获得更稳定的 P99。RT 策略应只给少数短任务,并配合 RLIMIT_RTTIME、RT 带宽和 watchdog,保留管理员抢救通道。
云环境还会引入 vCPU 调度、虚拟机退出、宿主机超分与共享中断,客体内核里设置 FIFO 99 也无法控制宿主机什么时候把 vCPU 暂停。对这类场景,先确认是否有独占物理核、实时实例类型或设备直通,再解释数据。别把裸机压测的数字直接搬到云上,发布说明里要写清测试边界。
十一、常见误区与调优避坑指南
11.1 误区1:开启 RT 内核就等于绝对实时
PREEMPT_RT 把大量内核路径变得可抢占、可继承优先级,能显著改善延迟尾部,但仍有硬中断、原始自旋锁、固件管理事件、设备 DMA、SMI、缓存和外部总线等边界。即便所有内核路径都能很快让路,应用的算法复杂度、锁依赖和网络服务器也可能超期。正确说法是“在明确硬件、软件和压力条件下,获得更低且更可测的延迟”,而不是开关一拨就获得绝对上界。
11.2 误区2:实时优先级越高性能越好
优先级只改变竞争顺序,不会减少线程自身的计算量;过高的 FIFO 线程可能饿住数据生产者、网络栈、日志和救援 shell,系统看起来“控制很快”,实际已经失去可维护性。优先级应按截止时间、最长运行片段、锁依赖与故障恢复设计,留出 IRQ 线程和管理员通道的空间。把 30 改成 80 前,先问“谁被我延后了、它是否仍能完成输入和清理”。
11.3 常见性能抖动原因与系统化调优思路
常见抖动源包括 CPU 频率和深度睡眠状态切换、NUMA 远端访存、周期性内核线程、IRQ/软中断突发、页错误、日志与动态内存分配、锁竞争、热管理和虚拟化暂停。调优要遵循测量闭环:定义期限和指标,建立可复现负载,记录内核与硬件环境,先定位尖峰时间线,再一次只改一个变量,最后在长时间和故障场景下回归。不要一上来同时改 12 个 sysctl,然后看到最大值下降就宣布成功;那样后面连哪个改动有效都不知道。
可以把结果分三层保存:调度层(唤醒、运行、迁移)、内核层(IRQ、锁、缺页、软中断)、业务层(输入时间、输出时间、丢帧或控制误差)。三层时间戳统一使用 CLOCK_MONOTONIC_RAW 或经验证的单调时钟,避免墙上时间被校时跳变。观察直方图比只截一张 top 更有价值,故障时保留 trace buffer,便于复盘。
11.4 实时系统稳定性保障核心准则
第一,所有实时任务都要有明确的最长运行和阻塞预算;第二,初始化与周期执行分离,周期路径不做不可控 I/O、分配和日志;第三,优先级、亲和性、IRQ、cgroup 和内存锁定一起评审;第四,为 RT 任务设置监控、超时、降级和救援入口;第五,内核升级、驱动改动、BIOS 电源策略变化后重新测试。把这些准则写成检查表,往往比在代码里再塞一个 sched_yield() 更靠谱。
稳定性还依赖团队习惯。每次压测都保存原始直方图和配置,异常时不先删掉“难看的最大值”,而是标注发生时的负载、温度和 IRQ。真实系统不可能永远漂亮,愿意保留那些丑数据,才有机会找到真正的边界。
十二、总结与技术展望
12.1 全文核心知识点复盘梳理
Linux 调度器先按调度类选任务,再在类内按策略选队列成员。FIFO/RR 使用静态实时优先级,前者依赖主动阻塞或让出,后者在同级任务间轮转;Deadline 以运行预算、相对截止时间和周期为核心,并通过准入与带宽控制管理 CPU。普通 SCHED_OTHER 正从经典 CFS 向 EEVDF 演进,但公平调度仍不等于硬实时。PREEMPT_RT 通过线程化 IRQ、可睡眠锁、优先级继承等手段缩短不可抢占区间,却无法消除硬件、固件和错误驱动带来的所有延迟。
要落地,至少完成五件事:选对内核与配置;为线程、IRQ 和 CPU 设计资源拓扑;避免缺页、分配、阻塞 I/O 与长锁;用 cyclictest、rtla timerlat、ftrace 和业务时间戳测量;为超期、反转和失控任务准备限流、watchdog 与降级。任何一项缺失,调度策略都可能只是漂亮的配置截图。
12.2 Linux 实时调度技术迭代趋势
主线正在持续吸收 PREEMPT_RT 的工作,调度器在 EEVDF、公平类延迟控制、Deadline 带宽、sched_ext 和利用率钳位等方向演进;硬件异构、大小核、频率调节和能效约束也会让“固定一颗 CPU”变成更复杂的容量问题。未来的实时系统大概率不是单一策略统治全机,而是 Deadline/FIFO、公平类、cgroup 带宽、硬件加速和安全隔离组合工作。内核工具也会从单个最大延迟数字走向可追溯的事件链与噪声分类。
这意味着开发者需要同时懂调度理论、内核代码、驱动上下文、硬件拓扑和业务时序。只背 chrt -f 99 已经不够用了;能解释一次超期是在哪个队列、哪把锁、哪条 IRQ 链上发生,才算真正掌握实时调度。
12.3 硬核实时系统学习与进阶方向
入门可从 sched(7)、sched_setattr(2)、kernel/sched/rt.c 和 kernel/sched/deadline.c 对照阅读,配合最小 FIFO/RR/Deadline 程序观察 /proc/<PID>/sched。下一步学习 rtmutex、优先级继承、锁依赖验证、ftrace tracepoint、rtla 和 CPU 隔离;再往后做驱动线程化、DMA 时序、NUMA、虚拟化和故障注入。每学一个机制,都用“释放—唤醒—运行—阻塞—完成”的时间线验证,不要只抄概念图。
建议建立自己的实验仓库:同一份周期程序在 PREEMPT_NONE、普通 PREEMPT 和 PREEMPT_RT 下跑,逐步加入网络、磁盘、内存压力和 IRQ 风暴,记录每次最大值与直方图。这样得到的经验可能没有博客里的数字那么整齐,却是真正属于你的工程判断。慢一点没关系,实时这门课最怕急着下结论。想深入 操作系统 调度器的实现细节,可以从源码和实验两条线并行推进。
附:常用实时调度命令 + 内核参数对照表
| 目标 |
命令或参数 |
说明 |
| 查看线程策略 |
chrt -p <TID> |
查看 FIFO/RR/Deadline 等策略与优先级 |
| 设置 FIFO |
sudo chrt -f -p 30 <TID> |
需要权限;先确认线程不会无限运行 |
| 设置 RR |
sudo chrt -r -p 30 <TID> |
同级 RT 线程按时间量子轮转 |
| 查看 RR 量子 |
sched_rr_get_interval(0, &ts) |
以程序接口读取当前线程量子 |
| 设置亲和性 |
taskset -cp 2 <TID> |
只改变允许 CPU 集合 |
| 查看线程落点 |
ps -L -p <PID> -o tid,cls,rtprio,psr,stat,comm |
psr 是最近运行 CPU 的快照 |
| 查看 RT 总预算 |
/proc/sys/kernel/sched_rt_period_us |
默认常见为 1000000 微秒,按系统确认 |
| 查看 RT 运行额度 |
/proc/sys/kernel/sched_rt_runtime_us |
默认常见为 950000;-1 表示不保留公平类额度 |
| 检查配置 |
grep CONFIG_PREEMPT /boot/config-$(uname -r) |
需对应当前启动内核 |
| 锁住用户页 |
mlockall(MCL_CURRENT|MCL_FUTURE) |
仍受 RLIMIT_MEMLOCK 和内存访问模式约束 |
| 测唤醒延迟 |
cyclictest --help |
先按本机 rt-tests 版本确认参数 |
| 分析 timer 延迟 |
rtla timerlat top |
将定时器与线程阶段放到时间线上 |
| 看 IRQ 分布 |
cat /proc/interrupts |
配合 IRQ 线程和亲和性分析 |
| 看软中断 |
cat /proc/softirqs |
观察网络、定时器等软中断负载 |
| 保护救援通道 |
RLIMIT_RTTIME、RT 带宽、watchdog |
防止失控 RT 任务锁死系统 |
最后再提醒一次:表里的命令是工具,不是方案。先写清楚任务的周期、预算、截止时间和失败后果,再选策略、内核和硬件;测量结果不满足期限,就回到时间线找原因。实时系统没有“调一个参数就结束”的结局,只有一轮轮把不确定性变小的工程工作。