在上一篇文章中,我们从Linux内核内部分析了PREEMPT_RT到底改变了什么。通过内核抢占、中断线程化、实时锁以及优先级继承等机制,Linux可以明显降低高优先级任务在内核执行路径中的等待时间,使Linux具备更强的实时处理能力。
但当我们真正把PREEMPT_RT用于工业控制、机器人、实时仿真或者智能制造系统时,很快就会发现一个新的问题:
即使实时任务已经拥有很高的调度优先级,它真的就能够获得一个“干净”的CPU吗?
答案并不一定。
假设一台设备拥有8个CPU核心,其中CPU7专门运行一个1ms周期的实时控制任务。开发人员通过taskset或者CPU affinity把这个任务绑定到了CPU7,看起来似乎已经完成了CPU资源分配:
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 ↑ 实时控制任务但是,CPU7并不一定真的只运行这个实时任务。
它仍然可能受到硬件中断、内核线程、定时器、RCU活动、softirq以及其他系统任务的影响。也就是说:
让一个任务“只能在某个CPU上运行”,并不等于“这个CPU只服务于这个任务”。
这两个概念看似非常接近,实际上解决的是完全不同的问题。
CPU affinity解决的是:
一个任务可以在哪里运行。
CPU isolation解决的则是:
一个CPU上允许哪些类型的任务运行。
而如果进一步从整个操作系统架构出发,对实时任务、普通任务、中断、内核服务和系统资源进行更严格的边界划分,那么我们讨论的就已经不只是简单的CPU affinity,而是:
核心隔离。
对于实时Linux来说,这一步非常重要。因为PREEMPT_RT主要解决的是“内核执行路径是否能够及时被抢占”,而核心隔离解决的是“实时任务运行的CPU环境是否足够干净”。
换句话说:
PREEMPT_RT让Linux更容易实时;核心隔离则进一步减少实时任务受到的系统干扰。
这也是为什么在很多实时系统设计中,真正值得关注的不只是“调度策略是什么”,而是:
关键任务到底拥有怎样的CPU执行环境?
一、CPU Affinity到底是什么?为什么“绑核”经常被误认为“CPU隔离”
理解CPU隔离之前,首先需要把CPU affinity,也就是CPU亲和性讲清楚。
现代处理器通常拥有多个CPU核心:
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7Linux调度器默认会根据系统负载,在这些CPU之间安排任务。
对于普通应用而言,这种方式非常合理。
例如系统中存在:
Task A Task B Task C Task D调度器可以让它们分布在不同CPU上运行:
CPU0 → Task A CPU1 → Task B CPU2 → Task C CPU3 → Task D如果系统负载发生变化,任务还可以在CPU之间迁移。
这种机制有利于提高CPU利用率。
但是实时系统经常希望:
某个任务不要随意迁移。
例如一个机器人关节控制任务:
MotorControl 周期:1ms 优先级:实时开发人员可能希望它始终运行在CPU7:
CPU7 ↓ MotorControl这时候就可以使用CPU affinity。
CPU affinity的核心思想就是:
告诉调度器,一个任务允许在哪些CPU上运行。
例如:
Task A 允许CPU0、CPU1 Task B 允许CPU2 Task C 允许CPU6、CPU7从任务角度来看,可以理解为:
Task A → CPU0/1 Task B → CPU2 Task C → CPU6/7这能够减少任务迁移,也可以让开发人员更容易规划CPU资源。
对于实时任务而言,这一点非常有价值。
因为任务在不同CPU之间迁移可能带来额外的调度、缓存以及系统状态变化。
例如:
CPU6 ↓ 实时任务 ↓ 迁移 ↓ CPU7 ↓ 继续运行虽然Linux可以非常高效地完成任务迁移,但对于追求确定性的实时应用而言,减少不必要的迁移通常更加有利。
所以,CPU affinity是实时Linux优化中非常基础的一项技术。
但问题是:
CPU affinity只限制“这个任务可以去哪”,并没有规定“其他任务不能来”。
这是理解CPU绑核与核心隔离区别的关键。
假设:
CPU7 ↓ 实时任务我们把实时任务绑定到CPU7:
RealTimeTask CPU affinity = CPU7这只能说明:
RealTimeTask只能在CPU7运行。
它并不代表:
普通任务 不能进入CPU7也不代表:
IRQ 不能进入CPU7更不代表:
内核线程 不能进入CPU7所以实际情况可能是:
CPU7 ├── RealTimeTask ├── IRQ ├── kworker ├── timer ├── RCU └── 其他允许运行在CPU7的任务实时任务虽然“绑核”了,但这个CPU并没有真正被隔离。
这就好像一个公司给某个部门安排了一个专属会议室,并规定这个部门的会议只能在这里召开。
但是其他部门仍然可以进入这个会议室。
那么这个会议室实际上并不是真正意义上的“专属资源”。
CPU隔离也是同样的逻辑。
所以:
CPU Affinity解决的是任务位置问题,而CPU Isolation解决的是CPU资源边界问题。
这也是实时Linux进入更深层优化后必须理解的第一个区别。
二、CPU Isolation到底隔离了什么?真正的目标不是“绑住任务”,而是减少CPU上的非实时活动
如果CPU affinity是:
“我的任务去CPU7。”
那么CPU isolation更接近:
“CPU7尽量不要处理与实时任务无关的事情。”
这个思想非常重要。
假设系统拥有8个CPU:
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7普通Linux可以把各种任务分布到所有CPU:
CPU0 → 普通任务 CPU1 → 网络 CPU2 → 文件系统 CPU3 → 后台服务 CPU4 → 应用 CPU5 → 内核线程 CPU6 → 普通业务 CPU7 → 实时任务看起来CPU7已经主要运行实时任务。
但如果没有进一步隔离,CPU7仍然可能承担:
定时器 中断 RCU 内核线程 softirq 其他调度活动所以真正的实时CPU设计往往会变成:
CPU0-CPU5 普通任务 网络 文件系统 后台服务 系统管理 CPU6 Housekeeping CPU7 实时任务这里出现一个很重要的概念:
Housekeeping CPU。
简单理解,就是让一部分CPU负责系统日常“家务”。
例如:
普通任务调度;
网络处理;
系统管理;
定时器;
RCU等后台机制;
部分内核线程;
其他非实时工作。
那么实时CPU就可以尽可能减少这些活动。
这种设计的思想其实非常接近传统硬实时系统:
不要让关键任务和大量非关键任务共享同一执行资源。
区别在于,传统RTOS可能从系统设计之初就把任务和资源进行严格划分,而Linux需要在保留复杂通用生态的同时,通过配置和内核机制逐步建立这种隔离环境。
这也是实时Linux最有意思的地方。
它并不是把Linux变成一个完全不同的操作系统,而是在Linux原有生态基础上:
普通Linux ↓ 实时化 ↓ 资源划分 ↓ CPU隔离 ↓ IRQ隔离 ↓ 实时任务专用核心逐步构建确定性环境。
那么CPU isolation究竟要处理哪些问题?
首先是:
普通任务。
如果普通任务可以随意进入实时CPU,就会和实时任务共享CPU资源。
因此需要让普通任务尽可能运行在非实时CPU上。
第二个是:
中断。
这是非常容易被忽略的问题。
假设:
CPU7 ↓ 实时控制任务此时网卡突然产生大量中断:
Network IRQ ↓ CPU7 ↓ 打断实时任务那么即使实时任务优先级非常高,它的执行时间仍然会受到影响。
所以实时系统通常还需要考虑:
IRQ affinity。
也就是:
哪些中断应该由哪个CPU处理?
理想情况下,可以把大量普通中断放到housekeeping CPU:
CPU0-CPU5 ↓ 普通IRQ CPU7 ↓ 关键实时任务这样可以减少普通中断对实时CPU的干扰。
第三个问题是:
内核线程。
Linux中存在大量内核线程。
例如:
kworker migration rcu watchdog以及其他与具体驱动和系统功能相关的内核线程。
如果这些内核线程进入实时CPU并执行较长时间,同样会影响实时任务。
因此,CPU isolation真正做的事情其实非常复杂:
实时CPU ↓ 减少普通任务 ↓ 减少普通IRQ ↓ 减少非必要内核线程 ↓ 减少系统后台活动 ↓ 降低调度干扰 ↓ 提高实时任务确定性这就是为什么不能把CPU isolation简单理解为:
“把CPU7留出来。”
更准确的理解是:
尽可能减少非实时活动进入这个CPU。
三、为什么实时系统还要隔离IRQ?一个看似很小的中断,为什么可能影响整个控制周期
在实时系统中,中断是一个特别值得深入研究的问题。
因为从硬件角度来说,中断本身就是为了提高系统响应速度。
设备有事情需要CPU处理:
设备 ↓ 产生IRQ ↓ CPU响应这本来是一件好事。
但是从实时任务的角度看:
实时任务 ↓ 正在执行 ↓ 突然产生IRQ ↓ CPU处理IRQ ↓ 实时任务暂停 ↓ IRQ结束 ↓ 实时任务恢复如果中断非常短,影响可能非常小。
但如果系统中断频率很高,那么问题就会累积。
例如一个网络设备每秒产生大量中断。
如果这些中断恰好都进入实时CPU:
CPU7 RT Task IRQ RT Task IRQ RT Task IRQ RT Task IRQ实时任务虽然拥有最高优先级,但它的执行环境已经被大量系统活动切碎。
对于一个需要稳定1ms周期的控制任务来说,这种情况显然不是理想状态。
所以实时系统需要进一步做:
IRQ affinity规划。
简单来说,就是:
IRQ0 → CPU0 IRQ1 → CPU1 IRQ2 → CPU2 IRQ3 → CPU3而实时CPU尽可能少承担普通设备中断。
例如:
CPU0-CPU5 网络IRQ 磁盘IRQ USB IRQ 其他普通设备IRQ CPU7 实时控制 必要的实时设备IRQ这时候整个系统的CPU职责就开始变得清晰:
普通CPU 负责“复杂和繁杂”的事情 实时CPU 负责“关键和确定”的事情这种设计对于机器人系统尤其有价值。
假设机器人控制器拥有:
视觉 网络 运动规划 状态估计 关节控制 日志其中:
视觉 网络 日志可能产生大量普通计算活动。
而:
关节控制可能要求非常稳定的控制周期。
如果所有任务都放在同一个CPU上:
CPU0 ├── Vision ├── Network ├── Logging ├── Planning └── MotorControl那么MotorControl很容易受到其他任务的影响。
而如果进行合理的CPU资源划分:
CPU0-CPU3 视觉 网络 规划 日志 CPU4-CPU5 状态估计 CPU6-CPU7 实时控制那么实时控制任务就获得了相对独立的资源。
再进一步:
CPU6-CPU7 实时任务 必要IRQ 必要内核活动整个控制链路的确定性就会进一步提高。
这其实也是工业控制系统中非常重要的一种思想:
不是所有计算任务都需要同样的实时等级。
因此,与其让所有任务争抢同一套资源,不如按照任务的实时等级和关键程度进行资源划分。
这种思想最终会自然进入:
混合关键性系统。
也就是说:
高关键任务 ↓ 高确定性资源 普通任务 ↓ 普通资源 非关键任务 ↓ 共享资源这和现代openEuler Embedded中讨论的混合关键性部署方向实际上存在非常强的技术关联。
四、CPU隔离之后还不够:Timer、RCU、内核线程和调度活动仍然可能影响实时核心
到了这里,我们已经解决了一个问题:
普通任务不要进入实时CPU。
也解决了:
普通中断尽量不要进入实时CPU。
但如果真正进行实时系统调优,很快会发现:
CPU上还有很多“看不见的工作”。
这些工作不是用户直接启动的应用,却可能对CPU产生影响。
其中比较典型的就是:
timer;
RCU;
内核线程;
scheduler tick;
workqueue;
softirq。
这也是为什么真正的CPU isolation比简单绑核复杂得多。
先看timer。
Linux需要大量定时器机制。
例如:
任务超时 网络定时 内核周期任务 设备定时 调度器tick这些机制都可能在特定时间点触发CPU活动。
如果实时CPU不断被周期性tick打断,那么对于高精度实时任务来说,就可能形成额外的抖动。
因此实时Linux会进一步研究:
如何减少实时CPU上不必要的周期性系统活动。
这也是tickless和NO_HZ等机制存在的重要背景。
进一步就是RCU。
RCU是Linux内核中非常重要的一种同步机制。
它能够高效地解决大量读多写少场景下的数据同步问题。
对于普通Linux系统来说,RCU非常高效。
但对于实时系统而言:
RCU相关活动什么时候执行?
会不会影响实时CPU?
系统在CPU隔离之后是否还会产生RCU回调?
这些问题同样需要考虑。
因此,一个真正经过实时调优的CPU环境,通常不是简单地:
taskset CPU7就结束了。
而是需要综合考虑:
任务 + IRQ + Timer + RCU + 内核线程 + softirq + scheduler tick + workqueue这就是为什么实时系统优化最终会从:
“某个任务怎么调度?”
升级到:
“整个CPU核心上到底允许发生什么?”
这其实已经接近“核心隔离”的真正含义。
可以把它总结为一个非常直观的模型:
普通Linux CPU ┌───────────────────────┐ │ 普通任务 │ │ 网络 │ │ IRQ │ │ softirq │ │ timer │ │ RCU │ │ kworker │ │ 其他内核活动 │ └───────────────────────┘ 实时CPU ┌───────────────────────┐ │ 实时任务 │ │ 必要实时IRQ │ │ 必要内核活动 │ └───────────────────────┘两者最大的区别并不是:
实时CPU速度更快。
而是:
实时CPU上的活动更加可控。
这就是确定性的重要来源。
当然,这里需要特别强调一点:
CPU隔离并不是越彻底越好。
如果把大量CPU全部隔离出来,系统剩余的housekeeping CPU数量过少,那么网络、文件系统、后台服务等普通工作就会集中到少数CPU上,反而可能造成系统整体负载过高。
所以实际工程中需要根据:
CPU数量;
实时任务数量;
控制周期;
IRQ数量;
网络负载;
应用负载;
内核版本;
驱动情况;
综合设计CPU布局。
实时系统追求的并不是:
“把CPU全部隔离。”
而是:
把真正关键的计算资源从非关键活动中合理分离出来。
这其实就是“资源隔离”的核心思想。
五、从CPU绑核到核心隔离:为什么这可能成为国产实时操作系统的重要技术分水岭
到这里,我们可以重新回答最开始的问题:
CPU绑核和核心隔离到底有什么区别?
可以用一句非常简单的话概括:
CPU绑核,是规定一个任务去哪里;核心隔离,是规定一个核心应该承担什么。
CPU affinity关注的是:
Task ↓ CPU集合CPU isolation关注的是:
CPU ↓ 允许哪些活动IRQ affinity关注的是:
IRQ ↓ CPU集合而完整的实时核心设计,则需要把三者结合:
实时任务 ↓ 实时CPU ↑ IRQ隔离 ↑ 内核活动隔离最终形成:
普通CPU ┌──────────────────────┐ │ Linux普通任务 │ │ 网络 │ │ 文件系统 │ │ 后台服务 │ │ 普通IRQ │ │ 内核线程 │ └──────────────────────┘ 实时CPU ┌──────────────────────┐ │ 实时任务 │ │ 实时控制 │ │ 必要实时IRQ │ │ 必要系统活动 │ └──────────────────────┘这时候,实时任务拥有的就不只是:
一个高优先级。
而是:
一块相对独立的计算资源。
这就是从“实时调度”走向“实时资源管理”的关键变化。
而这对于国产实时操作系统来说,是一个非常值得长期研究的方向。
近年来,随着机器人、智能制造、工业控制和边缘计算快速发展,操作系统面临的任务已经发生明显变化。
过去一个嵌入式设备可能只需要运行一个控制程序:
控制程序 ↓ RTOS现在一台智能设备可能同时运行:
AI 视觉 网络 数据库 Web服务 远程运维 设备管理 实时控制这时候单纯依靠“所有任务提高优先级”显然无法解决问题。
系统必须开始回答:
哪些任务是实时任务? 哪些任务是普通任务? 哪些任务必须保证确定性? 哪些任务可以接受延迟? 哪些CPU应该服务实时任务? 哪些CPU应该服务普通Linux? 哪些IRQ可以进入实时核心? 哪些IRQ必须隔离? 哪些系统服务应该放到housekeeping CPU?这也是现代实时操作系统越来越强调:
核心隔离、资源隔离和混合关键性。
openEuler Embedded目前已经在Linux实时能力之外探索RTOS和混合关键性部署,通过Linux与RTOS协同承担不同类型任务。官方MICA相关文档提出,Linux可以承担文件系统、网络等通用服务,而RTOS可以承担实时控制和实时计算,并通过共享内存、OpenAMP等机制实现通信。
从这个角度来看,未来的国产实时操作系统竞争,可能不只是:
谁支持Linux?
谁支持PREEMPT_RT?
而会越来越关注:
谁能够构建更加确定的实时执行环境?
这也是望获OS可以重点建立技术认知的地方。
望获OS的实时技术宣传,如果只是停留在:
“支持实时Linux。”
其实很容易和其他方案形成同质化。
因为今天越来越多Linux发行版、嵌入式Linux方案以及国产操作系统都可以讨论实时Linux、PREEMPT_RT以及实时调度。
真正能够形成技术差异的,是进一步回答:
实时任务如何获得独立资源?
普通任务如何与实时任务隔离?
中断如何隔离?
CPU核心如何隔离?
如何降低实时任务运行环境中的非确定性因素?
这时候,“核心隔离”就不再只是一个配置项,而是可以上升到操作系统架构层面的技术能力。
当然,必须注意:
核心隔离不是万能的。
即使进行了CPU isolation,也不能简单地认为系统已经天然成为硬实时系统。
实时性能仍然受到:
CPU架构;
cache;
内存;
DMA;
驱动;
PCIe;
网络;
中断;
调度器;
锁;
应用程序;
编译器;
系统负载;
等大量因素影响。
因此真正严谨的实时系统设计,需要结合实际工作负载,通过cyclictest、ftrace、trace-cmd、perf等工具进行测量,而不能只根据某一个配置参数判断实时性能。
最终需要观察的是:
平均延迟 + 最大延迟 + 延迟分布 + 高负载下表现 + 长时间运行稳定性其中尤其重要的是:
最大延迟。
因为实时系统真正害怕的不是:
“平时稍微慢一点。”
而是:
“绝大多数时候都很快,但偶尔突然出现一个无法接受的长延迟。”
这也是核心隔离真正有价值的地方:
它不是简单追求平均性能,而是在尽可能减少影响实时任务的变量。
从普通Linux到PREEMPT_RT,再到CPU affinity、IRQ affinity、CPU isolation以及核心级资源隔离,可以看到实时Linux实际上正在经历一个非常清晰的技术演进:
普通Linux ↓ 解决“能不能抢占” ↓ PREEMPT_RT ↓ 解决“中断和锁会不会阻塞” ↓ CPU Affinity ↓ 解决“任务在哪里运行” ↓ CPU Isolation ↓ 解决“CPU上谁可以运行” ↓ IRQ / Timer / RCU / 内核线程隔离 ↓ 进一步减少系统干扰 ↓ 确定性实时执行环境而这条路线最终会指向一个更加宏观的问题:
实时操作系统究竟应该如何管理不同关键等级的任务?
例如:
高关键任务 → 专用实时核心 中关键任务 → 实时调度资源 普通业务 → 普通Linux核心 后台服务 → Housekeeping核心这已经不再是单纯的“CPU调度”问题,而是一个完整的操作系统资源隔离和混合关键性设计问题。
这也是下一阶段非常值得继续研究的方向。
如果说上一篇文章的结论是:
PREEMPT_RT让Linux更容易实时。
那么这一篇可以进一步把结论推进一步:
核心隔离让实时任务拥有更加独立、更加可控的CPU执行环境。
而下一步,我们还可以继续追问:
如果已经有了SCHED_FIFO、SCHED_RR和SCHED_DEADLINE等实时调度策略,那么不同调度策略到底应该解决什么问题?
为什么有些任务适合FIFO?
为什么有些任务适合RR?
为什么周期性实时任务又会涉及Deadline?
为什么“优先级越高越实时”其实是一个并不完整的理解?
这些问题会把我们从CPU资源隔离继续带回到Linux调度器本身。
下一篇就可以继续深入:
《SCHED_FIFO、SCHED_RR、SCHED_DEADLINE到底有什么区别?从Linux实时调度策略理解实时任务》
从这里开始,整个系列就会形成一条完整的技术链:
openEuler → PREEMPT_RT → 核心隔离 → 实时调度 → 优先级反转 → 混合关键性 → 望获OS实时架构。