0/3 已展开

LLM 分析

sched/cache:让 nr_pref_llc_runningh_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.caccount_llc_enqueueaccount_llc_dequeueaccount_llc_delayedaccount_llc_requeue_delayed 等 helper,以及 alb_break_llc 调用点保持不变)
  • 代码统计:补丁主体为 account_llc_* 四个 helper + task_pref_llc_runnable() 谓词 + pref_llc_running_inc/dec helper
  • 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_runnableset_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.caccount_llc_enqueue/dequeue 增加 sched_delayed 守卫;新增 account_llc_delayed / account_llc_requeue_delayed;可收敛为 task_pref_llc_runnable() + pref_llc_running_inc/dec

关键实现

  1. account_llc_dequeue() 的双重扣减保护dequeue_hierarchy() 先经 dequeue_entity()account_llc_dequeue(),再走到 clear_delayed()。如果任务之前已 set_delayednr_pref_llc_running 已在 set 时减过一次,这里必须跳过,否则出现下溢;清掉 pref_llc_queued 还能阻止后续 clear_delayed 再补回。

  2. account_llc_enqueue() 的迁移守卫:Chen Yu 提出 migrate_loaddetach_tasks/attach_tasks,期间 p->se.sched_delayed 未被清掉;如果在原 rq 已经 set_delayed,再在新 rq 加进来就会重复计数。因此迁移分支必须 if (!sched_delayed)nr_pref_llc_running += pref_llc_queued

  3. account_llc_delayed() / account_llc_requeue_delayed():分别在 set_delayed()clear_delayed() 里调用;后者路径经 requeue_delayed_entity() 只做 __enqueue_entity() + clear_delayed(),不经过 account_entity_enqueue,所以必须在 requeue 路径手动 +1,否则唤醒后偏好 LLC 计数永远偏低。

  4. sd->llc_counts 维持 queued 语义:两个 reader(fair.c:1189513228)都是 llc_counts 之间互比,DELAY 任务在两侧平移,结果自洽,所以不需要改。

  5. 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_queuedset_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/dec helper,消灭四个 account_llc_* 的重复 if

一句话总结

alb_break_llc()nr_pref_llc_runningh_nr_runnable 一个数 queued、一个数 runnable 而永远判 false,patch 通过在四个 account_llc_* helper 里统一加 sched_delayed 守卫、再收敛为 task_pref_llc_runnable 谓词,让两边语义对齐,恢复主动负载均衡对 LLC 亲和性的正确保护。