0/1 已展开

LLM 分析

sched/deadline: 为动态 sched_getattr() 正确加锁任务 runqueue

系列概况

  • 标题: [PATCH] sched/deadline: Lock the task's runqueue for dynamic sched_getattr()
  • 作者: Guopeng Zhang zhangguopeng@kylinos.cn
  • 版本: 单封 patch(v1,无版本号)
  • 规模: 3 个文件,+43 / −15 行
  • 修改文件: kernel/sched/deadline.c、kernel/sched/sched.h、kernel/sched/syscalls.c
  • 代码统计: deadline.c +29/−7,sched.h +1/−1,syscalls.c +13/−7
  • Message-ID: 20260718094847.1239114-1-guopeng.zhang@linux.dev
  • Fixes tag: 2e7af192697e ("sched/deadline: Add reporting of runtime left & abs deadline to sched_getattr() for DEADLINE tasks")
  • 完整性: commit message 含 Subject、commit body、Fixes、Signed-off-by,diff 完整;thread 仅 1 封邮件,无后续 review

补丁目的

修掉 SCHED_GETATTR_FLAG_DL_DYNAMIC 路径上一个潜伏的并发与正确性 bug,并让接口适配 core scheduling / proxy execution 的 rq 锁抽象:

  1. race 风险:旧实现 task_rq(p) 在取锁之前完成;若任务在 "查询 rq" 与 "拿 rq->__lock" 之间发生迁移,CPU 上跑了别的任务,但 rq_clock(rq) 来自旧 rq,最后算出的 runtime / absolute deadline 与 p->dl 完全错位。
  2. policy 检查缺失: 旧 __getparam_dl 没有复检任务当前策略,任务被中途切到 SCHED_OTHER / SCHED_RT 后仍按 DL 字段填充。
  3. 绕过 core scheduling 的 rq 锁抽象: 直接拿 rq->__lock 会跳过 rq_pin_lock / task_rq_lock 的 indirection,无法正确处理 sched core 强制 donor 切换、IRQ 嵌套等场景。
  4. proxy execution 误算: update_curr_dl() 在 donor 模式下记账的是 rq->donor,旧 task_current(rq, p) 只看 rq->curr,导致 update_curr_dl 用错 dl实体。
  5. 错误码不传播: __getparam_dlget_params 都是 void,即便内部出错也只能静默错填。

旧流程的问题

__getparam_dl 关键段落(伪代码):

void __getparam_dl(struct task_struct *p,
 struct sched_attr *attr,
                    unsigned int flags)
{
    struct rq *rq = task_rq(p);          /* (1) 先取 rq,未加锁 */
    u64 adj_deadline;

    attr->sched_priority = p->rt_priority;
    guard(raw_spinlock_irq)(&rq->__lock); /* (2) 晚到的底层锁 */

    /* (3) 仅看 rq->curr,忽略 donor */
    if (task_current(rq, p)) {
        attr->sched_runtime = dl_se->runtime;
        attr->sched_deadline = dl_se->deadline;
    } else {
        attr->sched_runtime  = dl_se->dl_runtime;
        attr->sched_deadline = dl_se->dl_deadline;
    }

    if (flags & SCHED_GETATTR_FLAG_DL_DYNAMIC) {
        update_rq_clock(rq);             /* (4) 用错 rq 的 clock */
        update_curr_dl(rq);              /* (5) 错过 donor 路径 */
        attr->sched_runtime  = dl_se->runtime;
        adj_deadline = dl_se->deadline - rq_clock(rq) + ktime_get_ns();
        attr->sched_deadline = adj_deadline;
    }
    ...
}

隐患链条:取 rq → 拿锁 → 期间 p 可能迁移;rq_clock(rq)p->dl 来自不同 CPU;update_curr_dl 跳过 donor;policy 一旦变化就静默按 DL 字段填充。

新流程

int __getparam_dl(struct task_struct *p,
                   struct sched_attr *attr,
                   unsigned int flags)
{
    struct rq_flags rf;
    struct rq *rq;
    u64 adj_deadline;

    rq = task_rq_lock(p, &rf); /* (a) pin 住 p 与 rq */
    if (!task_has_dl_policy(p)) {       /* (b) 锁内复检策略 */
        task_rq_unlock(rq, p, &rf);
        return -EINVAL;
    }

    if (task_current_donor(rq, p))      /* (c) donor 语义 */
        attr->sched_policy = p->policy;

    attr->sched_priority = p->rt_priority;
    attr->sched_runtime  = dl_se->dl_runtime;
    attr->sched_deadline = dl_se->dl_deadline;

    if (flags & SCHED_GETATTR_FLAG_DL_DYNAMIC) {
        update_rq_clock(rq);             /* (d) 同源 rq 的 clock */
        update_curr_dl(rq);              /* (e) dl_se 与 rq 一致 */
        attr->sched_runtime  = dl_se->runtime;
        adj_deadline = dl_se->deadline - rq_clock(rq) + ktime_get_ns();
        attr->sched_deadline = adj_deadline;
    }

    attr->sched_period = dl_se->dl_period;
    attr->sched_flags &= ~(SCHED_FLAG_RESET_ON_FORK | SCHED_DL_FLAGS);
    if (p->sched_reset_on_fork)
        attr->sched_flags |= SCHED_FLAG_RESET_ON_FORK;
    attr->sched_flags |= dl_se->flags;

    task_rq_unlock(rq, p, &rf);
    return 0;
}

sched_copy_attrget_params 也由 void 改为 intsched_setattrSCHED_FLAG_KEEP_PARAMS 分支与 sched_getattr 调用 get_params 后都把返回值冒泡给用户态。

关键实现

  1. task_rq_lock() 替代 rq->__lock: helper 同时 pin 住 task 与 rq,正确处理 IRQ、sched core、double-lock balance,是 sched 子系统对外读写 task 状态的标准入口。
  2. 锁内 policy 复检: 任务不再是 DL 时直接 -EINVAL 冒泡,避免读到 RT/CFS 任务却按 DL 字段填充。
  3. task_current_donor() 替换 task_current(): donor 模式下 rq->donor != rq->curr,必须走 donor 才能让 update_curr_dl 找到正确的 DL 实体;顺带把 sched_policy 填齐。
  4. sched_flags 清理更准确: SCHED_FLAG_RESET_ON_FORK 先清空再依 p->sched_reset_on_fork 重建,比旧版"只看 dl_se->flags"更可靠。
  5. 错误码向上传递: __getparam_dlget_params改返 intsched_setattr / sched_getattr 在两条路径上都处理失败。

类比

把"读取任务的截止时间"想成去医院打印一份体检报告:

  • 旧实现像前台先告诉你去哪个诊室(task_rq),等你走到诊室门口时医生已经换班去了别的诊室(迁移),但你仍按"上一位医生桌面的体检报告"打印,结果数据和现在的你对不上;而且你不看门口医生名牌,只看墙上贴的"值班医生"(task_current忽略 donor)。
  • task_rq_lock 像前台直接带锁把这位医生按在椅子上不让他走,并现场让他先自报身份(task_has_dl_policy)、再把体检报告递给你。task_current_donor 则是承认"真正给你体检的可能是代班医生",而不是只看门口名牌。

Highlight:风险与注意点

  • rq_clock()ktime_get_ns() 跨时钟域:调度器内部 clock 与 wall clock 不同步;调用方不应把 sched_deadline 当作 wall-clock 绝对时间直接比对业务时间戳。本 patch 不解决语义混淆,只确保数值来自正确的 rq。
    -旧代码中 dl_se 在 diff 里被重排,review 时需重点确认 dl_se = &p->dl; 仍保留在函数体头部;否则新代码会引用未初始化指针。
  • ABI 行为变化:sched_getattr(..., SCHED_GETATTR_FLAG_DL_DYNAMIC) 在非 DL 任务上现在返回 -EINVAL,用户态 wrapper 需要回归测试。
  • 单封 patch,无 maintainer 回复与后续 review;若被 maintainer 指出需拆分为"core scheduling 适配"与"DYNAMIC flag 修复"两封,需要再发一版。
  • guard(raw_spinlock_irq) 在 core scheduling 下并不足以表达 "lock the task's runqueue" 的语义;后续若引入 rq 锁嵌套规则,原 patch 还可能需要进一步约束 rf 的 flag组合。

版本变化

本 thread 仅 v1,无 v2/v3 演进。

一句话总结

__getparam_dl 从直接拿 rq->__lock 改成走 task_rq_lock()、锁内复检 DL 策略、改用 task_current_donor(),既修了任务迁移场景下 rq_clock 错位与 update_curr_dl 漏算的 race,也把接口对齐 core scheduling / proxy execution 的 rq 锁抽象。

+----------------------------------------------------+
| OLD path: rq fetched early, wrong rq clock |
+----------------------------------------------------+
  userspace
      |
      v
  sched_getattr(..., SCHED_GETATTR_FLAG_DL_DYNAMIC)
      |
      v
  get_params(p, attr, flags) -- returns void
      |
      v
  __getparam_dl(p, attr, flags)
      |-- task_rq(p)                 <-- snapshot only
      |-- guard(rq->__lock)           <-- too late, p may have migrated
      |-- task_current(rq, p)         <-- ignores rq->donor
      |-- update_curr_dl(rq)          <-- wrong dl_se for donor
      |-- rq_clock(rq) + ktime_get_ns <-- mismatched with p->dl
      v
  return to userspace: stale runtime / deadline```

```text
+----------------------------------------------------+
| NEW path: task_rq_lock + policy recheck + donor   |
+----------------------------------------------------+
  userspace
      |
      v
  sched_getattr(..., SCHED_GETATTR_FLAG_DL_DYNAMIC)
      |
      v
  get_params(p, attr, flags) -- returns int
      | (errno bubbles up on failure)
      v
  __getparam_dl(p, attr, flags)
      |-- task_rq_lock(p, &rf)            -- pin p and its rq
      |-- if (!task_has_dl_policy(p))     -- recheck under lock |       task_rq_unlock(); return -EINVAL
      |-- task_current_donor(rq, p)       -- honor proxy execution
      |-- update_curr_dl(rq)              -- dl_se matches rq
      |-- rq_clock(rq) + ktime_get_ns     -- same rq for clock
      |-- task_rq_unlock(rq, p, &rf)
      v
  return 0 to userspace: runtime / deadline in sync