sched discussion
[PATCH] sched/deadline: Lock the task's runqueue for dynamic sched_getattr()
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 锁抽象:
- race 风险:旧实现
task_rq(p)在取锁之前完成;若任务在 "查询 rq" 与 "拿 rq->__lock" 之间发生迁移,CPU 上跑了别的任务,但rq_clock(rq)来自旧 rq,最后算出的 runtime / absolute deadline 与p->dl完全错位。 - policy 检查缺失: 旧
__getparam_dl没有复检任务当前策略,任务被中途切到 SCHED_OTHER / SCHED_RT 后仍按 DL 字段填充。 - 绕过 core scheduling 的 rq 锁抽象: 直接拿
rq->__lock会跳过rq_pin_lock/task_rq_lock的 indirection,无法正确处理 sched core 强制 donor 切换、IRQ 嵌套等场景。 - proxy execution 误算:
update_curr_dl()在 donor 模式下记账的是rq->donor,旧task_current(rq, p)只看rq->curr,导致update_curr_dl用错 dl实体。 - 错误码不传播:
__getparam_dl与get_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_attr 中 get_params 也由 void 改为 int;sched_setattr 的 SCHED_FLAG_KEEP_PARAMS 分支与 sched_getattr 调用 get_params 后都把返回值冒泡给用户态。
关键实现
task_rq_lock()替代rq->__lock: helper 同时 pin 住 task 与 rq,正确处理 IRQ、sched core、double-lock balance,是 sched 子系统对外读写 task 状态的标准入口。- 锁内 policy 复检: 任务不再是 DL 时直接
-EINVAL冒泡,避免读到 RT/CFS 任务却按 DL 字段填充。 task_current_donor()替换task_current(): donor 模式下rq->donor != rq->curr,必须走 donor 才能让update_curr_dl找到正确的 DL 实体;顺带把sched_policy填齐。sched_flags清理更准确:SCHED_FLAG_RESET_ON_FORK先清空再依p->sched_reset_on_fork重建,比旧版"只看dl_se->flags"更可靠。- 错误码向上传递:
__getparam_dl与get_params改返int,sched_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