sched discussion
sched/fair: which tasks should nr_pref_llc_running be compared against?
LLM 分析
sched/cache:让 nr_pref_llc_running 与 h_nr_runnable 语义对齐
系列概况
- 标题:sched/fair: which tasks should nr_pref_llc_running be compared against?(衍生 patch:sched/cache: Keep nr_pref_llc_running in the runnable domain)
- 作者:Zhan Xusheng zhanxusheng@xiaomi.com;维护者参与:Tim Chen tim.c.chen@linux.intel.com;Chen Yu yu.c.chen@intel.com
- 版本:单 patch,迭代讨论三轮(问诊 → v1 dequeue 守卫 → v2 enqueue + delayed helper → v3 helper 收敛)
- 规模:邮件 thread 共 8 封;代码侧
kernel/sched/fair.c净增约 +44 行;本帖只讨论代码语义,不存在完整 diffstat - 修改文件:仅
kernel/sched/fair.c(account_llc_enqueue、account_llc_dequeue、account_llc_delayed、account_llc_requeue_delayed等 helper,以及alb_break_llc调用点保持不变) - 代码统计:补丁主体为
account_llc_*四个 helper +task_pref_llc_runnable()谓词 +pref_llc_running_inc/dechelper - Message-ID 起点:
<20260827135000.735138-1-zhanxusheng@xiaomi.com> - 完整性:thread 完整,8/8 封邮件均参与讨论,最终在 helper 收敛阶段尚未看到 maintainer 取舍结论
补丁目的
alb_break_llc() 想用 nr_pref_llc_running == cfs.h_nr_runnable 来判定「这个 LLC 上全部 runnable 任务都偏好本 LLC」,从而决定是否跳过主动负载均衡以保护 LLC 亲和性。但两个计数器的语义不一致:
nr_pref_llc_running只在account_entity_enqueue/dequeue里加/减,跟随 queued 集合;h_nr_runnable在set_delayed()时减、clear_delayed()时加,跟随 runnable 集合。
带 DELAY_DEQUEUE 的任务已 sleep,却仍停留在 cfs 红黑树上,于是 nr_pref_llc_running > h_nr_runnable,等式永远不成立,alb_break_llc() 始终返回 false,主动负载均衡会无视 LLC 偏好,破坏 cache 亲和性。补丁目标:把 nr_pref_llc_running 也对齐到 runnable 语义,并在所有加/减入口统一守卫 sched_delayed。
旧流程的问题
// account_llc_enqueue()
rq->nr_llc_running++;
rq->nr_pref_llc_running += pref_llc_queued;
// account_llc_dequeue()
rq->nr_llc_running--;
if (p->pref_llc_queued)
rq->nr_pref_llc_running--;
两个 helper 完全不感知 sched_delayed,把 DELAY_DEQUEUE 任务仍计入「偏好 LLC 计数器」。结合 set_delayed() 单独改 h_nr_runnable,两侧永远对不齐,alb_break_llc() 的相等判定恒为假。
新流程
// 入队:被迁移的 delay 任务不计,等 clear_delayed 再补回
if (!p->se.sched_delayed)
rq->nr_pref_llc_running += pref_llc_queued;
// 出队:最终 dequeue 时如已 set_delayed,set_delayed 已减过,这里跳过避免下溢
if (!p->se.sched_delayed)
rq->nr_pref_llc_running--;
// 新增 account_llc_delayed() :进入 DELAY 状态时 -1
// 新增 account_llc_requeue_delayed():clear_delayed()/迁移回来时再 +1
最终在 helper 层收敛为一个布尔谓词:
static bool task_pref_llc_runnable(struct task_struct *p)
{
return p->pref_llc_queued && !p->se.sched_delayed;
}
static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) { ... }
static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) { ... }
Patch 概览
| 文件 | 修改 |
|---|---|
kernel/sched/fair.c | account_llc_enqueue/dequeue 增加 sched_delayed 守卫;新增 account_llc_delayed / account_llc_requeue_delayed;可收敛为 task_pref_llc_runnable() + pref_llc_running_inc/dec |
关键实现
-
account_llc_dequeue()的双重扣减保护:dequeue_hierarchy()先经dequeue_entity()→account_llc_dequeue(),再走到clear_delayed()。如果任务之前已set_delayed,nr_pref_llc_running已在 set 时减过一次,这里必须跳过,否则出现下溢;清掉pref_llc_queued还能阻止后续clear_delayed再补回。 -
account_llc_enqueue()的迁移守卫:Chen Yu 提出migrate_load走detach_tasks/attach_tasks,期间p->se.sched_delayed未被清掉;如果在原 rq 已经set_delayed,再在新 rq 加进来就会重复计数。因此迁移分支必须if (!sched_delayed)才nr_pref_llc_running += pref_llc_queued。 -
account_llc_delayed()/account_llc_requeue_delayed():分别在set_delayed()和clear_delayed()里调用;后者路径经requeue_delayed_entity()只做__enqueue_entity()+clear_delayed(),不经过account_entity_enqueue,所以必须在 requeue 路径手动+1,否则唤醒后偏好 LLC 计数永远偏低。 -
sd->llc_counts维持 queued 语义:两个 reader(fair.c:11895、13228)都是llc_counts之间互比,DELAY 任务在两侧平移,结果自洽,所以不需要改。 -
alb_break_llc()不动:等计数器语义对齐后,相等判定自然恢复「全部 runnable 任务都偏好本 LLC」的语义,无需修改调用点。
##计数器语义对比
+----------------------+ +------------------------+
| cfs_rq->nr_queued | | rq->nr_pref_llc_ |
| (queued set) | | running |
+----------------------+ | old: queued set |
| | new: runnable set |
| enqueue / dequeue +------------------------+
v ^
+----------------------+ set_delayed() |
| task on rq |------------------+
| se->sched_delayed=0 | clear_delayed()+
+----------------------+<-----------------+
| |
| migrate_load |
v v
+----------------------+ alb_break_llc()
| detach/attach across | nr_pref_llc_running
| CPUs (sched_delayed | ==
| unchanged) | cfs.h_nr_runnable ?
+----------------------+
关键调用序列对比
patch 前(语义错位):
enqueue ---> nr_pref++ h_nr_runnable++
set_delayed h_nr_runnable--
dequeue ---> nr_pref-- (real dequeue)
alb_break_llc(): nr_pref != h_nr_runnable -> 忽略 LLC 偏好
patch 后(语义对齐):
enqueue(sched_delayed=0) ---> nr_pref++ h_nr_runnable++
set_delayed() ---> account_llc_delayed() h_nr_runnable--
nr_pref--
requeue_delayed / clear_delayed ---> nr_pref++
dequeue(sched_delayed=0) ---> nr_pref-- h_nr_runnable--
alb_break_llc(): nr_pref == h_nr_runnable -> 保护 LLC 亲和性
类比
把 LLC 想象成咖啡店里一个靠窗的 4 人桌。h_nr_runnable 是「正在等咖啡还能聊天的客人」,nr_pref_llc_running 旧版是「占着位子的全部人(含低头刷手机已经睡着了的)」。服务员要决定「这桌所有人是不是都想靠窗」,旧逻辑因为睡着的人没退位,导致永远凑不齐 4 这个数,于是算法以为「并非所有人都想靠窗」,就把这桌的客人拆散到别的桌子去。新逻辑只在「醒着且想要靠窗」的人头上计数,服务员才能正确判断这桌是不是「全员都要窗边」,从而避免无意义的换桌、保留 cache 亲和性。
Highlight:风险与注意点
- 下溢风险:
account_llc_dequeue没加sched_delayed守卫之前,若任务先set_delayed再最终 dequeue,会减两次导致计数器下溢;必须严格保证pref_llc_queued在set_delayed路径被清掉。 - 迁移路径盲点:迁移任务期间
sched_delayed状态不变,若 enqueue 不守卫会出现「set_delayed 时减一次 + 迁移 enqueue 时再加一次」的偏移;Chen Yu 的追问推动补丁覆盖了migrate_load路径。 requeue_delayed_entity()不经 account path:唤醒路径必须显式调用account_llc_requeue_delayed(),否则延迟任务醒来后永远不计入偏好 LLC 计数,相等判定又会偏向 false。alb_break_llc()调用点是否还需要保护:本次未改调用点;理论上仍可能存在「所有 runnable 都偏好 LLC 时保护性成立」的额外细节(如cfs.h_nr_queued > 0之类),值得在合并前再过一遍 caller。sd->llc_counts不动是对的:但要确保后续没有新增 reader 把它与nr_pref_llc_running互比,否则语义不一致会再次发生。
版本变化
- v0(Zhan 8/27):仅问诊未提 patch;指出
nr_pref_llc_running应跟随 queued 或 runnable 二选一。 - v1(Zhan 8/28 patch):只修
account_llc_dequeue,引入sched_delayed守卫,跑通 enqueue/sleep/wake/dequeue 三种序列;Zhan 给自己的 Reviewed-by。 - v2(Chen Yu 9/03 patch):把守卫对称加到
account_llc_enqueue,新增account_llc_delayed/account_llc_requeue_delayed两个 helper 覆盖 set/clear_delayed 路径,覆盖迁移场景。 - v3(Tim Chen 9/04 提议):进一步收敛:抽出
task_pref_llc_runnable()谓词与pref_llc_running_inc/dechelper,消灭四个account_llc_*的重复if。
一句话总结
alb_break_llc() 因 nr_pref_llc_running 与 h_nr_runnable 一个数 queued、一个数 runnable 而永远判 false,patch 通过在四个 account_llc_* helper 里统一加 sched_delayed 守卫、再收敛为 task_pref_llc_runnable 谓词,让两边语义对齐,恢复主动负载均衡对 LLC 亲和性的正确保护。