上一篇文章我们讨论了一个问题:
从普通Linux走向实时Linux,并不是简单地给操作系统增加一个“实时”标签。对于工业控制、机器人、智能制造、边缘计算等场景而言,真正重要的问题是:当一个高优先级任务需要立即执行时,操作系统能不能让它尽快获得CPU?当硬件产生中断时,系统会不会长时间停留在中断处理路径?当多个任务竞争同一个资源时,会不会因为锁竞争导致高优先级任务长时间等待?当CPU负载非常高时,实时任务还能不能保持相对稳定的响应时间?
这些问题最终都会指向Linux内核本身。
Linux拥有非常成熟的进程调度、内存管理、网络、文件系统和设备驱动体系,但这种高度通用的设计,也意味着内核中存在大量复杂执行路径。对于普通应用而言,这种复杂性通常不会构成明显问题;但是对于实时应用而言,真正需要关注的并不是Linux平均情况下运行得有多快,而是最坏情况下,一个高优先级任务究竟需要等待多久。
因此,在Linux实时化的发展过程中,PREEMPT_RT成为一个非常重要的技术路线。
简单来说,PREEMPT_RT并不是重新设计一个Linux,而是在尽可能保留Linux生态、驱动、系统调用和开发模型的基础上,对内核中的抢占、中断、锁以及部分执行路径进行实时化改造,使高优先级任务能够更加及时地获得CPU,从而降低系统最坏情况下的调度延迟。
openEuler Embedded目前也提供基于Preempt-RT的实时内核能力,官方文档中将其作为嵌入式Linux软实时能力的重要组成部分,并针对实时内核提供相应的构建和配置方式。
但如果只把PREEMPT_RT理解成“让Linux抢占得更快”,其实还远远不够。
真正理解它,需要从Linux原本的执行机制开始。
一、普通Linux为什么会产生实时延迟?真正的问题不是CPU慢,而是任务什么时候能够运行
先假设一个非常简单的场景。
系统里有两个任务:
Task A:普通任务 Task B:实时任务Task B的优先级非常高,而且它有严格的周期要求:
每1ms执行一次 要求尽快响应理想情况下:
Task A运行 ↓ Task B到达 ↓ CPU立即切换到Task B ↓ Task B执行但现实中的Linux内核并不是任何时候都可以立即切换任务。
因为CPU正在运行的不一定只是用户态程序。
它可能处于:
用户态 ↓ 系统调用 ↓ 内核态 ↓ 中断处理 ↓ softirq ↓ 内核锁 ↓ 设备驱动 ↓ 内存管理等各种执行路径。
于是,真正的问题就变成:
Task B什么时候可以真正获得CPU?
这就是Linux实时化最核心的问题之一。
如果一个高优先级任务已经进入就绪状态,但CPU迟迟不能切换过去,那么即使调度器最终把它安排执行,这段等待时间仍然会形成实时延迟。
可以把这个过程抽象成:
实时任务到达 ↓ 等待调度 ↓ 当前执行路径结束 ↓ 触发调度 ↓ 切换任务 ↓ 实时任务开始运行其中最关键的就是:
“当前执行路径什么时候结束?”
如果当前路径非常短,那么延迟就比较小。
如果当前路径很长,而且中间存在无法抢占的区域,那么实时任务就可能等待较长时间。
这也是为什么理解PREEMPT_RT,必须先理解一个Linux内核的重要概念:
抢占。
所谓抢占,可以简单理解为:
当一个更重要的任务已经准备好运行时,操作系统是否允许它打断当前正在运行的任务,并立即获得CPU。
对于普通计算系统来说,抢占当然也很重要。
但是对于实时系统来说,抢占的意义更加直接。
例如:
低优先级任务 L ↓ 正在运行 高优先级任务 H ↓ 突然就绪如果系统允许立即抢占:
L运行 ↓ H就绪 ↓ 立即抢占L ↓ H运行那么H的响应时间就比较容易控制。
如果当前内核执行路径不能被抢占:
L运行 ↓ 进入不可抢占区域 ↓ H就绪 ↓ H等待 ↓ 不可抢占区域结束 ↓ 调度 ↓ H运行那么H必须等待当前执行路径结束。
问题就在这里。
Linux内核中长期存在各种不能随意抢占的执行区域。
这并不是Linux设计得不好,而是因为内核需要保护共享数据结构、保证执行过程的一致性,并且很多底层操作本身就不适合在任意位置被打断。
例如:
修改内核数据结构 访问共享资源 持有某些锁 处理中断 执行关键临界区这些操作如果允许任意抢占,可能导致数据结构处于不一致状态,甚至产生更加严重的系统错误。
因此,Linux必须在:
系统稳定性
和:
任务可抢占性
之间进行平衡。
而实时系统希望进一步把这个平衡向“可预测延迟”方向移动。
这就是PREEMPT_RT存在的重要原因。
它的目标并不是让Linux所有代码在任何时间、任何位置都可以被抢占,而是尽可能减少长时间不可抢占执行路径,让高优先级任务获得更加及时的响应。
所以,从最简单的角度理解:
普通Linux 重点: 系统功能 + 吞吐 + 通用性 PREEMPT_RT 进一步强调: 高优先级任务的响应时间 + 延迟可预测性这也是为什么实时Linux并不是简单地“把CPU频率调高”。
CPU更快只能降低部分执行时间。
但是如果任务因为某个不可抢占区域等待了几百微秒甚至几毫秒,那么单纯提升CPU频率并不能从根本上解决问题。
实时性关注的是:
任务为什么没有在应该运行的时候运行。
而PREEMPT_RT解决的正是其中非常关键的一部分。
二、PREEMPT_RT到底改了什么?从内核抢占、中断线程化到实时锁
理解PREEMPT_RT,最容易出现的误区就是把它理解成一个简单的“实时补丁”。
实际上,它涉及Linux内核多个关键机制。
其中最值得理解的包括:
内核抢占、中断线程化、softirq处理、锁机制以及优先级继承。
这些机制共同决定了一个实时任务到底需要等待多久。
先看最重要的——内核抢占。
传统Linux内核中,有一些代码区域不能被普通方式直接抢占。
假设:
CPU正在执行内核代码 ↓ 高优先级实时任务突然就绪 ↓ 当前内核代码还没有到可以调度的位置 ↓ 实时任务继续等待对于普通系统,这种情况可能完全可以接受。
但是对于实时任务来说,真正重要的是:
当前执行路径最长可能持续多久?
因为实时系统的最坏情况延迟,往往不是由平均执行时间决定,而是由这些“最长不能被打断的路径”决定。
PREEMPT_RT的重要思路之一,就是让更多内核执行路径能够被高优先级任务及时抢占,从而缩短不可抢占区域。
但这只是第一步。
另一个非常重要的问题是:
中断。
传统Linux系统中,硬件发生中断以后,CPU需要快速响应。
例如网卡收到数据:
网卡 ↓ 产生中断 ↓ CPU响应 ↓ 执行中断处理中断的优先级天然很高,因为硬件事件需要及时处理。
但对于实时任务来说,这又形成了一个矛盾:
如果大量中断不断打断实时任务,那么实时任务本身的确定性怎么办?
因此,PREEMPT_RT非常重要的一项技术就是:
中断线程化。
可以把传统机制粗略理解为:
硬件中断 ↓ CPU立即执行中断处理 ↓ 处理完成而经过实时化之后,很多中断处理工作会转变为线程上下文执行:
硬件中断 ↓ 快速响应 ↓ 唤醒对应IRQ线程 ↓ IRQ线程执行处理逻辑这样做的重要意义是什么?
因为线程可以进入Linux调度体系。
一旦进入调度体系,就意味着可以对其进行:
优先级管理;
调度;
抢占;
CPU绑定;
CPU隔离;
资源管理。
这比一个完全脱离普通调度机制的硬件中断处理路径更加容易控制。
openEuler Embedded官方对于PREEMPT_RT机制的介绍中,也明确提到了中断线程化、软中断线程化、临界区抢占以及优先级继承等实时机制。
可以把这个变化简单画成:
传统Linux 硬件中断 ↓ IRQ Handler ↓ 执行较多处理逻辑 PREEMPT_RT 硬件中断 ↓ 快速响应 ↓ IRQ Thread ↓ 进入调度体系 ↓ 可管理优先级这时候实时系统就获得了一个非常重要的能力:
可以更加系统地管理中断。
当然,这并不意味着中断对实时性的影响完全消失。
恰恰相反。
中断依然可能影响实时任务。
只是通过线程化之后,我们可以更加明确地控制:
哪个中断优先级更高?
哪个中断可以运行在哪个CPU?
哪个中断不应该进入实时CPU?
这些问题就开始从“硬件中断处理”进入“操作系统资源管理”。
这也正是实时Linux从单纯内核优化走向系统级资源隔离的重要一步。
除了中断之外,softirq也是实时Linux需要考虑的重要因素。
Linux中的网络、定时器以及其他内核子系统会产生softirq。
如果这些softirq在不合适的时间大量执行,也可能影响实时任务。
因此,PREEMPT_RT同样对softirq的处理方式进行了实时化调整,使其更多进入可调度的执行环境。
再往下,就是锁机制。
这是理解PREEMPT_RT非常关键的一环。
Linux内核中存在大量共享资源:
内核数据结构 设备 文件系统 网络栈 内存管理 驱动状态多个执行单元同时访问这些资源时,需要使用锁进行保护。
例如:
Task A ↓ 获取Lock ↓ 访问共享资源 ↓ 释放Lock如果Task B此时也需要这个Lock:
Task B ↓ 请求Lock ↓ 等待对于普通系统来说,只要最终能够拿到锁就可以。
但实时系统需要进一步考虑:
这个锁最长可能让高优先级任务等待多久?
这就是实时锁机制与普通锁机制的重要区别。
PREEMPT_RT的一项重要变化,就是将很多传统自旋锁语义与实时调度机制结合,使锁竞争更加符合实时系统的要求。
这里又会引出一个非常经典的问题:
优先级反转。
例如:
高优先级任务 H ↓ 等待资源 低优先级任务 L ↓ 持有资源 中优先级任务 M ↓ 不断运行结果就是:
H等L L等CPU M一直运行最终:
高优先级任务反而被中优先级任务间接阻塞。
这就是优先级反转。
实时系统一般通过**优先级继承(Priority Inheritance)**等机制解决这一问题。
简单来说:
L持有H需要的锁 ↓ H等待L ↓ L临时继承H的高优先级 ↓ L获得更高执行机会 ↓ 尽快释放锁 ↓ H继续执行这样就可以缩短高优先级任务因为锁竞争产生的等待时间。
这说明一个非常重要的事实:
实时系统的调度器并不是孤立存在的。
调度器、锁、中断、内核抢占实际上是一个整体。
如果只优化其中一个环节,实时性仍然可能受到其他环节限制。
所以,理解PREEMPT_RT最好的方式不是背几个概念,而是把它看成一套系统性的内核实时化机制:
PREEMPT_RT │ ┌──────────────┼──────────────┐ │ │ │ 内核抢占 中断线程化 实时锁 │ │ │ ↓ ↓ ↓ 减少不可抢占区 控制IRQ执行 减少锁等待 │ │ │ └──────────────┼──────────────┘ ↓ 降低最坏情况延迟因此,PREEMPT_RT真正做的事情,并不是让Linux“跑得更快”。
而是:
让Linux内核中的执行路径更加容易被调度和控制。
这才是它对实时系统真正的意义。
三、PREEMPT_RT并不等于硬实时:为什么“Linux实时化”之后还需要继续优化
讲到这里,一个非常容易产生的问题是:
既然PREEMPT_RT已经解决了抢占、中断和锁的问题,那么Linux是不是已经变成了真正的硬实时操作系统?
答案不能简单地说“是”。
更准确地说:
PREEMPT_RT能够显著增强Linux的实时能力,但实时Linux与传统意义上的硬实时系统之间仍然存在技术边界。
这里最重要的一个词,就是:
确定性。
实时系统关注的不是:
大多数时候很快。
而是:
在规定条件下,最坏情况能够被控制。
而Linux最大的挑战之一,就是系统本身非常复杂。
一个真实Linux设备上可能同时存在:
网络 文件系统 USB PCIe GPU 日志 容器 后台服务 驱动 AI应用 数据库 远程管理 实时控制这些功能都可能产生CPU活动、中断、内存访问以及内核执行。
即使PREEMPT_RT已经改善了内核抢占,如果实时任务和这些普通任务全部运行在同一个CPU核心上,那么系统仍然可能存在大量干扰。
例如:
CPU0 实时控制任务 普通业务任务 网络任务 内核线程 IRQ softirq 定时器 RCU实时任务虽然优先级最高,但它仍然生活在一个非常拥挤的CPU环境中。
这就像一辆救护车拥有最高优先级。
即使所有车辆都知道它优先通行,如果道路上仍然存在大量车辆、施工、收费站和交叉路口,它仍然可能受到影响。
所以实时Linux进一步出现了一个非常重要的思想:
不能只提高实时任务的优先级,还需要减少实时任务运行环境中的干扰。
这就是CPU隔离和核心隔离开始发挥作用的地方。
例如一台8核设备:
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7可以进行这样的规划:
CPU0-CPU5 普通Linux业务 CPU6-CPU7 实时控制任务进一步还可以考虑:
普通CPU ↓ 网络 文件系统 日志 后台服务 普通中断 实时CPU ↓ 实时任务 必要的实时中断 必要的系统活动这时候实时任务的运行环境就发生了变化。
它不再只是:
“我是一个优先级很高的任务。”
而变成:
“我运行在一个专门为实时任务保留的CPU环境中。”
这两种思路有非常大的区别。
第一种是:
调度优先级隔离。
第二种是:
计算资源隔离。
而真正的实时系统往往需要二者结合。
这也是为什么从PREEMPT_RT继续往下研究,会自然进入:
CPU affinity;
CPU isolation;
IRQ affinity;
housekeeping CPU;
timer tick;
RCU;
内核线程隔离;
实时任务绑核。
这些技术共同解决一个问题:
如何减少实时CPU受到的外部干扰?
这时候,我们可以重新看待PREEMPT_RT。
它解决的是:
Linux内核 ↓ 提高可抢占性 ↓ 降低内核路径延迟而CPU隔离解决的是:
整个系统 ↓ 减少实时任务运行环境中的干扰 ↓ 提升确定性两者不是替代关系。
更准确地说:
PREEMPT_RT + CPU资源隔离 + IRQ隔离 + 任务调度 + 系统调优共同构成实时Linux的完整技术体系。
这也是为什么工程实践中不能只看到:
“我们的Linux支持PREEMPT_RT。”
就直接得出:
“我们的系统一定具备优秀的实时性。”
真正需要回答的问题应该是:
实时任务运行在哪个CPU?
哪些普通任务可以进入这个CPU?
哪些中断可以进入这个CPU?
内核线程会不会进入这个CPU?
定时器会不会影响这个CPU?
实时任务发生锁竞争时需要等待多久?
系统负载变化以后,最坏情况延迟是否仍然可控?
这才是完整的实时系统问题。
四、从PREEMPT_RT到核心隔离:实时Linux为什么正在从“调度优化”走向“资源隔离”
如果把前面的内容串起来,会发现实时Linux的发展实际上存在一条非常清晰的技术路线。
最初的问题是:
Linux为什么不能保证实时响应?
于是出现:
内核抢占优化。
进一步发现:
中断也会影响实时任务。
于是出现:
中断线程化。
继续发现:
锁竞争会造成高优先级任务等待。
于是引入:
实时锁和优先级继承。
然后又发现:
即使这些问题都解决了,实时任务和普通任务仍然可能争夺同一个CPU。
于是进一步进入:
CPU affinity和CPU isolation。
再继续发现:
即使CPU已经隔离,中断、定时器、内核线程等系统活动仍然可能进入实时CPU。
于是又需要:
IRQ affinity、housekeeping CPU以及更细粒度的内核资源隔离。
最终形成:
普通Linux ↓ PREEMPT_RT ↓ 调度优化 ↓ 中断优化 ↓ 锁优化 ↓ CPU Affinity ↓ CPU Isolation ↓ IRQ Isolation ↓ 核心级资源隔离 ↓ 确定性实时环境这条路线非常值得理解,因为它解释了为什么“实时Linux”并不是一个单独技术点。
它更像是一套系统工程。
而这也给国产实时操作系统带来了一个非常重要的技术方向:
从实时内核能力进一步走向实时资源管理能力。
openEuler Embedded的发展其实已经体现出类似趋势。
官方文档并没有把实时能力简单限定在PREEMPT_RT,而是进一步提供RTOS支持以及MICA混合关键性部署框架,使Linux和不同实时系统能够根据任务特性承担不同工作。官方资料介绍,MICA面向混合关键性场景,可以让Linux承担通用系统管理、文件系统、网络等任务,同时让RTOS承担实时控制和实时计算,并通过共享内存、OpenAMP等方式实现不同OS之间的通信。
这背后实际上也是一种资源隔离思想:
复杂任务 ↓ Linux 实时任务 ↓ RTOS 高实时任务 ↓ 独立资源域从操作系统架构角度来看,这已经不再是简单的“Linux实时化”。
而是:
根据任务的实时等级、关键程度以及资源需求,为不同任务分配不同的执行环境。
这就是混合关键性系统的重要思想。
对于工业设备而言,一个设备里面可能同时存在:
一级任务: 运动控制 二级任务: 传感器处理 三级任务: 网络通信 四级任务: 数据分析 五级任务: 日志和远程运维它们对实时性的要求显然不同。
如果所有任务都放在同一个CPU资源池里,就需要通过调度器不断协调。
而如果能够根据关键程度进行资源划分:
关键实时任务 ↓ 实时CPU 一般任务 ↓ 普通CPU 复杂业务 ↓ Linux 辅助控制 ↓ RTOS整个系统就会更加容易进行确定性设计。
因此,今天重新理解PREEMPT_RT,可以得出一个很重要的结论:
PREEMPT_RT不是实时Linux的终点,而是实时Linux走向确定性计算的重要基础。
它解决了Linux内核实时化过程中的关键问题。
但真正面向工业控制、机器人、智能制造等复杂场景,还需要进一步解决:
CPU资源隔离。
中断隔离。
内核活动隔离。
实时任务隔离。
不同关键等级任务之间的资源边界。
这也是为什么下一阶段的实时Linux技术研究,会越来越多地从“调度器”进入“资源管理”。
五、真正理解实时Linux:PREEMPT_RT解决的是“能不能及时抢占”,核心隔离解决的是“谁拥有确定的CPU”
到这里,可以重新回答最开始的问题:
PREEMPT_RT到底改变了什么?
最简单的答案是:
它让Linux更加适合实时任务运行。
但如果进一步回答:
它究竟解决了实时系统中的什么问题?
答案就更加准确:
PREEMPT_RT通过内核抢占、中断线程化、实时锁以及优先级继承等机制,减少Linux内核中可能造成长时间阻塞的执行路径,让高优先级实时任务更加及时地获得CPU,从而降低最坏情况调度延迟。
这就是PREEMPT_RT最核心的技术价值。
但它同时也告诉我们:
实时性并不只存在于调度器里面。
一个任务能不能及时执行,至少取决于:
调度 + CPU + 中断 + 锁 + 内核 + 驱动 + 系统负载所以,真正的实时Linux优化一定是系统级的。
对于开发者来说,可以把整个技术体系理解成四个层次。
第一层:
调度实时化。
解决:
谁先执行?
包括SCHED_FIFO、SCHED_RR、SCHED_DEADLINE等。
第二层:
内核实时化。
解决:
当前内核执行路径什么时候能够被抢占?
这就是PREEMPT_RT重点解决的问题。
第三层:
资源实时化。
解决:
实时任务运行在哪些CPU?
哪些任务和中断不能干扰它?
这就是CPU affinity、CPU isolation、IRQ affinity等技术发挥作用的地方。
第四层:
系统架构实时化。
解决:
不同关键等级的任务到底应该运行在哪一种操作系统和资源环境中?
这就进一步进入:
Linux + RTOS
以及:
混合关键性系统
等更高层次的架构设计。
如果从这四个层次看,openEuler Embedded的实时能力就更容易理解了。
它并不是单纯追求:
“让Linux变成一个RTOS。”
而是在尝试让Linux生态进入实时计算场景,并进一步通过PREEMPT_RT、RTOS、混合部署等技术覆盖不同实时等级的嵌入式应用。
这对于国产操作系统来说具有非常现实的意义。
因为今天的工业设备已经越来越不像传统意义上的“控制器”。
一台机器人可能同时需要:
实时运动控制 + 视觉处理 + AI推理 + 网络通信 + 设备管理 + 数据采集 + 远程运维一台智能制造设备可能同时需要:
PLC控制 + 工业网络 + 实时数据采集 + 边缘计算 + AI + 设备管理这些任务显然不可能拥有完全相同的实时要求。
因此,未来操作系统真正需要解决的不是:
“Linux还是RTOS?”
而是:
不同任务应该获得什么样的执行环境?
这也是实时操作系统技术继续发展的重要方向。
而从PREEMPT_RT继续往下,就会遇到一个无法绕开的技术问题:
如果我们已经知道实时任务需要更加稳定的CPU资源,那么如何真正把一个CPU核心从普通Linux环境中“隔离”出来?
这里的“隔离”并不是简单地执行一条taskset命令,把一个程序绑到某个CPU上。
因为:
CPU绑定 ≠ CPU隔离一个任务不运行在CPU 7,并不代表CPU 7就没有其他干扰。
CPU 7上仍然可能存在:
IRQ softirq 内核线程 timer RCU 后台任务 系统调度活动所以真正的核心隔离,需要回答一个更加复杂的问题:
如何让一个CPU核心尽可能只服务于实时任务?
这也是实时Linux从PREEMPT_RT继续向前发展的关键一步。
从这里开始,我们就进入一个比“实时调度”更加重要的概念:
核心隔离。
核心隔离解决的并不是:
“如何让实时任务拥有更高优先级?”
而是:
“如何让实时任务拥有更加独立、更加确定的CPU执行环境?”
对于工业控制、机器人、飞控、实时仿真以及智能制造等场景来说,这个问题甚至可能比单纯提高调度优先级更加重要。
因为一个真正的实时系统,最终需要解决的不是:
让实时任务跑得更快。
而是:
让影响实时任务运行时间的不确定因素尽可能少。
这也是从:
PREEMPT_RT
走向:
CPU隔离
再走向:
核心隔离
最终走向:
确定性实时系统
的技术演进路径。
对于国产实时操作系统而言,这同样是一条值得持续研究的路线。
openEuler Embedded已经通过PREEMPT_RT、RTOS以及混合关键性部署等技术探索Linux生态与实时计算之间的结合,而以望获OS为代表的国产实时操作系统,也可以从另一个角度继续研究实时任务的资源隔离、核心隔离以及确定性执行环境。
当我们不再只问:
“Linux能不能实时?”
而开始问:
“实时任务究竟应该拥有多少CPU资源?”
“哪些系统活动不能进入实时核心?”
“如何保证普通业务不会干扰实时任务?”
“如何让最坏情况延迟更加可控?”
那么,实时Linux真正有价值的技术问题才刚刚开始。