Linux进程调度如何决定下一个运行的进程:task_struct与sched_entity内部机制
2026/9/8 20:24:44 网站建设 项目流程

Linux进程调度如何决定下一个运行的进程:task_struct与sched_entity内部机制

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

在终端里敲下一条sleep 300,按回车,这个进程就不再出现在top的活跃列表里,但杀进程时它依然"活着"。它没有消失,只是切换了状态,而内核每隔一个时间片都要重新回答同一个问题:下一个 CPU 周期给谁?这个问题的答案就存在两个结构体里:记录进程全部信息的task_struct,以及调度器真正拿来比较的sched_entity(调度实体,可理解为 task_struct 里那张专门给调度器看的"评分卡")。下面把这张评分卡的字段、排序规则和唤醒到重新上 CPU 的完整路径讲清楚。

机制在代码中的位置:三个文件看完全貌

调度机制横跨三处代码:状态与结构体定义在 include/linux/sched.h,CFS 公平调度器的算法主体在 kernel/sched/fair.c,用户可见的统计输出在 kernel/sched/debug.c。

字段 / 常量作用源码位置
task_struct.__state32 位位图,标记进程当前处于运行还是哪一类睡眠include/linux/sched.h#L843
TASK_RUNNING等状态位各状态对应的位掩码(TASK_RUNNING0x0,"可运行"即无标志位)include/linux/sched.h#L106-L127
task_struct.prio/static_prio/normal_prio静态优先级、nice 换算的优先级、含动态调整后的最终优先级include/linux/sched.h#L884-L886
task_struct.se普通进程(fair 类)的调度实体,内嵌在 task_struct 中include/linux/sched.h#L889
task_struct.rt/dl实时类(SCHED_FIFO/RR)与截止时间类(SCHED_DEADLINE)各自的调度实体include/linux/sched.h#L890-L891
sched_entity.load由 nice 值换算出的进程权重,权重越大分到的 CPU 时间越多include/linux/sched.h#L574
sched_entity.run_node红黑树节点,该进程在队列中的挂靠点include/linux/sched.h#L576
sched_entity.vruntime虚拟运行时间,排序与比较的核心include/linux/sched.h#L592
sched_entity.sum_exec_runtime该进程累计的真实 CPU 时间(纳秒),不受 nice 影响include/linux/sched.h#L590
sched_entity.avg负载平均值(PELT,指数加权滑动平均),供负载均衡与 util clamp 使用include/linux/sched.h#L618
sched_entity.slice/min_vruntime当前时间片长度;队列中虚拟时间的最小值缓存include/linux/sched.h#L597, L578

task_struct 中三种调度类实体并列存放,每个进程同时带着这三张"卡",但同一时刻只有一张卡对当前调度类生效:

struct task_struct { /* ... */ struct sched_entity se; /* fair 类进程使用的调度实体 */ struct sched_rt_entity rt; /* SCHED_FIFO / SCHED_RR 实时进程 */ struct sched_dl_entity dl; /* SCHED_DEADLINE 截止时间进程 */ };

se内部与调度决策直接相关的部分(完整定义约 48 行,此处只摘核心):

struct sched_entity { struct load_weight load; /* nice 值换算出的权重 */ struct rb_node run_node; /* 红黑树节点 */ u64 vruntime; /* 虚拟运行时间,排序依据 */ u64 sum_exec_runtime; /* 真实累计执行时间 */ u64 vlag; /* 虚拟滞后的近似值,EEVDF 使用 */ u64 slice; /* 当前时间片 */ struct sched_avg avg; /* 负载平均值 */ };

推演:一个进程从睡眠到再次上 CPU 的完整路径

以最典型的事件流走一遍:sleep 300被定时器唤醒之后,CFS 如何把它重新放上 CPU。

  1. 定时器到期,调度器把task_struct.__stateTASK_INTERRUPTIBLE改回TASK_RUNNING,进程重新"可运行"。
  2. 进程经由enqueue_task_fair()挂回该 CPU 的 CFS 运行队列,实体插入红黑树,位置由vruntime与队列最小值min_vruntime的差决定。
  3. 当前进程每运行一个时间片,update_curr()把真实经过的时间除以权重load后累加进vruntime——权重小的进程虚拟时间涨得快,下次轮到它就更晚。
  4. 需要换人时,pick_next_entity()取出虚拟期限(deadline,由 vruntime 加时间片推算)最早的进程。
  5. 调度器执行上下文切换:保存旧进程的寄存器与pcpu上下文,恢复新进程;旧进程若被抢占,重新排队,回到第 2 步循环。

这里值得单独说明vruntime的"虚拟"二字:它不是墙上时钟时间,而是真实执行时间经过权重缩放后的量。nice 值越高的进程权重越小、vruntime涨得越快,于是它连续获得 CPU 的机会越少——公平性正是靠这一把"变速的尺子"实现的,而不是靠给不同进程分配不同长度的真实时间片。

验证:一条命令读出评分卡

不需要编译内核,/proc/<pid>/sched就是kernel/sched/debug.c直接导出的实体快照:

pid=$(pidof sleep) grep -E "se\.(vruntime|sum_exec_runtime|slice)|nr_involuntary|prio" /proc/$pid/sched

典型输出及字段含义:

输出字段对应实体字段含义
se.vruntimese.vruntime虚拟运行时间(秒.纳秒拆分显示),队列排序依据
se.sum_exec_runtimese.sum_exec_runtime真实累计 CPU 时间,与 nice 无关
se.slicese.slice内核当前分配给该进程的时间片长度
nr_involuntary_switchesp->nivcsw被内核强制切走的次数(非主动让出)
priop->normal_prio最终生效的优先级,含动态调整

对比同一进程的两个数就能看懂权重换算:sum_exec_runtime是它实际消耗了多少 CPU,vruntime是被"除以权重"之后的账本——对高 nice 进程,vruntime会明显大于sum_exec_runtime

当前内核的调度参数与旧资料里的说法不同,速查如下:

参数查看方式说明
base_slice_nscat /sys/kernel/debug/sched/base_slice_ns(需挂载 debugfs)时间片基准,由 CPU 频率推导而非固定 4 ms
各类调度特性开关sysctl -a \| grep kernel.sched_sched_autogroup_enabled等,EEVDF 时代sched_latency_nssched_min_granularity_ns等旧参数已移除
每 CPU 队列统计cat /proc/sched_debug各 CPU 上min_vruntimenr_running等队列状态
全局调度事件计数cat /proc/schedstat按 CPU 汇总的切换次数与运行时间

误区与边界

  • vruntime不是真实时间,两者不能相减比较。它是权重缩放后的虚拟量;想统计一个进程真正消耗了多少 CPU,看sum_exec_runtime或用pidstat -t -p,拿vruntime当秒数用会得到错误结论。
  • 红黑树排序的键已经不是vruntime本身。自 EEVDF 取代 CFS 以来(设计见 Documentation/scheduler/sched-eevdf.rst),选择依据是deadline(vruntime 加上时间片推算出的虚拟截止时间),min_vruntime也改为缓存最小 deadline 的近似值。旧文档里"挑 vruntime 最小的进程"这一句在当下内核只算近似正确。
  • sched_entity只对 fair 类进程有效。SCHED_FIFO提权的实时线程走rt实体、纯轮转链表,dl类进程走截止时间队列;它们运行时会直接抢占 CFS 队列,/proc/<pid>/sched里那几个se.*字段对实时进程基本无意义。判断方法:ps -o pid,cls,ni -p <pid>,看调度类是否为FIFO/RR/DL

延伸

task_struct是进程的全部档案,sched_entity只是其中交给调度器的那张评分卡;看懂权重如何换算vruntimedeadline如何参与选择,就能解释绝大多数"我的进程为什么拿不到 CPU"的现象。继续深入可先读官方调度器文档,再对照 kernel/sched/fair.c 中enqueue_task_fairupdate_currpick_next_entity三个函数的实现。

  • Documentation/scheduler/sched-design-CFS.rst:CFS 原始设计文档
  • Documentation/scheduler/sched-eevdf.rst:EEVDF 调度算法设计
  • kernel/sched/debug.c:/proc/<pid>/sched等接口的实现

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询