第17章 调度子系统:从rq到EEVDF 第17章 调度子系统从rq到EEVDF内核版本Linux 7.1.3架构x86_64对应上篇引入章节第10章核心源码路径include/linux/sched.hkernel/sched/sched.hkernel/sched/core.ckernel/sched/fair.ckernel/sched/rt.ckernel/sched/deadline.ckernel/sched/ext.c调度器要解决的问题很朴素CPU 数量有限 runnable 任务可能很多内核必须决定下一段 CPU 时间给谁。难点在于目标互相拉扯吞吐量希望少切换交互延迟希望快响应公平性希望按权重分配效率又要求每次选择足够便宜实时任务要求确定性普通任务要求平均公平。17.0 五维内容矩阵维度一启动/初始化调度子系统的早期入口是start_kernel() - sched_init()核心代码在kernel/sched/core.c。调用到这里时系统仍处于 boot CPU 单核执行阶段中断关闭普通进程还不能创建真正的多任务切换也还没有开始。它依赖早期内存管理已经建立到足以分配调度器对象和 per-CPU 数据也依赖异常与中断环境逐步成形因为后续上下文切换、抢占返回和时钟 tick 都要靠体系结构入口配合。sched_init()的工作可以拆成三步。第一步是数据结构准备为每个 CPU 初始化struct rq并在其中建立cfs_rq、rt_rq、dl_rq等运行队列。第二步是调度类框架就位stop_sched_class - dl_sched_class - rt_sched_class - fair_sched_class - idle_sched_class构成优先级链。第三步发生在rest_init()内核通过kernel_thread()创建 PID 1 的kernel_init路径和 PID 2 的kthreadd。完成后系统获得三项能力进程可创建任务可入队schedule()有了可选择的运行队列。调度器核心策略基本与架构无关但保存/恢复寄存器、切换内核栈和地址空间的底层动作落在 x86_64 的switch_to位于arch/x86/include/asm/switch_to.h及相关汇编路径。维度二原理/设计调度器的根本问题是CPU 时间如何在多个 runnable 任务之间分配同时让实时任务获得可预测延迟。这个问题没有单一最优解。时间片越大吞吐量和 cache 命中往往更好但交互任务响应会变慢调度越公平统计和树操作越复杂实时任务越强势普通任务越容易被饿死。Linux 用调度类隔离策略用统一的schedule()串起选择过程schedule() - __schedule() - stop_sched_class - dl_sched_class - rt_sched_class - fair_sched_class - idle_sched_class调度策略到调度类的映射由sched_setscheduler()等路径建立SCHED_NORMAL和SCHED_BATCH进入 fair 类SCHED_FIFO和SCHED_RR进入 RT 类SCHED_DEADLINE进入 DL 类SCHED_IDLE进入 idle 类。普通任务的核心思想来自 CFS/EEVDF用虚拟运行时间和虚拟截止时间描述任务相对公平份额优先运行“欠账更多或截止更早”的任务。维度三核心数据结构调度器对象关系可以压缩成一条链task_struct - sched_entity / sched_rt_entity / sched_dl_entity - cfs_rq / rt_rq / dl_rq - rq(per-CPU)task_struct是进程描述符调度相关字段包括prio、static_prio、normal_prio、policy、sched_class以及嵌入的se、rt、dl调度实体。struct rq是每 CPU 运行队列关键字段是curr、idle、nr_running、cfs、rt、dl和 rq 自身锁。struct cfs_rq管理 fair 类实体包含tasks_timeline、min_vruntime或 EEVDF 相关统计、nr_running/nr_queued、curr等。struct sched_entity是 fair 类中的可调度实体字段包括vruntime、deadline、load、run_node、on_rq、sum_exec_runtime。task_struct通常来自 fork 路径的专用 cache随进程创建和退出分配释放rq是 per-CPU 长生命周期对象启动后基本常驻sched_entity嵌在 task 或调度组对象中随所属对象存在。调度器热路径尽量避免临时分配因为调度本身正是系统在内存压力和锁竞争下仍必须可用的基础设施。维度四核心算法最核心的调度决策路径是schedule() - __schedule() - pick_next_task() - context_switch() - switch_to()pick_next_task()按调度类链选择最高优先级类中的下一个任务。fair 类的选择路径是fair_sched_class - pick_next_task_fair()在 CFS/EEVDF 队列中选择合适的sched_entity。传统表述是取红黑树中vruntime最小者在 EEVDF 语义下还要看 entity 是否 eligible、虚拟截止时间是否更早、lag 是否需要被保护。唤醒抢占路径是另一条热路径try_to_wake_up() - ttwu_do_wakeup() - check_preempt_curr() - resched_curr() - 设置 TIF_NEED_RESCHEDSMP 负载均衡路径则由周期性调度器或 idle balance 触发run_rebalance_domains() - load_balance() - find_busiest_group() - find_busiest_queue() - migrate task维度五调试与观测调度状态优先从运行时接口看/proc/sched_debug /proc/schedstat /proc/pid/sched tracepoint:sched_switch tracepoint:sched_wakeup tracepoint:sched_migrate_taskGDB 或 crash 调试可以从schedule、__schedule、pick_next_task_fair、try_to_wake_up、load_balance下断点。常见问题不是“调度器坏了”而是某类约束让任务没有机会运行RT 任务长期占 CPU、cgroup quota throttle、软中断占用、不可抢占区太长、CPU affinity 过窄或负载均衡被隔离配置限制。soft lockup 的典型诊断是结合 watchdog 日志和/proc/sched_debug确认某 CPU 当前任务是否长时间不发生调度。17.1 调度类架构Linux 用sched_class把不同策略放进同一框架。核心结构在kernel/sched/sched.hstructsched_class{void(*enqueue_task)(structrq*rq,structtask_struct*p,intflags);bool(*dequeue_task)(structrq*rq,structtask_struct*p,intflags);void(*wakeup_preempt)(structrq*rq,structtask_struct*p,intflags);int(*balance)(structrq*rq,structtask_struct*prev,structrq_flags*rf);structtask_struct*(*pick_task)(structrq*rq,structrq_flags*rf);structtask_struct*(*pick_next_task)(structrq*rq,structtask_struct*prev,structrq_flags*rf);void(*put_prev_task)(structrq*rq,structtask_struct*p,structtask_struct*next);void(*set_next_task)(structrq*rq,structtask_struct*p,bool first);void(*task_tick)(structrq*rq,structtask_struct*p,intqueued);void(*update_curr)(structrq*rq);};优先级链stop_sched_class - dl_sched_class - rt_sched_class - fair_sched_class - ext_sched_class 可选 - idle_sched_classstop 用于 CPU hotplug、迁移等特殊停止任务DL 实现 deadlineRT 实现 FIFO/RRfair 是普通任务 CFS/EEVDFidle 是兜底。17.2 核心数据结构task_struct中的调度字段在include/linux/sched.hstructtask_struct{intprio;intstatic_prio;intnormal_prio;unsignedintpolicy;structsched_entityse;structsched_rt_entityrt;structsched_dl_entitydl;structsched_ext_entityscx;conststructsched_class*sched_class;};每 CPU runqueue 是调度器的中心structrq{unsignedintnr_running;structtask_struct*curr;structtask_struct*idle;raw_spinlock_t__lock;structcfs_rqcfs;structrt_rqrt;structdl_rqdl;conststructsched_class*next_class;intcpu;};CFS/EEVDF 的队列structcfs_rq{structload_weightload;unsignedintnr_queued;s64 sum_w_vruntime;u64 sum_weight;u64 zero_vruntime;structrb_root_cachedtasks_timeline;structsched_entity*curr;structsched_entity*next;structsched_avgavg;};普通任务的调度实体structsched_entity{structload_weightload;structrb_noderun_node;u64 deadline;u64 min_vruntime;u64 min_slice;u64 max_slice;u64 exec_start;u64 sum_exec_runtime;u64 vruntime;s64 vlag;u64 vprot;u64 slice;structsched_avgavg;};关系图CPU - rq rq-cfs - cfs_rq-tasks_timeline - sched_entity - task_struct rq-rt - priority arrays rq-dl - deadline rb-tree17.3 EEVDF 核心思路传统 CFS 主要围绕vruntime选择最左实体。EEVDF 在保留虚拟时间的基础上引入 eligible、virtual deadline 和 lag任务只有在虚拟 lag 表示“有资格”时才参与选择选择时倾向虚拟截止时间更早者。简化模型weight 决定任务应得 CPU 份额 vruntime 记录已消费的加权运行时间 vlag 估计任务相对公平份额是欠账还是超前 deadline 用于 EEVDF 选择的虚拟截止时间 slice 影响 deadline 和延迟边界就绪队列仍然使用红黑树缓存节点但语义从“最小 vruntime”演进到“eligible entity 中合适的 earliest deadline”。延迟和滞后边界通过vlag、vprot、slice 保护等字段限制避免短任务无限插队或长任务长期饥饿。17.4 唤醒抢占唤醒路径会把任务 enqueue 到目标 rq然后调用调度类的wakeup_preempt()。CFS/EEVDF 的判断不是简单“新任务优先级更高”而是比较当前任务和新唤醒实体的虚拟时间状态、deadline、是否 eligible、是否需要降低交互延迟。结果是新任务明显落后或 deadline 更紧时可以设置 resched。当前任务仍在合理 slice 内时避免过度抢占。RT/DL 类因为优先级链高于 fair会在策略层面直接压过普通任务。更完整地看唤醒不是单点动作而是一条从等待队列到目标 CPU 的路径。典型睡眠任务在等待锁、I/O、futex、pipe 或 epoll 事件时挂在某个 wait queue 上事件发生后唤醒方调用try_to_wake_up()在合适的锁保护下把任务状态从 sleeping 改回 runnable然后选择 CPU 并入队。这个选择 CPU 的过程会综合 affinity、当前 CPU、上次运行 CPU、调度域拓扑和 wake affine。它既想让任务尽快运行又不想为了短任务频繁跨 CPU 迁移破坏 cache locality。try_to_wake_up()的核心约束是并发正确性。一个任务可能正在睡眠路径设置状态另一个 CPU 同时唤醒它一个任务可能刚从 runqueue 取下迁移路径又想移动它。调度器用pi_lock、rq lock、状态位和内存屏障把这些窗口收窄。理解唤醒抢占时不能只看最后的resched_curr()还要看到前面的状态同步只有任务确实被成功激活并且目标 rq 上的调度类认为它应该尽快运行抢占请求才有意义。fair 类的抢占判断体现了 EEVDF 的设计目标。旧 CFS 常用wakeup_granularity避免新唤醒任务因为极小的vruntime差异过度抢占EEVDF 语义下任务的deadline、vlag和 slice 共同决定“它是不是已经落后到值得抢占”。这使交互任务能更快获得 CPU同时保留对吞吐型任务的保护。对于 RT 和 Deadline情况更直接调度类优先级高于 fair只要新任务可运行且约束满足就会要求当前普通任务让出 CPU。17.5 负载均衡SMP 上每 CPU 一个 rq。负载均衡负责在 CPU 间迁移任务触发点包括 tick、新 idle、周期性 balance、wake affine 等。调度域sched_domain描述 CPU 拓扑root domain 聚合实时/deadline 约束。负载均衡的权衡是成本和收益迁移能提高 CPU 利用率和公平性但会损失 cache locality。现代调度还要考虑 capacity、NUMA、energy-aware scheduling、utilization clamping。调度域把硬件拓扑表达成层次SMT sibling、同一 core、同一 LLC、同一 NUMA node、跨 node。每层都有自己的 balance interval、imbalance_pct、flags 和 capacity 信息。负载均衡不是简单找“任务最多”的 CPU而是先判断调度组是否过载、是否有空闲 CPU、是否存在 capacity 不匹配再决定从 busiest group 拉取哪些任务。新 idle balance 是低延迟路径CPU 即将进入 idle 时如果同一调度域里有明显可迁移任务它会主动拉任务避免自己睡下而其他 CPU 排队。周期性 balance 则由 tick 或调度时钟推动更偏向全局公平。wake affine 则发生在唤醒时倾向把刚被唤醒的任务放到与唤醒者相关或 cache 更热的 CPU 上。三者服务不同场景新 idle 追求利用率周期性 balance 修正长期偏差wake affine 追求短期局部性。NUMA balancing 又增加了一层复杂性。任务的 CPU 位置和内存页位置如果长期分离会造成远端访问但迁移任务或迁移页都有成本。调度器、内存管理和 NUMA balancing 共同观察访问模式试图让任务靠近热内存或者让热页靠近任务。这个逻辑说明调度器并不是孤立分配 CPU 时间它和内存拓扑、缓存层级、电源模型共同决定实际性能。17.6 RT 和 DeadlineRT 调度使用优先级数组和 bitmap。SCHED_FIFO不主动时间片轮转SCHED_RR有轮转时间片。RT 任务高于 fair但受 RT bandwidth 控制避免完全饿死普通任务。Deadline 调度基于 CBS每个任务有 runtime、deadline、period。调度器按绝对 deadline 选择运行时消耗 runtimeruntime 用尽时推迟到下一周期补充。dl_rq使用按 deadline 排序的红黑树并维护带宽统计。RT 队列的核心是“固定优先级”。每个优先级一个队列bitmap 用来快速找到最高非空优先级。SCHED_FIFO任务一旦运行只要不阻塞、不主动让出、不被更高优先级抢占就可以持续运行SCHED_RR在同优先级任务之间按时间片轮转。这个模型简单、可预测但很容易饿死普通任务所以内核提供 RT runtime/bandwidth 控制让 RT 类在一个周期内最多消耗一定运行时间。Deadline 类解决的是另一类问题任务声明“每 period 需要 runtime必须在 deadline 前完成”。CBS 的意义在于把实时需求转化为带宽预算。任务运行会消耗 runtime预算耗尽时即使任务还想继续运行也要被推迟到下一周期。这样多个 deadline 任务可以按总带宽进行可行性控制避免单个任务超用 CPU 后破坏其他任务的 deadline。这三类调度策略可以放在同一优先级链中理解Deadline 关注时间约束RT 关注固定优先级Fair/EEVDF 关注长期公平和交互延迟。__schedule()不需要理解每种策略的全部细节它只需按sched_class链调用pick_next_task()。策略差异封装在类内部这就是调度类架构的价值。17.7 从 tick 到上下文切换调度并不只在系统调用返回时发生。触发调度的来源包括timer tick / hrtimer - scheduler_tick() - curr-sched_class-task_tick() - need_resched wakeup - try_to_wake_up() - enqueue_task() - wakeup_preempt() blocking - set_current_state() - schedule() explicit yield / preempt enable / irq return - 检查 TIF_NEED_RESCHED - schedule()__schedule()是核心切换点。它先把当前任务按状态决定是否留在 rq仍 runnable 的任务继续在队列里竞争阻塞任务从 rq 移除然后从最高优先级调度类开始选择下一个任务。选中后context_switch()负责 mm、寄存器、栈、体系结构状态和 rq 当前任务指针的切换。上下文切换的成本来自多处保存和恢复寄存器、切换内核栈、可能切换地址空间、污染 cache/TLB、打断流水线。调度器追求低延迟但不会为了每次微小优势都切换任务。EEVDF 的 slice、deadline、lag 控制本质上就是在“及时响应”和“少切换”之间放置可解释的边界。17.8 EEVDF 的字段如何协作可以把普通任务的选择拆成三个问题1. 任务是否 runnable是否在 cfs_rq 中 2. 任务是否 eligible它是否没有超前太多 3. 在 eligible 任务中选谁虚拟截止时间更早者优先vruntime仍然表示加权后的已运行时间。权重越大同样真实运行时间带来的vruntime增量越小因此高权重任务长期获得更多 CPU。vlag表示任务相对理想公平份额的偏离正负方向分别可理解为欠账或超前。deadline则把 slice 纳入选择短 slice 任务可以获得更早的虚拟截止时间从而改善交互响应。这种设计比“永远取最小 vruntime”更能表达延迟目标。一个任务即使vruntime较小如果已经超前或不 eligible也不该无限插队一个交互任务刚从睡眠回来如果相对公平份额落后且 deadline 更近就应该尽快运行。EEVDF 让公平和延迟不再混在单个vruntime排序里而是拆成 eligibility 与 deadline 两层判断。源码阅读时要抓住几个点enqueue 时更新实体的虚拟时间和 deadlinepick 时从tasks_timeline中寻找合适实体tick 和 dequeue 时更新当前实体的运行时间层级 group scheduling 下调度实体可能代表一个 task也可能代表一个 task group。后者会把“组之间公平”和“组内任务公平”串起来形成多层 cfs_rq。17.9 调度器与锁、抢占模型调度器本身位于并发控制的中心。rq lock 保护每 CPU runqueue迁移时可能同时涉及两个 rq需要按地址或 CPU 顺序加锁以避免死锁。preempt_count控制当前上下文能否被抢占中断、软中断、持有 raw spinlock 的区域都可能推迟调度。PREEMPT_DYNAMIC 让同一个内核可以在不同抢占模型之间切换从服务器吞吐优先到桌面低延迟优先。PREEMPT_RT 则把许多原本不可睡眠的锁和中断路径改造成可抢占、可线程化的形态以降低最坏延迟。调度器必须同时服务这些配置因此很多代码会区分 raw spinlock、普通 spinlock、irq state 和 preempt disable 边界。调度器与 RCU 也紧密相连。上下文切换、idle 进入退出、用户态/内核态边界都会影响 RCU quiescent state 的判断。一个 CPU 长时间运行不可抢占内核代码会拖延 grace period实时化和抢占模型变化也会改变 RCU 的实现细节。第10章看到的 RCU 和本章调度器实际上在运行时互相提供时序信息。17.10 观察一次调度决策读/proc/pid/sched时可以把字段分成几组se.sum_exec_runtime 任务累计运行时间 se.vruntime 公平类虚拟运行时间 nr_switches 上下文切换次数 se.avg.* PELT 负载/util 信号 policy/prio 调度策略和优先级读/proc/sched_debug时应先看 CPU/rq 层面的nr_running、curr、cfs/rt/dl 队列再看调度域和负载均衡信息。tracepointsched_switch能告诉你谁切给谁sched_wakeup能看到唤醒者和被唤醒者sched_migrate_task能看到迁移。把三者按时间线连起来通常可以解释一次延迟尖峰任务什么时候被唤醒、被放到哪个 CPU、是否迁移、何时真正运行。调试建议不是只打__schedule()。如果问题是“为什么没有被唤醒”看try_to_wake_up()如果问题是“为什么唤醒后不抢占”看wakeup_preempt_entity()或 fair 类 pick 路径如果问题是“为什么跨 CPU 跑”看 select task rq 和 load balance如果问题是“实时任务抢占异常”看 rt_rq、dl_rq 和 bandwidth 状态。17.11 源码阅读路线从schedule()到pick_next_task()调度器源码容易迷路因为入口很多。建议先抓一条最普通的主动睡眠路径schedule() - __schedule(SM_NONE) - prev_state READ_ONCE(prev-__state) - deactivate_task() 或保留 runnable - pick_next_task() - context_switch()schedule()的调用者通常已经设置好当前任务状态。例如等待 I/O 的任务先把状态设为TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE再调用schedule()如果在真正切走前事件已经发生唤醒方可能把状态改回 runnable调度器就不能把它从 rq 移除。这就是状态检查和锁顺序如此重要的原因。pick_next_task()体现调度类链。它先看是否能走 fair fast path否则按 stop、dl、rt、fair、ext、idle 的顺序询问各类。高优先级类只要有可运行任务就不会落到低优先级类。普通任务平时都在 fair 类里竞争当系统存在 deadline 或 RT 任务时fair 类可能长时间没有机会运行这时 RT bandwidth 和 DL admission control 就是保护系统可用性的关键。context_switch()不只是换一个 C 指针。它会处理地址空间切换、体系结构寄存器、内核栈、FPU 状态延迟保存、lockdep/RCU/accounting 钩子。对于同一进程的两个线程mm 相同切换成本相对低对于不同进程可能需要切 CR3 或使用 PCID/ASID 优化 TLB 影响。调度策略选择的是“谁运行”体系结构代码负责“怎么切过去”。17.12 PELT、uclamp 与能耗感知现代调度器不仅看 runnable 数量还要估计任务负载。PELTPer-Entity Load Tracking用指数衰减方式记录任务和 rq 的 runnable/load/util 信号。短时间运行的任务不会立刻被当成满负载长期运行的任务 util 会逐步上升任务睡眠后 util 逐步衰减。这些信号喂给负载均衡、CPU frequency、EAS 和 uclamp。utilization clamping 提供上下限约束。uclamp.min可以保证任务至少按某个利用率看待避免延迟敏感任务被放到太弱的 CPU 或频率太低uclamp.max可以限制后台任务不要拉高频率。移动设备和混合大小核平台尤其依赖这个机制因为“公平地分 CPU 时间”并不等于“选择合适性能/能耗点”。Energy-aware scheduling 会把 CPU capacity、能耗模型和任务 util 结合起来。小核可能能效高但容量低大核容量高但耗电大。调度器在选择 CPU 时要判断任务放到哪个 CPU 能满足 util同时使能耗估计更低。这个过程不是替代公平调度而是在 select task rq 和负载均衡阶段影响放置决策。服务器也有类似问题只是约束更多体现为 NUMA、cache 和 SMT。这些机制说明 Linux 调度器已经从“队列里挑下一个任务”扩展成“在拓扑、能耗、实时性、公平性之间做动态决策”。EEVDF 处理 fair 类内部的延迟与公平PELT/uclamp/EAS 处理任务放置和性能信号RT/DL 处理显式实时约束。它们共同构成 7.1.3 中调度子系统的完整面貌。17.13 调度组与 cgroup普通任务的公平性不只发生在 task 之间也发生在 task group 之间。启用 group scheduling 后cgroup 可以拥有自己的调度实体。父级cfs_rq看到的是代表整个组的sched_entity组内部再有自己的cfs_rq管理成员任务。这样 CPU 权重可以按服务、容器或用户分配而不是让拥有更多线程的组天然获得更多 CPU。层级调度的选择路径可以理解为多次 pick先在父 cfs_rq 中选中某个组实体再进入组内 cfs_rq 选具体任务。如果组内还有子组就继续向下。运行时间也会沿层级累计保证父级看到的组消耗能反映子任务实际运行。这个模型让cpu.weight、cpu.max等控制接口有了内核基础。cgroup quota 与普通公平不同。quota 限制一个周期内最多可用 CPU 时间超过后整个 cgroup 会被 throttle即使其中还有 runnable 任务也不能继续运行。排查容器“CPU 很空但进程卡住”时要检查 cgroup CPU throttling 统计。调度器此时不是找不到任务而是资源控制明确禁止该组继续消耗 CPU。17.14 失败模式和经验判断调度问题经常表现为延迟而不是崩溃。常见原因包括RT 任务长期占用 CPU、cgroup quota 过低、负载均衡迁移过度、NUMA 位置不佳、软中断挤占、不可抢占内核区太长、CPU isolation 配置和实际中断亲和性不一致。分析时应把“任务没运行”拆成几个问题它是否 runnable是否在正确 rq是否被更高类任务压住是否被 cgroup throttle是否被 IRQ/softirq 占用是否被迁移到远端 CPU。一个实用的观察顺序是先用top、pidstat、perf sched或 tracepoint 确认 runnable 延迟再用/proc/pid/sched看nr_switches、wait sum 和 PELT再看/proc/sched_debug的 rq 状态最后才进入源码断点。不要一开始就断在__schedule()因为调度器每秒可能运行海量次数没有清晰问题假设时很难从断点中得到结论。本章的核心结论是调度器不是单个算法而是一组分层决策。sched_class决定策略优先级rq/cfs_rq 保存局部状态EEVDF 处理 fair 任务选择RT/DL 处理实时约束负载均衡处理跨 CPU 放置PELT/uclamp/EAS 处理性能和能耗信号。理解这些层次后再读kernel/sched/core.c和kernel/sched/fair.c代码路径会清晰得多。17.15 sched_extLinux 7.1.3 包含kernel/sched/ext.c。sched_ext 允许 BPF scheduler 通过sched_ext_ops接管一部分普通调度策略。它不是替代 core scheduler而是在调度类框架中增加可扩展层。关键点BPF struct_ops 提供 select_cpu、enqueue、dispatch、tick 等回调。异常、超时或 lockup 时可以 disable 并回退。它把调度策略实验从修改内核 C 代码转移到受约束的 BPF 接口。17.16 版本演进5.12 到 7.1.3CFS 语义从单纯 vruntime 红黑树逐步演进到 EEVDF目标是更明确地表达延迟和公平份额。sched_ext 进入主线后调度策略扩展有了 BPF struct_ops 通道。PREEMPT_DYNAMIC 让内核可以在不同抢占模型间切换PREEMPT_RT 持续把实时语义推进主线。uclamp 和 EAS 让调度器能把性能、能耗和任务利用率约束纳入决策。17.17 调试观察点GDB 断点sched_init __schedule pick_next_task_fair enqueue_task_fair wakeup_preempt_entity load_balance运行时接口/proc/sched_debug /proc/pid/sched /sys/kernel/debug/sched/ tracepoints: sched_switch, sched_wakeup, sched_migrate_task下一章内存管理子系统深度剖析。