sched discussion
[PATCH 10/10] sched/fair: Strive to find a task to migrate if newly idle when LB_PROMOTE
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.c、kernel/sched/fair.c、kernel/sched/features.h、kernel/sched/sched.h - 代码统计:4 files changed, 224 insertions(+), 25 deletions(-)
- Message-ID:
20260815110257.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 的事件。
旧流程的问题
- rd overload 误清零(PATCH 01):
update_sg_lb_stats仅遍历env->cpus内的 sched_group;当env->cpus != rd->online时会让set_rd_overloaded误判为非 overload,sched_balance_newidle因此过早返回。 - 重复检查 busiest_cpu(PATCH 02):
cpu_active(busiest_cpu)已经覆盖了"CPU 下线"语义,再加busiest_cpu != smp_processor_id()是冗余。 - active_balance 清零太早(PATCH 03):在
attach_one_task()之前清零,可能让其他 CPU 看到active_balance == 0后又重复发起 stop task。 select_task_rq_fair太复杂(PATCH 05):默认开启的SD_WAKE_AFFINE让 wake-up 走快路径,遗漏sd_llc内的远端 idle CPU,嵌入式 4-CPU 场景下尤为明显。- CFS 被 RT 抢占无迁移(PATCH 07):CFS 处于不支持 PI 的临界区(rwlock/rwsem)时被 RT 抢占,调度器不主动迁走。
- newly idle 过早退出(PATCH 08):
avg_idle较短时直接goto out,错失真正的迁移窗口。 more_balance无差别(PATCH 09):newly idle 且 dst_rq 上已有任务等待时仍跳进more_balance,白耗 CPU。- 找不到迁移任务就 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 CPUsched_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 概览
| # | 标题(精简) | 类型 | 关键依赖 |
|---|---|---|---|
| 01 | Do not set_rd_overloaded() if rd->online != env->cpus | bugfix | 独立前置 |
| 02 | Remove duplicate check for busiest_cpu in active_load_balance_cpu_stop() | cleanup | 独立前置 |
| 03 | Clear active_balance at the end of active_load_balance_cpu_stop() | refactor | 依赖 PATCH 02 |
| 04 | Add LB_PROMOTE feature to enhance real-time performance of fair tasks | feature 引入 | 独立 |
| 05 | Introduce select_task_rq_fair_thin() | feature | 依赖 PATCH 04 |
| 06 | Modify active_load_balance_cpu_stop() to accommodate more scenarios | refactor | 依赖 PATCH 02/03 |
| 07 | Trigger active balance if a CFS task is preempted | feature | 依赖 PATCH 04 + PATCH 06 |
| 08 | Do not check avg_idle to prematurely exit newly idle | feature | 依赖 PATCH 04 |
| 09 | Not goto more_balance if newly idle and has pending task | feature | 依赖 PATCH 04 |
| 10 | Strive to find a task to migrate if newly idle | feature(核心) | 依赖 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:风险与注意点
- sys% 上升:
LB_PROMOTE用 idle 时间换调度及时性;PATCH 04 注释明确指出开启后 sys% 上升,桌面/服务器需评估性价比。 task_hot()未变:PATCH 07 注释特意强调,避免迁移频率暴增;但同时也意味着短 idle 窗口内的 hot task 不会被迁走,可能错过某些抢占迁移时机。- PATCH 03 行为变更:
active_balance清零移到函数末尾,窗口更宽;并发下需关注是否引发 spurious stop task。 - PATCH 06 重命名 busiest->src:破坏函数局部语义但不破坏 ABI,系列内部已完成对齐。
- PATCH 07 新锁路径:
trigger_preempt_alb中raw_spin_rq_unlock(rq)+raw_spin_rq_lock_irqsave(dst_rq)涉及两把 rq 锁,需关注死锁与中断关闭时长。 - PATCH 09 LBF_DST_PINNED 路径:作者说明
can_migrate_task()在 newly idle 路径下不会置位LBF_DST_PINNED,但维护者需确认所有未来调用路径都满足这一不变量。 - PATCH 10 多次
check_redo:开启LB_PROMOTE后 newly idle 可能多次重试simple_find,是否引入长时间关中断或关抢占窗口,需要 lockdep 与 perf benchmark 验证。 LB_PROMOTE与SCHED_FEAT体系耦合:未来若主线对sched_feat做 batch sysctl,需确保LB_PROMOTE不会与SIS_UTIL、WAKE_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 迁移三处主动出击。