4、负载均衡触发时机:周期性负载均衡(tick)、空闲时负载均衡(newidle)、唤醒时负载均衡(wake)、fork/exec时负载均衡

Linux 调度器的负载均衡并非持续运行,而是在特定事件或时间点被触发。理解这四种触发时机,是调试跨 CPU 域迁移与负载均衡协同工作的基础。每种触发时机对应不同的系统状态和性能目标,错误的参数配置可能导致负载失衡或额外的迁移开销。

4.1 四种触发时机总览

下表从触发条件、主要目标、典型开销和适用场景四个维度,对比了四种负载均衡触发时机:

触发时机 触发条件 主要目标 典型开销 适用场景
周期性负载均衡(tick) 每个调度 tick(通常 1ms~4ms) 长期全局负载均衡 中等,随 CPU 数量增加 通用服务器、长时间运行任务
空闲时负载均衡(newidle) CPU 即将进入空闲状态 快速拉取任务,避免 CPU 空转 低,但可能延迟空闲 交互式系统、延迟敏感负载
唤醒时负载均衡(wake) 任务从睡眠状态被唤醒 减少跨域唤醒延迟 低,仅检查目标 CPU 短任务、IPC 密集型应用
fork/exec 时负载均衡 新进程创建或执行新程序 初始放置,避免后续迁移 低,仅一次放置决策 批处理、容器启动、Web 服务器

4.2 周期性负载均衡(tick)

周期性负载均衡是调度器最基础的均衡机制。每次时钟 tick 中断时,当前 CPU 会检查是否需要发起负载均衡。该机制由 sched_tick() 函数驱动,通过 load_balance() 执行。

关键内核参数:
  • kernel.sched_migration_cost_ns:任务迁移的“成本”时间窗口,默认 500000 ns。若任务运行时间小于此值,则视为“热缓存”任务,避免迁移。
  • kernel.sched_nr_migrate:一次均衡操作最多迁移的任务数,默认 32。

周期性均衡的触发频率由 sysctl_sched_interval 控制,默认值为 1ms(HZ=1000 时)。每次 tick 仅检查当前 CPU 所属调度域是否需要均衡,不会全局扫描所有域。

周期性负载均衡触发流程 时钟 tick 中断 sched_tick() 调用 检查当前 CPU 所属调度域 load_balance() 执行迁移

4.3 空闲时负载均衡(newidle)

当 CPU 执行完所有 runqueue 中的任务,即将进入 idle 状态时,会触发 newidle 均衡。这是降低 CPU 空转率、提高系统吞吐量的关键机制。其核心函数为 newidle_balance()。

性能提示: newidle 均衡是“最积极”的均衡方式,因为它试图在 CPU 空闲前从其他 CPU 拉取任务。但过度拉取可能导致任务在多个 CPU 间“乒乓”迁移,增加缓存缺失。参数 kernel.sched_migration_cost_ns 在此处同样生效。

newidle 均衡的搜索范围受 SD_BALANCE_NEWIDLE 标志控制。如果某个调度域未设置此标志,则 newidle 均衡不会扫描该域。

空闲时负载均衡决策路径 CPU runqueue 为空 newidle_balance() 扫描调度域层级 拉取任务到本地 CPU 进入 idle

4.4 唤醒时负载均衡(wake)

当任务从睡眠状态被唤醒时,调度器会执行 wake 均衡,决定将任务放置到哪个 CPU 上。这由 select_task_rq_fair() 函数处理,并受 SD_BALANCE_WAKE 标志控制。

wake 均衡的核心目标是减少跨域唤醒延迟。如果唤醒源 CPU 和目标 CPU 不在同一个调度域,调度器会评估是否将任务迁移到唤醒源所在的 CPU 或域内其他空闲 CPU。

调试注意: 如果 /proc/sys/kernel/sched_wakeup_granularity_ns 设置过大,wake 均衡可能无法及时将任务迁移到更优的 CPU,导致唤醒延迟增加。该参数默认值为 4ms(HZ=250 时),建议在低延迟场景下调小。

wake 均衡的决策逻辑可概括为:

  1. 检查缓存亲和性:如果任务上次运行的 CPU 仍在同一域且负载不高,优先放置。
  2. 评估域负载:比较唤醒源 CPU 所在域与目标 CPU 所在域的负载差异。
  3. 执行迁移:若跨域迁移收益大于成本,则通过 migrate_task() 移动任务。

4.5 fork/exec 时负载均衡

当新进程通过 fork() 创建或通过 exec() 加载新程序时,调度器会执行 fork/exec 均衡,为新任务选择初始 CPU。这是任务生命周期中第一次负载均衡决策,直接影响后续的缓存热度和迁移开销。

该机制由 sched_fork() 和 sched_exec() 函数实现,受 SD_BALANCE_FORK 和 SD_BALANCE_EXEC 标志控制。

事件 触发函数 均衡策略 典型参数影响
fork sched_fork() 选择负载最低的 CPU,尽量与父进程同域 kernel.sched_child_runs_first(子进程优先运行)
exec sched_exec() 重新评估 CPU 负载,可能迁移到更空闲的 CPU kernel.sched_autogroup_enabled(自动分组影响放置)
实践案例: 在 Web 服务器场景中,频繁 fork 子进程处理请求。如果 SD_BALANCE_FORK 未正确配置,所有子进程可能堆积在同一 CPU 上,导致负载不均。建议通过 taskset 或 cgroup cpuset 配合调度域参数进行调优。
fork/exec 负载均衡决策流程 fork() 系统调用 sched_fork() 选择初始 CPU exec() 系统调用 重新评估放置 任务开始运行

4.6 四种触发时机的协同关系

在实际运行中,四种触发时机并非独立工作,而是形成一套完整的负载均衡体系:

  • fork/exec 均衡 负责初始放置,减少后续迁移需求。
  • wake 均衡 在任务唤醒时微调放置位置,适应瞬时负载变化。
  • tick 均衡 作为后台守护机制,定期纠正长期负载偏差。
  • newidle 均衡 作为最后手段,在 CPU 空闲时主动拉取任务,最大化利用率。
调优建议: 在调试跨 CPU 域迁移时,建议先通过 perf stat -e sched:sched_migrate_task 统计迁移次数,判断哪种触发时机导致的迁移最多。然后针对性地调整对应参数,例如减少 tick 均衡频率(增大 sysctl_sched_interval)或关闭某些域的 newidle 均衡。