0/1 已展开

LLM 分析

sched/deadline: 本地 CPU 是唯一过载时不发起 pull

系列概况

  • 标题: [PATCH] sched/deadline: Do not pull when the local CPU is the only overloaded one
  • 作者: Liang Hao haohlliang@gmail.com
  • 版本: v1(无版本号,单 patch)
  • 规模: 1 个 patch,1 文件改动
  • 修改文件: kernel/sched/deadline.c
  • 代码统计: +7 / -1(kernel/sched/deadline.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
  • Message-ID: 20260807150709.47181-1-haohlliang@gmail.com
  • 完整性: 单封邮件,commit message 完整(含 Signed-off-by),diff 与说明一致

补丁目的

DEADLINE 调度器在 pull_dl_task() 中会扫描 rd->dlo_mask 寻找过载 CPU,再尝试把对方的 deadline 任务拉过来。如果当前 CPU 自己就是唯一被标记为过载的 CPU,那么 for_each_cpu() 循环每次迭代都会通过 if (this_cpu == cpu) continue; 跳过自己,最终什么都不做——这是一次空转。

本 patch 把 RT 调度器在 commit f73c52a5bcd1 ("sched/rt: Do not pull from current CPU if only one CPU to pull") 中加入的短路判断镜像到 DEADLINE:当 dlo_count == 1 且当前 CPU 拥有 dlo_mask 里唯一的 bit 时直接 return,避免无意义的循环扫描。

旧流程的问题

旧代码即使本地是唯一过载 CPU,仍会执行 smp_rmb()、进入 for_each_cpu() 遍历 dlo_mask,每次都用 if (this_cpu == cpu) continue; 跳过自己再走完一轮循环。功能上不影响正确性,但:

  • 多余的 cache line 读取(dlo_mask / dlo_count
  • 多余的 branch 与 loop 迭代
  • 与 RT 侧形成不对称,DEADLINE 这条路径遗漏了同类短路

新流程

新流程把"本地是唯一过载源"这一情形提前到 fast-path 之后立即返回,不再进入遍历循环:

+----------------------------------------+
| pull_dl_task(this_rq) |
+----------------------------------------+
            |
            v
+----------------------------------------+
| count = dl_overloaded(this_rq)         |
+----------------------------------------+
            |
            v
+----------------------------------------+
| if (count == 0) return                 |
+----------------------------------------+
            |
            v
+----------------------------------------+
| smp_rmb()                              |
+----------------------------------------+
            |
            v
+----------------------------------------+
| if (count == 1 &&                      |
|   cpumask_test_cpu(this_cpu, dlo_mask))|
|   return                               |
+----------------------------------------+
            |
            v
+----------------------------------------+
| for_each_cpu(cpu, dlo_mask) {          |
|   if (this_cpu == cpu) continue        |
|   /* original pull logic */ |
| }                                      |
+----------------------------------------+

RT 与 DEADLINE 两侧的 short-circuit 现状对比:

Before patch (asymmetric):       After patch (symmetric):

pull_rt_task:                    pull_rt_task:
  if (count == 1 && own)           if (count == 1 && own)
    return                           return
  for_each_cpu(rto_mask) {         for_each_cpu(rto_mask) {
    if (this_cpu == cpu)             if (this_cpu == cpu)
      continue                         continue
    ...                             ...
  }                                }

pull_dl_task:                    pull_dl_task:
  (no short-circuit)               if (count == 1 && own)
 for_each_cpu(dlo_mask) {           return   <-- NEW if (this_cpu == cpu)           for_each_cpu(dlo_mask) {
      continue                       if (this_cpu == cpu)
    ...                               continue
  }                                  ...
 }

Patch 概览

kernel/sched/deadline.cpull_dl_task() 顶部只动两处:

  1. dl_overloaded(this_rq) 的结果保存到 dl_overload_count,并用它替代原 fast-path 判断条件。
  2. smp_rmb() 之后、for_each_cpu() 之前插入 4 行短路判断:count == 1 && 本 CPU 在 dlo_mask 中return

关键实现

static void pull_dl_task(struct rq *this_rq)
{
    int dl_overload_count = dl_overloaded(this_rq);

-   if (likely(!dl_overloaded(this_rq)))
+   if (likely(!dl_overload_count))
        return;

    smp_rmb();

+   /* If we are the only overloaded CPU do nothing */
+   if (dl_overload_count == 1 &&
+       cpumask_test_cpu(this_rq->cpu, this_rq->rd->dlo_mask))
+       return;
+
    for_each_cpu(cpu, this_rq->rd->dlo_mask) {
        if (this_cpu == cpu)
            continue;
        ...
    }
}

要点:

  • dl_overloaded() 内部读的是 rd->dlo_count,保存到本地变量后既能复用做 fast-path,也能在短路判断里继续用,避免重复读取。
  • cpumask_test_cpu(this_rq->cpu, this_rq->rd->dlo_mask) 直接复用现有的 dlo_mask,与 RT 侧用 rto_maskthis_cpu 的写法完全对称。
  • 提交者明确说明 DEADLINE 没有 RT_PUSH_IPI 对等路径,因此这次只是单边对称补齐,没有引入 push 端短路。
  • 整体内存序:依靠 smp_rmb()dl_set_overload() 里的 smp_wmb() 配对保护 dlo_count/dlo_mask 的可见性,新增判断位于 smp_rmb() 之后,时序正确。

类比

想象一个快递员走进分拣中心,先扫一眼大厅里"有待取件"的窗口清单(dlo_mask / dlo_count)。如果清单上写着"只有你自己的窗口有待取件",那他根本没必要再沿着清单走一圈——绕到头来也只能跳过自己。补丁做的就是:在大厅门口看一眼,发现清单只有自己那行,就直接掉头回去,把这一圈白跑省掉。

另一个角度:RT 调度器是已经"装了这种感应门"的版本,DEADLINE 调度器这边一直没装,这次给同一栋楼的对称入口补上了同款感应门,让两边走法对称。

Highlight:风险与注意点

  • 功能不变:作者明确写"No functional change",仅做对称补齐和微小性能优化,理论上不应改变可观察行为;下游 stable backport 不必着急。
  • 关注点 1dl_overloaded()smp_rmb() 之间没有重新读 dlo_count;本地变量复用是安全的,因为读、写配对 (smp_wmb / smp_rmb) 仍然成立。
  • 关注点 2:DEADLINE 没有 RT_PUSH_IPI 等价的 push 路径,因此这里只是"pull 端"的对称化;如果未来加 push,需要同样在 push 端判断"只有自己是过载源"时跳过。
  • 关注点 3pull_dl_task() 的调用方(如 dl_task_offline_migrationbalance_callback)依赖它尝试拉任务;这次新增的 early return 不会改变"是否值得调用"的上层决策,仅在该函数内部更早退出。
  • 关注点 4:建议关注 review 端是否要求把判断下沉到 dl_overloaded() 内部做封装,或要求同时给 pull_rt_task() 加注释引用本次 commit,保持两侧注释对称。

一句话总结

把 RT 调度器已有的"唯一过载就是自己就跳过 pull"短路逻辑镜像到 DEADLINE 的 pull_dl_task(),避免本地 CPU 是唯一过载源时空转 for_each_cpu(),并保持两侧 short-circuit 对称。