尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux 内核 PREEMPT_RT 实时抢占机制详解:睡眠自旋锁、线程化中断、配置项与硬件考量

Linux 内核 PREEMPT_RT 实时抢占机制详解:睡眠自旋锁、线程化中断、配置项与硬件考量 Linux 内核 PREEMPT_RT 实时抢占机制详解睡眠自旋锁、线程化中断、配置项与硬件考量【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文基于 Linux 内核官方文档 Real-time preemption 文档集含理论、行为差异、硬件、架构移植与内核配置五篇子文档系统讲解 PREEMPT_RT 如何将 Linux 内核转变为全抢占式实时内核。读完后你能理解 rtmutex 睡眠自旋锁与优先级继承、强制线程化中断的工作方式掌握 PREEMPT_RT 与普通内核在锁、软中断、定时器、内存分配上的行为差异并知道如何正确配置实时内核、评估硬件对最坏延迟的影响。一、PREEMPT_RT 是什么PREEMPT_RT 面向内核开发者与贡献者解释实时化改造的核心概念以及相对于非实时配置所需的全部改动。其改造路径可以用三句话概括见 theory.rst替换锁原语把spinlock_t等自旋锁替换为可抢占、支持优先级继承的实现——rtmutex实时互斥锁强制线程化中断让绝大多数中断处理运行在进程上下文由调度器管理结果除入口代码、调度器和底层中断处理等少数关键路径外内核变得完全可抢占。这使绝大多数内核执行上下文都纳入调度器控制显著增加抢占点从而缩短高优先级任务变为可运行到真正在 CPU 上执行之间的延迟。二、调度基础SCHED_OTHER 与 SCHED_FIFO内核默认使用SCHED_OTHER策略任务在消耗了相对于其他可运行任务的公平份额 CPU 时间后才被抢占但该策略不保证新的SCHED_OTHER任务就绪时立即抢占当前任务。实时策略如SCHED_FIFO行为完全不同一旦高优先级的实时任务变为可运行调度器会立即选择它执行该任务一直运行直到自愿让出 CPU通常是阻塞在某个事件上。理解这一点对理解 PREEMPT_RT 所有后续机制都是前提——实时化的本质就是让内核代码尽量以SCHED_FIFO线程的形式运行让调度器能够及时切换到更高优先级任务。三、睡眠自旋锁从忙等到让出 CPU在非 PREEMPT_RT 内核中获取spinlock_t的流程是先禁用抢占然后主动自旋等待锁可用释放后再恢复抢占。从实时角度看这是危险的——禁用抢占会阻止调度器切换到更高优先级任务。PREEMPT_RT 的对策是用不关闭抢占的睡眠自旋锁取代自旋锁在 PREEMPT_RT 上spinlock_t底层由 rtmutex 实现rtmutex 设计可参考 rt-mutex.rst各类锁在实时配置下的行为总览见 locktypes.rst竞争到锁时任务不再自旋而是禁用 CPU 迁移pin 住当前 CPU向持锁者让渡自己的优先级优先级继承自愿调度出去睡眠等待锁释放。其中禁用 CPU 迁移起到了类似禁用抢占的效果但又允许本 CPU 上的抢占发生同时保证持锁任务始终在同一 CPU 上运行——两者兼得。四、优先级继承Priority InheritancePREEMPT_RT 内核中spinlock_t、mutex_t等锁均构建在支持 PI 的 rtmutex 之上。当一个任务阻塞在锁上时PI 机制会把被阻塞任务的调度参数临时传递给持锁者例如SCHED_FIFO任务 A 阻塞在由SCHED_OTHER任务 B 持有的锁上A 的策略与优先级被 B 临时继承随后 A 睡眠B 实际上成为系统中最高优先级任务得以继续执行并最终释放锁B 释放锁后恢复原调度参数A 醒来继续执行。这正是实时系统避免优先级反转的核心机制也是spin until ready等问题见第六节必须依赖 PI 型握手的原因。五、线程化中断把硬中断交给调度器中断处理是抢占禁用且不受调度器控制代码的另一大来源。PREEMPT_RT 通过强制线程化将其纳入调度器管理中断处理被拆成两阶段初级处理程序primary handler仍在硬中断上下文、关中断状态下执行唯一职责是唤醒对应的线程化处理器线程化处理器即request_irq()中传入的中断处理函数在进程上下文中运行由内核调度。从唤醒中断线程到线程化处理完成该中断源在控制器中被屏蔽设备中断保持 pending 状态但不会再次触发 CPU——系统因此可以退出中断上下文在可调度线程中完成处理。关键细节线程化处理器默认使用SCHED_FIFO策略优先级 50即MAX_RT_PRIO / 2实时优先级区间中点若线程化处理器执行期间产生软中断这些软中断例程会在处理器完成后于同一线程内调用且执行期间抢占保持开启。六、PREEMPT_RT 与普通内核的关键行为差异以下差异逐条来自 differences.rst是驱动开发与内核开发在实时平台上最易踩坑的部分。6.1 锁spinlock_t 与 raw_spinlock_t自旋锁原本用于保护同时被中断与进程上下文访问的数据结构因此存在_irq()/_irqsave()后缀变体先关中断再拿锁。但在 PREEMPT_RT 上中断已被强制线程化、不再运行于硬中断上下文使用spinlock_t时无需在加锁流程中关闭中断。对于中断处理、调度器、定时器子系统等底层核心组件内核改用raw_spinlock_t它保留传统语义禁用抢占配合_irq()/_irqsave()时关闭中断确保这些必须保持不可抢占/关中断的临界区同步正确。6.2 软中断与 bottom half软中断由中断处理器置起、在处理器返回后执行。由于它们运行在线程上下文可被其他线程抢占——不能再假设软中断上下文是关抢占的。由此推导出两条重要规则不能依赖进程上下文中的local_bh_disable()来保护 per-CPU 变量这在 PREEMPT_RT 下不是可靠的同步手段若出于性能需要这类保护应改用local_lock_nested_bh()在非 RT 内核上它让 lockdep 校验 BH 已关闭在 PREEMPT_RT 上它会加入必要的锁以保证正确保护同时让锁作用域对阅读者显式可见。6.3 per-CPU 变量应避免仅靠preempt_disable()保护 per-CPU 变量尤其当临界区运行时间无界或可能调用可睡眠 API 时若嫌spinlock_t开销大可用local_lock_t非 RT 配置下lockdep 关闭时零运行时开销启用 lockdep 时校验锁只能在进程上下文中获取在 PREEMPT_RT 上它由per-CPU 的spinlock_t实现在保持可抢占的同时安全保护 per-CPU 数据由于 PREEMPT_RT 的spinlock_t不关抢占不能再靠隐式关抢占保护 per-CPU 数据。若确实必须隐式关抢占且无法使用local_lock_t可选preempt_disable_nested()非 RT 内核上 lockdep 会校验抢占本已关闭PREEMPT_RT 上它会显式禁用抢占。6.4 定时器默认执行上下文反转非 RT 内核hrtimer 默认在硬中断上下文执行仅HRTIMER_MODE_SOFT初始化的定时器在 softirq 上下文执行。PREEMPT_RT 内核行为反转——hrtimer 默认在 softirq 上下文执行通常在ktimersd线程中该线程以最低实时优先级运行先于所有SCHED_OTHER任务、但不干扰更高优先级实时线程若确实需要在 PREEMPT_RT 上于硬中断上下文执行必须显式标记HRTIMER_MODE_HARD。这一点在 include/linux/hrtimer.h 的头文件注释中可以直接印证HRTIMER_MODE_HARD定义为即使在 PREEMPT_RT 上也在硬 irq 上下文执行并提供HRTIMER_MODE_ABS_HARD/HRTIMER_MODE_REL_HARD组合宏。6.5 内存分配kmalloc()、alloc_pages()需要gfp_t标记上下文。非 RT 内核上从中断上下文或关抢占区分配必须用GFP_ATOMIC而 PREEMPT_RT 的分配器内部使用睡眠锁关抢占时无法获取——因此GFP_ATOMIC在 RT 上并不可行。所幸 RT 把绝大多数传统关抢占/关中断上下文搬进了可睡眠的线程上下文。仍然有问题的是那些显式禁用抢占或中断的代码内存分配以及涉及内部加锁的释放路径如kfree()、free_pages()必须移出这类临界区之外。6.6 IRQ workirq_workAPI 用于在无法安全使用工作队列的上下文NMI 处理器、调度器内部等安排回调非 RT 系统所有 irq_work 立即在中断上下文执行IRQ_WORK_LAZY项延迟到下一个 tick但仍在中断上下文PREEMPT_RT 系统由于回调可能获取睡眠锁或运行时间无界改由per-CPU 的 irq_work 内核线程在线程上下文处理该线程以最低实时优先级运行例外是IRQ_WORK_HARD_IRQ标记项仍在硬中断上下文执行IRQ_WORK_LAZY项继续延迟到下一个 tick同样由 irq_work 线程执行。6.7 RCU 回调RCU 回调默认在 softirq 上下文触发运行成本过高且可能与ksoftirqd中的SCHED_OTHER任务争抢 CPU。RCU 子系统支持改用进程上下文执行回调启动参数rcutree.use_softirq0而在 PREEMPT_RT 配置的内核中该设置是被强制的。6.8 Spin until ready 与活锁自旋直到就绪模式假设抢占/软中断/中断处于关闭状态若数据结构被标记 busy说明它正在被另一个 CPU使用自旋终会成功。经典例子是hrtimer_cancel()与timer_delete_sync()——取消方自旋等待回调完成是安全的因为回调只能跑在别的 CPU 上且终将结束。PREEMPT_RT 上这一假设被打破定时器回调运行在线程上下文更高优先级的取消方线程可能抢占回调线程本身而调度器又因亲和性约束无法把回调线程迁走——即使在多核系统上自旋也可能造成活锁。解法是取消方与回调方都改用支持优先级继承的握手机制取消方可以挂起等待回调完成既保证前向进展又规避活锁。6.9 序列锁seqlock的 RT 扩展序列计数器与顺序锁的完整说明见 seqlock.rst。为解决上述自旋读者—可能阻塞的写者交接问题seqlock 接口被扩展把写者串行化锁直接内嵌进序列计数器类型形成seqcount_spinlock_t、seqcount_mutex_t等复合类型。复合类型允许读者检测到写正在进行时主动提升写者优先级帮助其完成更新而不是自旋空等若使用普通seqcount_t必须格外小心读者-写者同步写者的更新必须相对于读者保持串行化且不可抢占——这在 PREEMPT_RT 上无法用普通spinlock_t做到它不关抢占此时seqcount_spinlock_t是首选例外如果读者不需要与写者串行化只需检测写是否已开始即没有自旋使用seqcount_t是合理的。七、硬件对实时性的影响hardware.rst 指出CPU、内存与互联总线都是共享资源任何单一应用对某资源的重占用都会破坏其他负载的确定性。7.1 系统内存与缓存主存与缓存是最常见的共享资源一个任务占满缓存会迫使另一任务等待缓存行写回。缓解手段让应用尽量不共享同一 CPU 缓存内核会向用户空间导出缓存拓扑可用 hwloc 项目的 lstopo 可视化工具查看层次即使缓存共享最小化内存带宽瓶颈仍可能存在——GPU、网卡等外设也通过 DMA 使用内存硬件支持资源划分时可进一步控制x86 上 Intel 的 CAT 与 AMD 的 PQoS 提供缓存分区Arm64 对应 MPAMMemory System Resource Partitioning and Monitoring。这些特性通过 Linux Resource Controlresctrl接口配置观测手段perf 可以分析缓存缺失并对比相邻 CPU 不同负载下的变化perf c2c还能定位多核反复读写同一缓存行的 cache-to-cache 问题。7.2 硬件总线PCI 类总线相对简单寄存器访问直达设备最坏情况是读操作让 CPU 阻塞到设备响应USB 更复杂寄存器读写被封装为 URB 由主控制器发送请求必须对齐到按端点类型与主控制器调度规则确定的下一帧边界由此引入额外延迟——USB 网卡吞吐可能达标但收发时延可能不满足实时需求电源管理会进一步叠加总线延迟例如启用 ASPM 的 PCIe 可挂起设备-主机链路SoC 内部总线同样可能受电源管理机制影响。7.3 虚拟化在 KVM 等虚拟化环境中每个 guest CPU 是宿主上的一个线程。若该线程以实时优先级运行需要验证系统能否长期维持实时优先级线程不会被低优先级线程抢占被 pin 到同一 CPU 的低优先级线程可能完全得不到 CPU 时间即使隔离了 CPU系统仍可能意外在该 CPU 上启动 per-CPU 线程让 guest CPU 真正空闲很难需同时避免任务调度和中断处理且 guest 若以idlepoll启动将永不进入空闲状态设备处理方面模拟 PCI 或 VirtIO 设备需要宿主侧配合引入宿主拦截与调度的额外延迟直接把 PCIe 设备直通给 guest 可规避支持 SR-IOV 的网络/存储控制器可拆分为多个虚拟功能分给不同 guest。7.4 网络低延迟网络场景下完整网络协议栈本身可能成为延迟源可用XDP绕过大部分栈逻辑仍复用内核网络驱动要求网卡驱动支持 XDP最好带 skb pool应用使用 XDP socket进一步调优包括 BPF 过滤、网络队列调优与基于时间发送的 qdisc 配置——这些技术在 TSNTime-Sensitive Networking环境中常见。7.5 固件EFI 与 OP-TEE固件可能执行内核无法直接触及的操作甚至抢占或拦截内核例如内存 scrubbing 期间固件周期性暂停内核、回读内存硬件级 scrubbing 则独立于固件运行。内核被长时间拦截的时段可通过硬件延迟检测器hwlat detector暴露。EFIEFI 运行时服务如读写 EFI 变量调用期间体系架构往往需要关闭内核抢占甚至中断服务调用时长直接决定可观测延迟。因此PREEMPT_RT 内核默认禁用 EFI 运行时服务。如确需启用原生实现通过名为efi_runtime的工作队列调用服务可将该工作队列通过/sys/devices/virtual/workqueue/efi_runtime/cpumask绑定到 housekeeping CPU避免长服务调用影响其他 CPU 上的实时负载。注意部分 x86 实现在调用期间会暂停所有 CPU此时单 CPU 绑定无济于事必须实测验证。OP-TEEArm从普通世界Linux到安全世界OP-TEE经 EL3 安全监视器切换由smc或hvc指令发起。调用约定分两类yielding call处理服务前会解除中断屏蔽允许普通世界中断发生fast call原子执行普通世界与安全世界的中断都被屏蔽。世界切换与上下文保存/恢复的额外开销通常在几微秒量级。需要特别警惕运行超时的 fast call例如偶发的长加密计算以及会向普通世界发起RPC 请求的 OP-TEE 服务如 RPMB发出请求的线程会阻塞到 RPC 完成即使 Linux 本身保持可抢占。八、实时内核的 Kconfig 配置要点kernel-configuration.rst 面向系统集成者按字母序列出会影响最坏延迟的配置项并给出期望值Expectation与严重程度Severity。核心结论汇总如下配置项期望严重度要点CONFIG_PREEMPT_RT开启fatal不启用则内核完全不可抢占无法实时CONFIG_CPU_FREQ开启high保证处理器可达最大频率注意实时不等于越快越好有时要求固定在某个频率CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE开启high实时负载期望运行期间频率固定performance governor 是最简单的纯内核配置手段非 performance 的 governor禁用medium避免用户态任务隐式改变频率破坏确定性ONDEMAND明确不应用于实时系统频率随负载波动严重损害确定性若必须保留可选CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE由用户态初始化时设稳定频率或CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE要求低频时CONFIG_CPU_IDLE开启infoC 状态可显著降低功耗但增加进出延迟不要整体禁用影响硬件寿命与热行为应通过启动参数processor.max_cstate1把最大 C 状态限制在 C1可用cpupower idle-info查看可用空闲态CONFIG_DRM禁用info集成 GPU 与 CPU 争抢 LLC 与内存带宽现代集显以牺牲 CPU 确定性换图形性能如 Intel Gen9 集显、AMD APU、Zynq UltraScale MPSoC EG/EV若图形与实时任务共存需用 glmark2 之类工具压测并测量系统延迟CONFIG_EFI_DISABLE_RUNTIME开启mediumEFI 运行时服务回调期间系统可能无法响应中断与上下文切换造成延迟尖峰PREEMPT_RT 默认启用该选项若手动关闭可用启动参数efinoruntimeCONFIG_NO_HZ/CONFIG_NO_HZ_FULL禁用medium无 tick 运行会增加内核-用户态切换延迟。周期性负载如 100 µs 控制环应避开 NO_HZ保持一致的 tick计算密集型负载可考虑 NO_HZ_FULL 并隔离计算核、将 housekeeping 放到专用 CPUCONFIG_TRACING开启info建议随内核发布开启 tracing但不实际运行便于出现延迟问题时取证生产实时运行中不要激活 tracer 或 trace event开销会显著恶化延迟CONFIG_IRQSOFF_TRACER/CONFIG_PREEMPT_TRACER禁用high即使未激活也有可测量的延迟开销CONFIG_LOCKUP_DETECTOR禁用high锁死检测器创建硬中断上下文的周期性内核定时器回调即使在实时内核上会造成延迟尖峰应改用硬件看门狗CONFIG_PROVE_LOCKING禁用high证明所有内核锁的正确性开销巨大显著抬高最坏延迟CONFIG_DEBUG_ATOMIC_SLEEP允许—以可接受的延迟代价捕获常见编程错误且每次might_sleep()可能引发上下文切换、增加调度覆盖CONFIG_DEBUG_BUGVERBOSE/CONFIG_DEBUG_INFO*允许—只增大镜像无延迟影响且对 BUG 日志、崩溃转储与剖析必不可少CONFIG_DEBUG_FS允许—前提是生产运行期间不访问 debugfsCONFIG_DEBUG_KERNEL允许—元选项本身无运行时影响但注意它可能隐式启用的调试特性两个实践性建议开发期充分开调试选项长跑文档鼓励在开发与早期测试阶段长时间开启 lockdep 等调试选项运行实时负载与外设——这些负载可能触发实时内核开发中未覆盖的代码路径有助于暴露锁缺陷没有一劳永逸的配置应从系统的实时需求出发统筹硬件、内核与用户态各组件任何一处配置错误都可能引入一个在最坏时刻出现的新最大延迟对实时系统是灾难性的。启动参数细节可查阅内核参数文档Documentation/admin-guide/kernel-parameters.rst 系列。九、架构移植让 PREEMPT_RT 在你自己的体系结构上工作architecture-porting.rst 列出了使 PREEMPT_RT 可选所需的架构级前置条件全部实现后在架构 Kconfig 中 selectARCH_SUPPORTS_RTPREEMPT_RT即可被选中genirq 支持等由公共代码强制文档中省略。强制要求强制线程化中断必须 selectCONFIG_IRQ_FORCED_THREADING必须留在硬中断上下文的中断需标记IRQF_NO_THREAD典型如 clocksource 事件中断、perf 中断、级联中断控制器处理器抢占支持CONFIG_ARCH_NO_PREEMPT必须保持未选来自中断/异常处理器的调度请求必须被立即处理POSIX CPU 计时器与 KVMPOSIX CPU 计时器必须在线程上下文而非计时器中断内部到期即设置CONFIG_HAVE_POSIX_CPU_TIMERS_TASK_WORK启用 KVM 等虚拟化支持时还需CONFIG_VIRT_XFER_TO_GUEST_WORK确保进入 guest 模式前处理完 POSIX 计时器到期等挂起工作硬/软中断栈软中断在其被触发的线程上下文中执行若来自硬中断上下文则推迟到ksoftirqd。软中断处理期间从不禁用抢占。若架构提供使用独立栈的自定义__do_softirq()实现必须 selectCONFIG_HAVE_SOFTIRQ_ON_OWN_STACK且仅在CONFIG_SOFTIRQ_ON_OWN_STACK设置时启用该功能内核态 FPU/SIMDFPU/SIMD 寄存器在内核抢占时不保存使用它们的代码必须包裹在kernel_fpu_begin()/kernel_fpu_end()之间。非 RT 内核上kernel_fpu_begin()通常调用local_bh_disable()PREEMPT_RT 内核上不得调用local_bh_disable()应改用preempt_disable()因为 RT 下软中断总在线程上下文处理仅关抢占即足够。加密子系统处理请求时的walk and map内存页操作发生在该保护区间之外需要抢占开启其抢占点通常足以避免过大的调度延迟异常处理器缺页等异常处理器通常尽早开启中断再进入通用处理代码——处理缺页可能涉及可睡眠操作而 RT 下spinlock_t已变为可睡眠锁。例如无效指令异常会给用户任务发 SIGILL、调试异常发 SIGTRAP两者都需要中断与内核抢占同时开启若异常发生在用户空间尽早开中断是安全的。可选特性非强制但值得考虑高精度计时器与时钟源推荐支持CLOCK_EVT_FEAT_ONESHOT的 clockevents 设备微秒级精度通常足够Lazy preemption允许内核内针对非实时任务的调度请求推迟到任务即将返回用户空间时执行避免抢占此刻正持有睡眠锁的任务。启用CONFIG_GENERIC_IRQ_ENTRY时需定义TIF_NEED_RESCHED_LAZY位建议靠近TIF_NEED_RESCHED基于 NBCON 的串行控制台PREEMPT_RT 下所有控制台输出由专用线程处理使printk()可在原子上下文安全使用但内核崩溃若无法切换到打印线程将没有任何输出panic()等紧急输出除外。要支持这一点控制台驱动必须实现新式锁机制在console::flags中设置CON_NBCON并实现write_atomic、write_thread、device_lock、device_unlock回调。十、小结PREEMPT_RT 的核心思路可以浓缩为一点缩小关中断/关抢占的代码面让调度器随时能够切换到更高优先级任务。为此它用 rtmutex 睡眠自旋锁替代自旋自旋配合优先级继承用强制线程化中断把硬中断处理搬进进程上下文并连带改变了软中断、定时器、irq work、RCU 回调、seqlock 等一系列子系统的默认行为。驱动与内核开发者迁移到实时平台时应重点对照第六节的 API 行为差异系统集成者应依据第八节的配置表约束最坏延迟架构维护者则按第九节的清单逐项落实移植要求。这三条路径共同构成了一份完整的 PREEMPT_RT 参考。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表