Linux 实时性优化:PREEMPT_RT 补丁、线程优先级、内存锁、减少调度抖动
Linux 实时性优化PREEMPT_RT 补丁、线程优先级、内存锁、减少调度抖动标准Linux的调度抖动能把你精心设计的PID控制周期搅成一锅粥这篇教你把软实时榨到极限。一、为什么工控需要实时性工控系统对时间有严格要求。举几个典型场景定时采集每 10ms 读取一次传感器数据延迟不能超 1ms否则数据丢帧精确控制伺服电机控制周期 1ms波动超过 50μs 就可能引起震动协议响应Modbus 从站要在规定时间内应答超时则主站报通信故障数据完整性高速采集场景如振动监测一次调度抖动就丢一包数据标准 Linux 内核设计目标是吞吐量而非实时性——它会让高优先级线程等待低优先级线程完成某些操作比如内核系统调用中的临界区导致不可预测的延迟。这种延迟称为调度抖动scheduling jitter。二、Linux 调度模型对比2.1 标准内核标准内核的抢占模型有几种配置配置说明最大延迟CONFIG_PREEMPT_NONE无抢占内核态不抢占数十msCONFIG_PREEMPT_VOLUNTARY自愿抢占内核代码中主动检查几msCONFIG_PREEMPT内核态可抢占除持锁临界区亚ms级CONFIG_PREEMPT_RT完全抢占几乎无不可抢占区微秒级标准内核即使用了PREEMPT仍然存在大量不可抢占的临界区自旋锁保护的代码段实时性不够。2.2 PREEMPT_RT 内核PREEMPT_RT 补丁的核心改造自旋锁转为 rt_mutex允许在锁保护的临界区中被抢占中断线程化硬中断处理变为内核线程可被高优先级线程抢占高精度定时器替代传统低精度时钟支持微秒级定时优先级继承解决优先级反转问题改造后内核中几乎不再存在不可抢占的代码路径最大调度延迟可控制在几十微秒以内。三、PREEMPT_RT 补丁安装与内核配置3.1 获取补丁# 以Linux 5.15为例wgethttps://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.tar.xzwgethttps://cdn.kernel.org/pub/linux/kernel/projects/rt/5.15/patch-5.15-rt59.patch.xztarxf linux-5.15.tar.xzcdlinux-5.15 xzcat../patch-5.15-rt59.patch.xz|patch-p1注意补丁版本必须和内核版本严格匹配否则打不上。3.2 内核配置makemenuconfig关键配置项General setup --- Preemption Model (Fully Preemptible Kernel (Real-Time)) [*] Fully Preemptible Kernel (Real-Time) # 选择PREEMPT_RT CPU/Task time and stats accounting --- [*] Pressure stall information tracking # 可选监控压力 Kernel hacking --- [*] Debug preemptible kernel # 调试可关影响性能编译安装make-j$(nproc)sudomakemodules_installsudomakeinstall3.3 验证uname-a# 输出应包含 PREEMPT RT# 检查实时内核标志grepPREEMPT /proc/version四、线程优先级设置4.1 调度策略Linux 实时调度策略有两种策略说明SCHED_FIFO先进先出同优先级不轮转除非主动让出否则一直运行SCHED_RR时间片轮转同优先级按时间片轮流执行工控中SCHED_FIFO 用得最多——实时线程不需要时间片轮转高优先级线程就该独占 CPU 直到主动让出。4.2 设置优先级#includesched.h#includestdio.hintset_realtime_priority(intpriority){structsched_paramparam;param.sched_prioritypriority;// SCHED_FIFO优先级范围1~99if(sched_setscheduler(0,SCHED_FIFO,param)!0){perror(sched_setscheduler failed);return-1;}return0;}// 使用设置优先级为80set_realtime_priority(80);优先级分配建议90~99最关键的实时控制线程如运动控制80~89数据采集线程70~79通信线程1~69辅助实时任务4.3 限制实时线程CPU占用# 允许用户设置实时优先级非root用户需要sudosh-cecho username rtprio 99 /etc/security/limits.confsudosh-cecho username memlock unlimited /etc/security/limits.conf五、CPU 亲和性绑定5.1 为什么需要绑核多核系统上线程可能在不同核心间迁移导致缓存失效cache miss增加延迟。绑定到固定核心可以消除这个开销。5.2 设置亲和性#define_GNU_SOURCE#includesched.hintset_cpu_affinity(intcpu){cpu_set_tcpuset;CPU_ZERO(cpuset);CPU_SET(cpu,cpuset);// 绑定到指定核心if(sched_setaffinity(0,sizeof(cpuset),cpuset)!0){perror(sched_setaffinity failed);return-1;}return0;}// 将实时线程绑定到CPU1CPU0留给系统set_cpu_affinity(1);5.3 内核隔离isolcpus在内核启动参数中隔离 CPU 核心避免普通进程调度到该核心# /etc/default/grubGRUB_CMDLINE_LINUXisolcpus1 nohz_full1 rcu_nocbs1sudoupdate-grubsudorebootisolcpus1从调度器中移除CPU1普通进程不会分配到该核心nohz_full1CPU1上无固定时钟中断ticklessrcu_nocbs1RCU回调不在CPU1上执行六、内存锁机制6.1 为什么要锁内存Linux 的虚拟内存机制会在内存不足时把不常用的页面换出到 swap 分区。如果实时线程的代码或数据被换出访问时会触发缺页中断产生不可预测的延迟。6.2 mlockall 锁定内存#includesys/mman.h// 锁定当前进程所有内存页包括未来分配的if(mlockall(MCL_CURRENT|MCL_FUTURE)!0){perror(mlockall failed);return-1;}MCL_CURRENT锁定当前已映射的所有页面MCL_FUTURE锁定未来分配的所有页面6.3 预分配栈空间#includepthread.hvoid*rt_thread(void*arg){// 预触碰栈空间确保栈页面已分配volatilecharstack_mem[256*1024];// 256KB栈for(size_ti0;isizeof(stack_mem);i4096){stack_mem[i]0;// 每页写一次触发缺页}// 实时任务循环while(1){// 控制逻辑...sched_yield();// 或使用定时等待}returnNULL;}intmain(){mlockall(MCL_CURRENT|MCL_FUTURE);pthread_ttid;pthread_attr_tattr;pthread_attr_init(attr);// 设置栈大小size_tstacksize512*1024;pthread_attr_setstacksize(attr,stacksize);pthread_create(tid,attr,rt_thread,NULL);pthread_join(tid,NULL);return0;}七、减少调度抖动实践7.1 关闭不必要的守护进程# 查看运行的服务systemctl list-units--typeservice--staterunning# 关闭典型的不必要服务sudosystemctl stop bluetoothsudosystemctl stop cupssudosystemctl stop avahi-daemonsudosystemctl stop NetworkManager# 如果不用桌面环境7.2 禁用 swap# 临时禁用sudoswapoff-a# 永久禁用注释/etc/fstab中的swap行sudosed-i/swap/s/^/#//etc/fstab7.3 禁用节能模式CPU 频率缩放和 C-states 都会增加延迟# 设置为performance模式sudocpupower frequency-set-gperformance# 禁用C-states内核参数# 在GRUB中添加processor.max_cstate0 intel_idle.max_cstate0八、实时性能测试cyclictest8.1 安装与运行sudoaptinstallrt-tests# 测试优先级80间隔1000μs持续60秒绑核1sudocyclictest-p80-i1000-d0-t1-a1-D60m8.2 结果解读输出示例T: 0 ( 1234) P:80 I:1000 C: 3600000 Min: 3 Act: 7 Avg: 8 Max: 47字段含义T线程编号P优先级I间隔(μs)C执行次数Min最小延迟(μs)Act实际延迟(μs)Avg平均延迟(μs)Max最大延迟(μs)评判标准最大延迟评价 50μs优秀满足绝大多数工控需求50~100μs良好满足大部分实时控制100~500μs一般可用于非严格实时场景 500μs差需要进一步优化或换硬件标准内核跑 cyclictest 最大延迟通常在几ms到几十msPREEMPT_RT 内核可压到几十微秒。九、实时优化配置清单优化项操作效果内核安装PREEMPT_RT补丁消除内核不可抢占区调度策略SCHED_FIFO 高优先级确保实时线程优先调度CPU亲和性sched_setaffinity绑核消除核间迁移开销核隔离isolcpus nohz_full减少核心上的干扰内存锁mlockall防止缺页中断栈预分配预触碰栈页面避免运行中缺页禁用swapswapoff杜绝换页延迟CPU性能模式performance governor消除频率切换延迟禁用C-statesmax_cstate0消除CPU唤醒延迟关闭守护进程停用不必要服务减少后台干扰十、注意事项实时线程不能占满 CPU。这是最常见的误区。PREEMPT_RT 是软实时不是硬实时。如果实时线程的 CPU 占用率持续接近 100%会导致系统守护进程饿死如 watchdog 触发系统重启网络栈无法收包导致丢包驱动中断处理被延迟经验法则实时线程的 CPU 占用率不要超过 70%~80%留出余量给系统线程。如果计算量大考虑用专用核心隔离运行实时线程将非实时工作如数据上传、日志记录放到普通优先级线程多核系统上实时线程绑一个核系统线程留在其他核// 正确做法实时循环中有让出CPU的机制while(running){// 实时控制逻辑耗时500μsdo_control();// 精确等待到下一个周期期间CPU空闲clock_nanosleep(CLOCK_MONOTONIC,TIMER_ABSTIME,next_wakeup,NULL);next_wakeup.tv_nsecPERIOD_NS;// 处理纳秒溢出...}实时性优化的核心思路减少一切不确定性——不确定的中断、不确定的换页、不确定的调度、不确定的频率切换通通干掉。