Linux RT 调度器的负载均衡:Push/Pull 模型的实时性优化

张开发
2026/4/21 22:13:46 15 分钟阅读

分享文章

Linux RT 调度器的负载均衡:Push/Pull 模型的实时性优化
前言在工业控制、自动驾驶、机器人、5G 基站等硬实时场景中Linux RT 调度器承担着毫秒级乃至微秒级的任务响应保障。SMP 多核架构下实时任务若集中在个别 CPU 核心会直接导致优先级反转、调度延迟飙升破坏系统确定性。CFS 调度器依赖周期性负载均衡以吞吐为目标而 RT 调度器必须以实时性优先不能为了均衡牺牲高优先级任务的抢占时机。为此内核设计了Push 过载推送 Pull 空闲拉取的专用均衡模型过载 CPU 主动推送多余实时任务空闲 CPU 主动拉取高优先级任务在不破坏优先级秩序的前提下完成负载分散。本文从内核源码、调试工具、实验验证三个维度完整拆解 RT Push/Pull 机制提供可复现的实验代码与调试命令适合内核开发、嵌入式实时工程师做深度调研与论文写作。一、核心概念1.1 RT 调度基础SCHED_FIFO先进先出高优先级绝对抢占同优先级不主动让出 CPU。SCHED_RR时间片轮转同优先级按时间片轮换仍优先于所有 CFS 任务。实时优先级1~99数值越大优先级越高CFS 为 120。1.2 RT 负载均衡核心术语rt_rq每个 CPU 独立的实时运行队列按优先级组织。rt_nr_running当前 CPU 上可运行的 RT 任务数。Overload过载单 CPU 上rt_nr_running 1存在任务等待 CPU。Push过载 CPU 将次高优先级可迁移 RT 任务推送到其他空闲 / 低负载 CPU。Pull空闲或低负载 CPU 主动从过载 CPU 拉取更高优先级 RT 任务。迁移掩码任务 CPU 亲和性允许迁移的核心集合。调度域 / 调度组多核拓扑抽象决定均衡搜索范围。1.3 Push/Pull 设计原则优先级绝对优先只迁移同优先级或低优先级任务绝不干扰最高优先级执行。触发即时性非周期在任务入队、唤醒、调度点触发。最小开销只迁移必要任务避免频繁 IPI 与缓存颠簸。亲和性尊重严格遵循cpuset与task_cpu约束。二、环境准备2.1 硬件与系统CPUx86_64≥4 核SMP 环境必需Kernel5.10.190-rtPREEMPT_RT 补丁或主线 RT 调度器工具链gcc、make、trace-cmd、kernel-debuginfo、perf2.2 内核配置# 必需配置 CONFIG_SMPy CONFIG_PREEMPT_RTy # 实时抢占 CONFIG_SCHED_DEBUGy CONFIG_SCHEDSTATSy CONFIG_RT_GROUP_SCHEDn # 实验关闭组调度简化观察 CONFIG_CPUSETSy2.3 依赖安装yum install -y trace-cmd perf kernel-debuginfo gcc make echo kernel.sched_rt_runtime_us -1 /etc/sysctl.conf sysctl -p说明关闭 RT 运行时限制避免实验中任务被强制限流。2.4 实验目录结构rt_balance/ ├── rt_task.c # 实时任务测试程序 ├── Makefile └── debug.sh # 调试脚本三、典型应用场景300 字在车载自动驾驶域控制器中MCUSoC 混合架构下Linux 主核心运行相机感知、激光雷达点云处理、车辆控制等 RT 任务。相机任务优先级 80、激光雷达任务优先级 85、控制指令任务优先级 90若集中在 CPU0会导致高优先级控制任务等待引发车辆控制延迟超标。RT Push 机制会将 CPU0 上的相机、雷达任务推送到 CPU1/2/3当 CPU1 进入空闲Pull 机制会主动拉取其他核心的中等优先级 RT 任务保证所有高优先级任务独占核心端到端延迟稳定在 5ms 内。在 5G 基站 BBU 信号处理中Push/Pull 可保证物理层下行调度任务不被阻塞满足毫秒级空口时延要求。四、内核源码深度解析4.1 RT 过载标记// kernel/sched/rt.c static inline void rt_set_overload(struct rq *rq) { if (!rq-online) return; cpumask_set_cpu(rq-cpu, rq-rd-rto_mask); } static inline void rt_clear_overload(struct rq *rq) { if (!cpumask_test_cpu(rq-cpu, rq-rd-rto_mask)) return; if (rq-rt.rt_nr_running 1) cpumask_clear_cpu(rq-cpu, rq-rd-rto_mask); }作用当rt_nr_running从 1 变为 2 时标记过载从 2 变为 1 时清除过载。过载掩码rto_mask是 Push/Pull 的搜索依据。4.2 Push 机制核心实现// kernel/sched/rt.c static int push_rt_task(struct rq *this_rq) { struct rq *busiest; struct task_struct *p; struct sched_rt_entity *rt_se; int cpu, ret; // 1. 仅过载CPU执行Push if (this_rq-rt.rt_nr_running 1) return 0; // 2. 取出次高优先级任务最高优先级正在运行不可迁移 rt_se pick_next_highest_rt_task(this_rq); if (!rt_se) return 0; p rt_task_of(rt_se); // 3. 寻找目标CPU空闲、允许迁移、当前任务优先级更低 cpu find_lowest_rq(this_rq, p, SCHED_PUSH); if (cpu -1) return 0; busiest cpu_rq(cpu); // 4. 迁移任务 ret move_task_same_group(this_rq, busiest, p); if (ret) return 1; // 5. 触发目标CPU重新调度 resched_curr(busiest); return 1; }触发点enqueue_task_rt任务入队task_woken_rt任务唤醒schedule调度点4.3 Pull 机制核心实现// kernel/sched/rt.c static void pull_rt_task(struct rq *this_rq) { struct rq *busiest; struct task_struct *p; int cpu; // 1. 仅空闲/轻载CPU执行Pull if (this_rq-rt.rt_nr_running) return; // 2. 遍历过载CPU掩码 for_each_cpu(cpu, this_rq-rd-rto_mask) { if (cpu this_rq-cpu) continue; busiest cpu_rq(cpu); // 3. 取出可迁移的最高优先级任务 p pick_next_pushable_task(busiest); if (!p) continue; // 4. 迁移到本地CPU if (move_task_same_group(busiest, this_rq, p)) { resched_curr(this_rq); return; } } }触发点idle 线程进入任务完成退出 CPU周期性调度 tick低频次五、实战实验与代码5.1 RT 测试任务代码// rt_task.c #define _GNU_SOURCE #include stdio.h #include stdlib.h #include pthread.h #include sched.h #include unistd.h #include sys/syscall.h #define RT_PRIO 80 #define CPU_MASK 0x01 // 初始绑定CPU0 void *rt_worker(void *arg) { int idx *(int *)arg; struct sched_param param {.sched_priority RT_PRIO}; cpu_set_t cpuset; // 设置实时调度策略 pthread_setschedparam(pthread_self(), SCHED_FIFO, param); // 绑定CPU0 CPU_ZERO(cpuset); CPU_SET(0, cpuset); pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), cpuset); printf(RT任务%d: PID%ld 绑定CPU0 优先级%d\n, idx, syscall(SYS_gettid), RT_PRIO); while (1) { usleep(1000); // 模拟周期性工作 } return NULL; } int main() { pthread_t tid1, tid2, tid3; int idx1 1, idx2 2, idx3 3; pthread_create(tid1, NULL, rt_worker, idx1); pthread_create(tid2, NULL, rt_worker, idx2); pthread_create(tid3, NULL, rt_worker, idx3); pause(); return 0; }5.2 MakefileCFLAGS -pthread -O2 all: rt_task clean: rm -f rt_task5.3 实验步骤编译运行make ./rt_task 观察 CPU0 过载watch -n1 ps -eLo pid,tid,class,pri,cpuid,cmd | grep -E RT|SCHED_FIFO抓取 Push/Pull 事件trace-cmd record -e sched:sched_process_move -e sched:sched_switch -e sched:rt_overload trace-cmd report查看负载均衡状态cat /proc/sched_debug | grep -A 20 cpu#0 | grep rt_5.4 实验预期3 个 RT 任务初始绑定 CPU0 → CPU0 标记过载。Push 触发将任务 2、3 推送到 CPU1/2。Pull 触发CPU1/2 空闲时主动拉取任务。最终每个 CPU 运行≤1 个 RT 任务均衡完成。六、调试命令工具箱6.1 查看 RT 运行队列# 查看每个CPU的rt_nr_running cat /proc/schedstat | awk {print CPUNR-1: rt_running$8} # 查看过载掩码 cat /sys/kernel/debug/sched/rt_overload_mask6.2 perf 跟踪 Push/Pullperf probe -x /lib/modules/$(uname -r)/build/kernel/sched/rt.o push_rt_task perf probe -x /lib/modules/$(uname -r)/build/kernel/sched/rt.o pull_rt_task perf record -e probe:rt:* -a sleep 10 perf script6.3 强制触发均衡# 重新绑定任务到其他CPU触发Push taskset -cp 1 线程PID七、常见问题与解决方案7.1 Push 不触发任务堆积原因任务cpuset限制无可用迁移目标 CPU。解决检查/proc/PID/status中的Cpus_allowed。7.2 Pull 不执行CPU 空闲但不接收任务原因目标 CPU 所在调度域未包含过载 CPU。解决检查调度域拓扑/proc/schedstat。7.3 迁移失败任务仍在原 CPU原因任务被mlock、内存亲和性绑定、中断亲和性冲突。解决关闭内存绑定调整中断亲和性。7.4 实时任务延迟增大原因Push/Pull 产生 IPI 与缓存失效。解决对核心 RT 任务设置CPUSET独占核心禁止均衡。八、最佳实践与工业调优高优先级任务独占核心对 90~99 优先级任务使用 cpuset 绑定独占 CPU关闭均衡。mkdir /cpuset mount -t cpuset cpuset /cpuset echo 1 /cpuset/cpu_exclusive echo 3 /cpuset/cpus echo $PID /cpuset/tasks中等优先级任务启用均衡80 以下优先级允许 Push/Pull提升资源利用率。减少均衡范围将 RT 任务限制在同一 LLC 共享调度域降低迁移开销。关闭不必要的 Pull 触发在硬实时系统可通过内核参数降低 pull 频率减少干扰。监控 rt_overload长期运行系统中rto_mask持续置位说明负载设计不合理需拆分任务。九、总结RT 调度器的 Push/Pull 模型是 Linux 实时性与多核均衡的折中核心Push过载主动疏散避免高优先级任务饥饿。Pull空闲主动承接提升 CPU 利用率。全程遵守优先级秩序保证硬实时系统的确定性。本文提供的源码分析、实验代码、调试命令可直接用于论文实验、性能报告与工业调优。在自动驾驶、工业控制、5G 基站等场景中合理配置 Push/Pull 策略可在保证微秒级调度延迟的同时充分利用多核资源。建议在实际项目中对最高优先级任务采用独占核心 禁止均衡对中等优先级任务开放均衡实现实时性与资源效率的平衡。

更多文章