0/11 已展开

LLM 分析

sched/fair LB_PROMOTE 系列:嵌入式 CFS 实时性负载均衡增强

系列概况

  • 标题[PATCH 00/10] sched/fair: A series of load balance patches to improve real-time performance of CFS tasks
  • 作者:Xin Zhao <jackzxcui1989@163.com>
  • 版本:v1(首版,无 [vN] 前缀,从邮件时间可推断)
  • 规模:10 个补丁 + 1 封 cover letter,共 11 封邮件
  • 修改文件kernel/sched/core.ckernel/sched/fair.ckernel/sched/features.hkernel/sched/sched.h
  • 代码统计:4 files changed, 224 insertions(+), 25 deletions(-)
  • Message-ID20260815110257.124354-1-jackzxcui1989@163.com(cover letter)
  • 完整性:本批次 11 封邮件全部为 patch,未见 review/reply,尚处于社区初次提交阶段

补丁目的

嵌入式平台(典型 CONFIG_HZ_250)实测发现大量"不合理 CPU idle"事件——CPU 进入 idle 后仍有可运行任务(不受 cgroup 限制)未及时被调度上来,导致延迟超过 2.5ms;超过 4ms 在实时系统中会引发性能尖刺。

本系列引入新的 SCHED_FEAT 开关 LB_PROMOTE(默认关闭),主动提升 CFS 负载均衡积极性,从 wake-up CPU 选择、newly idle 兜底、CFS 被抢占迁移三个场景入手,把"不合理 idle"事件彻底压制到 0,并完全消除 ≥4ms 的事件。

旧流程的问题

  1. rd overload 误清零(PATCH 01):update_sg_lb_stats 仅遍历 env->cpus 内的 sched_group;当 env->cpus != rd->online 时会让 set_rd_overloaded 误判为非 overload,sched_balance_newidle 因此过早返回。
  2. 重复检查 busiest_cpu(PATCH 02):cpu_active(busiest_cpu) 已经覆盖了"CPU 下线"语义,再加 busiest_cpu != smp_processor_id() 是冗余。
  3. active_balance 清零太早(PATCH 03):在 attach_one_task() 之前清零,可能让其他 CPU 看到 active_balance == 0 后又重复发起 stop task。
  4. select_task_rq_fair 太复杂(PATCH 05):默认开启的 SD_WAKE_AFFINE 让 wake-up 走快路径,遗漏 sd_llc 内的远端 idle CPU,嵌入式 4-CPU 场景下尤为明显。
  5. CFS 被 RT 抢占无迁移(PATCH 07):CFS 处于不支持 PI 的临界区(rwlock/rwsem)时被 RT 抢占,调度器不主动迁走。
  6. newly idle 过早退出(PATCH 08):avg_idle 较短时直接 goto out,错失真正的迁移窗口。
  7. more_balance 无差别(PATCH 09):newly idle 且 dst_rq 上已有任务等待时仍跳进 more_balance,白耗 CPU。
  8. 找不到迁移任务就 out(PATCH 10):!ld_moved && !active_balance 或 source group/source rq 查找失败时直接 out_balanced,没尝试 simple_find。

新流程

开启 LB_PROMOTE 后的整体改造:

  • select_task_rq 走精简版 select_task_rq_fair_thin(),优先 sd_llc 内的 idle CPU
  • sched_balance_newidle 不再因 avg_idle 过短而退出
  • sched_balance_rq 新增 simple_find 兜底,多分支失败时统一回退重新尝试
  • CFS 被 RT 抢占后通过 preempt_active_balance() 主动迁移到其他 idle CPU
  • active_load_balance_cpu_stop() 重构为通用 src->dst 迁移函数
                      +---------------------------+
                      |       __schedule()        |
                      |   triggered on RT preempt  |
                      +-------------+-------------+
                                    |
                          LB_PROMOTE && preempt?
                                    | yes
                                    v
                      +---------------------------+
                      | preempt_active_balance     |
                      | find idle cpu in sd_llc    |
                      +-------------+-------------+
                                    |
                                    v
                      +---------------------------+
                      |  queue_preempt_alb_cb      |
                      |  defer via balance_cb      |
                      +-------------+-------------+
                                    |
                                    v
                      +---------------------------+
                      |  trigger_preempt_alb       |
                      |  stop_one_cpu_nowait(dst)  |
                      +-------------+-------------+
                                    |
                                    v
                      +---------------------------+
                      | active_load_balance_stop   |
                      |  generic src -> dst move   |
                      +---------------------------+
  sched_balance_rq() entry (newly idle, LB_PROMOTE)
       |
       +-------+----------+----------+---------------+
       |       |          |          |               |
       v       v          v          v               v
  simple_find  find_src_group  find_src_rq  can_migrate_task
  (retry)      (fail->retry)   (fail->retry) (fail->retry)
       |       |          |          |               |
       +-------+----------+-----+----+
                                   |
                                   v
                          check_redo (PATCH 10)
                                   |
                                   v
                       detach_one_task / out_balanced

Patch 概览

#标题(精简)类型关键依赖
01Do not set_rd_overloaded() if rd->online != env->cpusbugfix独立前置
02Remove duplicate check for busiest_cpu in active_load_balance_cpu_stop()cleanup独立前置
03Clear active_balance at the end of active_load_balance_cpu_stop()refactor依赖 PATCH 02
04Add LB_PROMOTE feature to enhance real-time performance of fair tasksfeature 引入独立
05Introduce select_task_rq_fair_thin()feature依赖 PATCH 04
06Modify active_load_balance_cpu_stop() to accommodate more scenariosrefactor依赖 PATCH 02/03
07Trigger active balance if a CFS task is preemptedfeature依赖 PATCH 04 + PATCH 06
08Do not check avg_idle to prematurely exit newly idlefeature依赖 PATCH 04
09Not goto more_balance if newly idle and has pending taskfeature依赖 PATCH 04
10Strive to find a task to migrate if newly idlefeature(核心)依赖 PATCH 04/09

关键实现

PATCH 04:引入 LB_PROMOTE 开关

/* kernel/sched/features.h */
SCHED_FEAT(LB_PROMOTE, false)

默认关闭。注释同时给出了两组对比测试数据:5 组 fillback 场景下 on/off 的"不合理 idle"事件数(on 时全 0),以及 15 组 25 分钟对比的 end-to-end latency 与 sys% 数值。

PATCH 05:select_task_rq_fair_thin()

精简版 wake-up CPU 选择(按优先级):

static int select_task_rq_fair_thin(struct task_struct *p,
                                    int prev_cpu, int wake_flags)
{
    if (likely(available_idle_cpu(prev_cpu)))
        return prev_cpu;

    recent_used_cpu = p->recent_used_cpu;
    if (recent_used_cpu != prev_cpu &&
        available_idle_cpu(recent_used_cpu))
        return recent_used_cpu;

    /* search first idle CPU in sd_llc INTERSECT cpus_ptr */
    /* fallback to any idle CPU in cpus_ptr */
    /* ultimate fallback: prev_cpu */
}

作者实测:在 18-CPU / 2.1GHz、sd_llc 4+4+4+2 的平台上,select_task_rq_fair_thin() 比原版快 25%,节省 22ms/10s。

PATCH 07:preempt_active_balance()

CFS 任务被 RT 抢占时主动迁移:

void preempt_active_balance(struct task_struct *prev)
{
    if (prev->sched_class != &fair_sched_class) return;
    if (prev->migration_disabled) return;

    /* 1. prefer recent_used_cpu if idle */
    /* 2. else scan sd_llc INTERSECT cpus_ptr for idle cpu */

    /* list_move_tail to put prev at head so detach_one_task picks it */
    list_move_tail(&prev->se.group_node, &this_rq->cfs_tasks);
    queue_preempt_alb_callback(this_rq, idle_cpu);
}

迁移触发被延迟到 balance_callback,避免在持锁路径上引入新的锁依赖。

PATCH 10:simple_find 兜底

out_balanced branch fails -> simple_find
  |-- require CPU_NEWLY_IDLE and dst_rq empty
  |-- env.migration_type = migrate_task, env.imbalance = 1
  |-- for_each_cpu_andnot traverse cpus, find busiest nr_running>1
        `-- break and jump begin_balance for normal migration

check_redo 处若仍未迁移成功,再次跳回 simple_find 重试。

类比

把 CPU 想象成商场里的收银台:普通负载均衡是"顾客按规则排队,每个台只看自己队伍长度,超阈值才叫人过来帮忙";LB_PROMOTE 是给每个空闲收银台戴上"主动揽客"耳麦——空出来就朝隔壁队尾吆喝"这边没人";隔壁没人理(simple_find 也找不到)就再多看几家(多轮 retry);CFS 顾客被 RT 插队(preempt),就主动把这个 CFS 顾客换到隔壁空台结账,避免他在 RT 队伍里一直等导致 rwlock 卡住整队人。

Highlight:风险与注意点

  1. sys% 上升LB_PROMOTE 用 idle 时间换调度及时性;PATCH 04 注释明确指出开启后 sys% 上升,桌面/服务器需评估性价比。
  2. task_hot() 未变:PATCH 07 注释特意强调,避免迁移频率暴增;但同时也意味着短 idle 窗口内的 hot task 不会被迁走,可能错过某些抢占迁移时机。
  3. PATCH 03 行为变更active_balance 清零移到函数末尾,窗口更宽;并发下需关注是否引发 spurious stop task。
  4. PATCH 06 重命名 busiest->src:破坏函数局部语义但不破坏 ABI,系列内部已完成对齐。
  5. PATCH 07 新锁路径trigger_preempt_albraw_spin_rq_unlock(rq) + raw_spin_rq_lock_irqsave(dst_rq) 涉及两把 rq 锁,需关注死锁与中断关闭时长。
  6. PATCH 09 LBF_DST_PINNED 路径:作者说明 can_migrate_task() 在 newly idle 路径下不会置位 LBF_DST_PINNED,但维护者需确认所有未来调用路径都满足这一不变量。
  7. PATCH 10 多次 check_redo:开启 LB_PROMOTE 后 newly idle 可能多次重试 simple_find,是否引入长时间关中断或关抢占窗口,需要 lockdep 与 perf benchmark 验证。
  8. LB_PROMOTESCHED_FEAT 体系耦合:未来若主线对 sched_feat 做 batch sysctl,需确保 LB_PROMOTE 不会与 SIS_UTILWAKE_AFFINE 产生隐藏冲突。

版本变化

本系列为首版(v1),暂无前序版本可对比。后续若有 v2,需重点观察:维护者对 task_hot() 是否调整、是否把 select_task_rq_fair_thin() 的扫描顺序开放为参数、preempt_active_balance() 是否引入 cpus_read_lock() 替代 RCU。

一句话总结

LB_PROMOTE 是一组面向嵌入式/CFS 实时性的负载均衡增强系列:用 sys% 换取"不合理 CPU idle"事件归零,并完全消除 ≥4ms 调度延迟,核心思路是在 wake-up 选择、newly idle 兜底和 CFS preempt 迁移三处主动出击。