FEATURED · 精选文章

Linux 进程调度拆解:task_struct 与 sched_entity 如何分走 CPU 时间片

发布时间 / 2026/9/8 18:13:24
来源 / 创域科博编辑部
栏目 / 资讯中心
Linux 进程调度拆解:task_struct 与 sched_entity 如何分走 CPU 时间片 Linux 进程调度拆解task_struct 与 sched_entity 如何分走 CPU 时间片【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux一个反直觉的事实nice 0 的进程会被 nice 10 的进程反复抢占而 CFS 会认为这完全公平。想搞懂 Linux 进程调度得先放弃优先级 运行时长的直觉改问另一个问题调度器到底在读谁、写谁答案藏在一对结构体里。task_struct是进程在内核里的完整档案sched_entity下称 se则是为调度单独剪出来的小票。本文不逐字段念经而是跟一次 CPU tick 的完整生命周期走一遍——唤醒、入队、选优、切换、回收——看这两个结构体在每一步分别被谁读、被谁写。先分清两套数据完整档案和登机牌task_struct超过 2500 行的头文件里塞了线程控制块、信号掩码、内存描述符、cred 凭证内核里几乎每个子系统都在往里加字段。但调度器 99% 的时间只关心其中一小撮状态、优先级、以及三个调度实体——/* include/linux/sched.h#L889-L891 */ struct sched_entity se; /* CFS 普通任务用 */ struct sched_rt_entity rt; /* 实时任务用 */ struct sched_dl_entity dl; /* 截止时间任务用 */se 里装的就是调度决策的全部依据权重load、红黑树节点run_node、虚拟运行时间vruntime、总执行时间sum_exec_runtime外加负载平均avg见include/linux/sched.h#L572。类比一下档案柜里存着你的全部资料登机牌上只有航班、座位和闸口——登机只用登机牌查身份才翻档案。这也解释了架构上的一个细节静态优先级static_prio和动态优先级normal_prio存在task_struct里#L885nice 到 prio 的换算公式是NICE_TO_PRIOinclude/linux/sched/prio.h#L27而谁该多跑一点的权重load却放在 se 里。前者是身份后者是待遇两者分开存改 nice 值不需要动调度结构。唤醒时调度器写了什么进程睡眠前会把自己置成TASK_INTERRUPTIBLE并挂在某个等待队列上被唤醒时wake_up()路径做的是若state p-__state匹配就把__state写回TASK_RUNNING再调用try_to_wake_up()把它塞回运行队列。状态标志定义在include/linux/sched.h#L107注意TASK_RUNNING是 0其余状态都是非零掩码。一个容易踩的坑状态翻转不是原子语义set_current_state()宏内部带了屏障#L244附近就是为了防止检查状态和睡眠之间被别的 CPU 插进唤醒。30 秒验证cat /proc/pid/status | grep State看到S (sleeping)和D (disk sleep)的切换就是__state在两种非零值之间移动。入队时谁被排进红黑树唤醒后的任务进入enqueue_task_fair()CFS 做两件事。一是补账如果该任务上次出队时没结算先调update_curr()把exec_start到现在的墙钟时间折成虚拟时间加进vruntimekernel/sched/fair.c中update_curr的实现。二是归位以vruntime为键把se-run_node插进该 CPU 的cfs_rq红黑树最左端。关键在于vruntime的累加公式是按权重折算的跑 1ms 实际时间权重大的进程vruntime只涨一点点权重小的涨得多。所以 nice 10 的进程vruntime增长快、在树里爬得快但它的起点通常落后——这就是高优先级偶尔被低优先级反超的完整解释优先级只决定 vruntime 的增速vruntime 绝对值才决定排序。/proc/pid/sched里的se.vruntime和se.sum_exec_runtime就是这两个量的直接读数。30 秒验证cat /proc/pid/sched对比se.vruntime与se.sum_exec_runtime前者通常远小于后者差值比例就是权重的作用。选优时红黑树只认一个数CPU 上每次需要换人timer tick 触发、任务阻塞、被抢占调度器调用pick_next_task_fair()从当前cfs_rq红黑树取最左节点——vruntime最小的那个。整棵树 O(log n) 完成比较不需要遍历所有可运行任务。这里 se 和档案的分工再次清晰红黑树节点run_node、排序键vruntime、权重load全在 se 上task_struct只在最后一步被碰——取出se指向的宿主任务准备切换。选优阶段的读写映射可以浓缩成一张表字段被哪个函数读写影响什么决策task-__statetry_to_wake_up()写、调度入口读任务能否进/留在运行队列se-vruntimeupdate_curr()写、pick_next_entity()读红黑树排序键决定谁先跑se-loadset_task_cpu/se_update_runnable读折算 vruntime 增速决定时间片比例se-sum_exec_runtimeupdate_curr()累加记账与/proc展示不参与排序task-static_priorenice系统调用写换算权重 load间接影响 vruntime 增速切换与回收时间片去哪了选中下一个 se 后走context_switch()保存当前任务的寄存器与栈指针这些在task_struct的 thread 信息里恢复目标任务的现场。当前任务若仍有剩余虚拟时间被从树上摘除、记入prev_sum_exec_runtime__state保持TASK_RUNNING——它没睡只是暂时没牌。时间片的回收其实是账本闭环exec_start标记本次上 CPU 的时刻出队时墙钟差值折进vruntime下次入队时树里的相对位置就变了。跑得多的人vruntime大、排后面刚睡醒的人vruntime小、排前面——CFS 的公平不是均分墙钟而是均分虚拟时间。30 秒验证sysctl kernel.sched_min_granularity_ns kernel.sched_latency_ns看调度器设定的最小粒度与目标延迟这就是单次 tick 能容忍的时间片下限。带走三件事优先级不等于运行时长。nice只通过NICE_TO_PRIO改变权重从而改变vruntime的增速排序永远看vruntime绝对值所以高 nice 值进程被低 nice 值进程反超是机制使然不是 bug。一次调度决策只碰两个结构体的一小撮字段__state管能不能上se 里的vruntime、load、run_node管什么时候上task_struct其余 2000 多行字段在选优阶段完全不被触碰。排查调度问题先看/proc/pid/sched的se.vruntime与se.sum_exec_runtime比值再配合sysctl kernel.sched_*参数对照基本能定位是权重配置问题还是参数问题不需要进内核源码。想继续深挖从 kernel/sched/fair.c 的update_curr入手顺着enqueue_task_fair和pick_next_entity读一遍再对照Documentation/scheduler/sched-design-CFS.rst比任何字段清单都高效。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻