0/3 已展开

LLM 分析

sched/debug:引入 per-CPU debugfs 文件

系列概况

  • 标题:[PATCH v2] sched/debug: Introduce per-CPU debugfs files
  • 作者:Aaron Tomlin atomlin@atomlin.com
  • 版本:v2(基于 v1 修订,v1 链接 20260728020309.6169-1-atomlin@atomlin.com
  • 规模:单 patch,1 file changed, 43 insertions(+)
  • 修改文件kernel/sched/debug.c
  • 代码统计:净增 43 行,无删除
  • Message-ID20260728205238.18447-1-atomlin@atomlin.com
  • 完整性:thread 共 3 封邮件,1 patch + 2 follow-up 回复(作者自述 Sashiko 工具发现 pre-existing issues)

补丁目的

在 debugfs 中新增 per-CPU 子目录结构,让排查者无需 dump 全机所有 CPU 的 runqueue 内容,就能直接读取目标 CPU 的调度调试信息。

  • 新接口路径/sys/kernel/debug/sched/cpu/cpu<N>/debug
  • 老接口路径/sys/kernel/debug/sched/debug(dump 所有 online CPU)

定位动机来自 reviewer(Peter Zijlstra、Zhan Xusheng)的反馈:在大型 SMP 拓扑上,定位某颗 CPU 上的 latency/调度异常时,把所有 CPU 数据全量打印既慢又干扰判断。

旧流程的问题

读取 sched/debug 会触发顶层 sched_debug_show(),依次遍历 for_each_online_cpu 把所有 CPU 的 runqueue 状态塞进一个 seq_file。对运维/调优人员来说:

  1. 数据量随 CPU 数量线性膨胀(256/384/512 核机器直接刷屏)。
  2. 难以脚本化只取某颗 CPU 的字段。
  3. 一旦目标 CPU 临时 offline,输出里就缺这条数据,且调用方难以区分"本来就没"与"刚好被跳过"。

新流程

新增一颗 CPU 就多一条独立的 debug 文件;读取只触发对单颗 CPU 的 print_cpu(),offline CPU 直接返回 -ENODEV

+--- /sys/kernel/debug/sched/ (debugfs_sched) ---+
|                                                |
|  +-- debug                  (legacy, all CPUs) |
|  |
|  +-- cpu/                  (new directory)     |
|       |
|       +-- cpu0/                                  |
|       |    +-- debug   --> print_cpu(cpu=0)      |
|       +-- cpu1/                                  |
|       |    +-- debug   --> print_cpu(cpu=1)      |
|       +-- cpuN/                                  |
|            +-- debug   --> print_cpu(cpu=N)      |
+------------------------------------------------+

读取路径上的语义(ASCII 流程图):

  user reads <debug>
        |
        v
  sched_debug_cpu_open()      <-- 把 inode->i_private (=cpu) 塞给 seq_file
        |
        v
  sched_debug_cpu_show(m, v)  <-- show 回调
        |
        +-- cpu_online(cpu)? ----no----> return -ENODEV
        |
        yes
        |
        v
  print_cpu(m, cpu)           <-- 复用现有 print_cpu(),只打一颗 CPU
        |
        v
  return 0

关键实现

补丁在 kernel/sched/debug.c 增加以下结构:

/* forward decl,使 sched_debug_cpu_show 能调到 print_cpu */
static void print_cpu(struct seq_file *m, int cpu);

/* seq_file 的 show 回调:只打目标 CPU */
static int sched_debug_cpu_show(struct seq_file *m, void *v)
{
	unsigned long cpu = (unsigned long) m->private;

	if (!cpu_online(cpu))
		return -ENODEV;

	print_cpu(m, cpu);
	return 0;
}

/* open 时把 cpu 号塞进 private */
static int sched_debug_cpu_open(struct inode *inode, struct file *filp)
{
	return single_open(filp, sched_debug_cpu_show, inode->i_private);
}

/* 用 single_release + seq_read 系列标准接口 */
static const struct file_operations sched_debug_cpu_fops = {
	.open    = sched_debug_cpu_open,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = single_release,
};

/* 初始化:建 cpu 目录 + 每个 possible CPU 建子目录 + debug 文件 */
static __init void debugfs_cpu_init(void)
{
	struct dentry *d_cpu_dir;
	unsigned long cpu;
	char buf[16];

	d_cpu_dir = debugfs_create_dir("cpu", debugfs_sched);

	for_each_possible_cpu(cpu) {
		struct dentry *d_cpu;

		snprintf(buf, sizeof(buf), "cpu%lu", cpu);
		d_cpu = debugfs_create_dir(buf, d_cpu_dir);

		debugfs_create_file("debug", 0444, d_cpu,
				    (void *) cpu, &sched_debug_cpu_fops);
	}
}

并在 sched_init_debug() 末尾追加一行 debugfs_cpu_init();,使该目录树在系统启动期就建好。

类比

把它想成医院的体检中心:

  • 旧的 /sched/debug 像一次"全员体检报告"——全公司几百号人一起打印一摞纸,你只想看张三的心电图却拿到全员的。
  • 新的 /sched/cpu/cpu<N>/debug 像每人一格独立档案柜,柜门上贴着"张三""李四"标签,打开就只看那一份。
  • 如果某个员工当天请假(CPU offline),柜门打开会告诉你"该员工今天不在岗"(-ENODEV),而不是给你一份空表。

更进一步,for_each_possible_cpu() 表示把所有"理论上可能存在"的工位都预留档案柜——即便今天没人坐(offline),将来 CPU 热上线后这套接口也已就绪。

再换一种说法,像酒店的前台叫号系统:旧接口就是"今天所有住客一览",新接口是"只给我 1208 房客人的账单",空房(offline CPU)直接告诉你"无人入住"。

Highlight:风险与注意点

  1. possible vs online 不一致debugfs_cpu_init()for_each_possible_cpu 一次性建好所有 CPU 的目录/文件,但 sched_debug_cpu_show()cpu_online() 过滤。可能的副作用是大量已退役/未启用的 CPU 也会有空目录堆积在 debugfs 视图里,影响可读性。
  2. 读取窗口期的并发:在 show 回调里先 cpu_online(cpu)print_cpu(m, cpu),理论上仍存在读期间 CPU hotplug 下线的窗口;若打印过程触发了锁或耗时较长,可能拿到不一致快照。当前实现选择不持锁,需关注后续是否会引入 hotplug 路径的回调追踪需求。
  3. 路径长度与命名:路径是 …/sched/cpu/cpu<N>/debugN 越大路径越长;在大系统(>1000 核)下确认 PATH_MAX 内仍可正常 stat/open。
  4. debugfs 不是稳定 ABI:和已有 sched/debug 一样属于调试接口,未来可以调整;但任何脚本开始依赖新路径前,需要意识到它可能在后续被重命名。
  5. 后续回复里作者提到 Sashiko 工具发现 pre-existing issues,本 patch 自身没有引入回归,但 review 阶段可能顺手把它们一起处理掉——这是合并前需要 follow up 的事项。

版本变化

v1 → v2 的关键调整(作者在 changelog 中明确列出):

  • 动机重写:Peter Zijlstra、Zhan Xusheng 指出 v1 描述不够聚焦于"在大 SMP 上做 targeted interactive debugging",v2 把这段写进 commit message。
  • offline 保护:Zhan Xusheng 建议 sched_debug_cpu_show() 增加 cpu_online(cpu) 检查,offline 时返回 -ENODEV,避免打印到一半缺数据或行为未定义。
  • v1 链接补充:在 commit message 里附上 v1 的 Message-ID,便于 lore/archive 回溯。

代码层面 v2 没有 v1 之外的二次结构性变化,仅按 review 反馈补了保护与文档。

一句话总结

通过在 debugfs 下新建 /sched/cpu/cpu<N>/debug 入口,把"全 CPU 调度调试信息"拆成"单 CPU 按需读取",offline CPU 直接 -ENODEV,方便在大 SMP 上做精准定位。