Linux不直接提供“抢占延迟”单一数值,需综合schedstat的sum_wait_runtime、getdelays的preempt_delay/run_delay及trace-cmd的调度事件来区分实时抢占、中断或CFS调度决策。

Linux本身不直接暴露“抢占延迟”这个单一数值,schedstat 和 getdelays 提供的是构成抢占延迟的底层分项时间,你需要组合解读——不是查一个字段,而是看多个字段之间的关系。
看 /proc/[pid]/schedstat 里的三元组
/proc/[pid]/schedstat 每行输出三个数字,例如 1234567890 987654321 456789,它们分别代表:
- 进程在 CPU 上实际运行的纳秒数(
sum_exec_runtime) - 进程在就绪队列中等待被调度的总纳秒数(
sum_wait_runtime) - 进程被强制迁移(migrate)的次数(
nr_migrations)
其中第二项 sum_wait_runtime 是最接近“抢占延迟感知”的指标:它包含因高优先级任务抢占、调度器周期 tick 到期、或 CFS vruntime 差值触发重调度而产生的排队等待时间。但它不是单次抢占的延迟,而是累计值。
注意:sum_wait_runtime 不区分“被抢占”和“自愿让出后重新排队”,所以高值不一定等于频繁抢占——也可能是调度周期长、CPU 饱和、或大量短时睡眠唤醒导致的队列堆积。
用 getdelays -d 看 delayacct 的细分等待
getdelays 输出的 delayacct 数据比 schedstat 更细,尤其对抢占相关场景:
-
run_delay:任务从唤醒到真正开始执行的时间差(含抢占延迟 + 调度器入队延迟) -
preempt_delay:仅当内核配置了CONFIG_SCHEDSTATS=y且启用delayacct时才出现,表示因抢占导致的延迟(即高优先级任务插入导致本任务推迟执行) -
block_delay:I/O 或锁等待导致的延迟,与抢占无关
运行命令:sudo getdelays -d -p <pid></pid>。如果输出里没有 preempt_delay 字段,说明内核未开启 CONFIG_TASK_DELAY_ACCT,此时只能退回到 schedstat 的 sum_wait_runtime 估算。
常见坑:preempt_delay 只统计硬抢占(如实时任务抢占普通任务),不统计 CFS 内部基于 vruntime 的“软抢占”。CFS 场景下,你看到的往往是 run_delay 偏高,但 preempt_delay 为 0。
结合 trace-cmd 抓取真实抢占事件
要确认某次具体抢占发生的时间点和原因,必须用 trace-cmd 抓 sched_waking、sched_switch 和 sched_migrate_task 事件:
sudo trace-cmd record -e sched:sched_waking -e sched:sched_switch -e sched:sched_migrate_task -p 1000- 复现问题后
sudo trace-cmd report | grep -A5 -B5 "comm==.*your_process_name"
关键看 sched_switch 中的 prev_comm 和 next_comm,以及 prev_state;若 prev_state == R(可运行态)却被切走,大概率是被抢占;再对照 sched_waking 时间戳,就能算出从唤醒到被切的延迟。
注意:trace-cmd 默认不记录时间戳精度到纳秒级,需加 -T 参数启用高精度时钟;且 trace buffer 太小会导致丢事件,建议先 trace-cmd reset 并用 -b 8192 扩大 buffer。
真正难的不是拿到数字,而是区分“抢占”是来自实时任务、中断线程、还是 CFS 自身调度决策——preempt_delay 只报前者,run_delay 和 sum_wait_runtime 混合了所有原因,trace-cmd 才能告诉你谁在那一刻动了手。


















