ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux CFS 调度器深度解析:调度时机、选任务与负载均衡

Linux CFS 调度器深度解析:调度时机、选任务与负载均衡 1. 从一个真实场景说起为什么你的服务器会突然卡顿很多运维和开发同学都遇到过这种情况一台跑着几十个服务的 Linux 机器CPU 使用率看起来只有 40%但业务响应时间却突然飙升日志里出现大量超时。你登录上去用top一看发现load average高得离谱但 CPU 又没跑满。这时候大概率不是你的代码有问题而是调度子系统在某个环节出现了瓶颈。Linux 的进程调度器是整个操作系统的交通警察它决定了哪个进程什么时候上 CPU、跑多久、跑完了下一个该轮到谁。而 CFSCompletely Fair Scheduler完全公平调度器从 2007 年进入内核主线以来一直是普通进程调度的默认选择。它的核心思想说起来很简单——让每个进程都能公平地分到 CPU 时间但真正落地到代码层面涉及调度时机、选任务逻辑、负载均衡三大块每一块都有大量细节值得深挖。这篇文章面向的是有一定 Linux 基础、想深入理解内核调度机制的开发者和运维人员。我会从 CFS 的设计思路讲起把调度时机、选任务、负载均衡这三条主线拆开揉碎配上内核源码里的关键结构和参数计算过程最后给出实际排查调度问题的思路。读完你至少能搞清楚为什么你的进程会被饿死、为什么 CPU 没跑满但延迟很高、以及sched_class这套分级机制到底是怎么工作的。2. CFS 调度器的整体设计思路拆解2.1 为什么需要完全公平从 O(1) 调度器说起在 CFS 之前Linux 用的是 O(1) 调度器。O(1) 的核心思路是给每个进程分配固定时间片用两个优先级数组活跃数组和过期数组来管理调度时从活跃数组最高优先级链表里取第一个进程。这个设计在常数时间内完成调度效率很高但问题也很明显它依赖复杂的启发式规则来判断进程是交互型还是CPU 密集型这些启发式规则经常判断错误导致交互式进程响应变慢。CFS 的提出者 Ingo Molnar 换了一个思路与其费劲去猜进程类型不如直接模拟一个理想的多任务 CPU。这个理想 CPU 的特点是每个进程能同时运行各自拿到 1/n 的 CPU 时间n 是进程数。真实 CPU 一次只能跑一个进程所以 CFS 用虚拟运行时间vruntime来记录每个进程已经跑了多久每次选 vruntime 最小的那个进程上 CPU。这样跑得少的进程 vruntime 小自然会被优先调度公平性就得到了保证。这个设计的美妙之处在于它不需要任何启发式规则来判断进程类型交互式进程因为经常睡眠vruntime 增长慢自然会被优先调度CPU 密集型进程 vruntime 增长快会被适当压制。一切都在数学框架内自然发生。2.2 sched_class 分级机制调度器不是只有一个Linux 内核里调度器不是一个单一实体而是一套分级结构通过sched_class来组织。每个调度类有自己的优先级内核在选任务时从最高优先级的调度类开始遍历找到第一个有可运行任务的类然后从该类里选任务。当前内核里主要的调度类按优先级从高到低排列调度类用途典型场景stop_sched_class停止 CPU用于热插拔和迁移CPU 热插拔dl_sched_class截止时间调度实时性要求极高的任务rt_sched_class实时调度工业控制、音视频fair_sched_classCFS 公平调度普通用户进程idle_sched_class空闲调度CPU 没事干时这个分级结构的意义在于实时任务永远优先于普通任务普通任务永远优先于空闲任务。CFS 属于fair_sched_class它管理的是绝大多数普通进程。理解这个层级关系很重要因为很多进程被饿死的问题根源就是高优先级调度类里的任务一直占着 CPUCFS 里的任务根本没机会跑。2.3 调度实体与调度组CFS 的基本管理单元CFS 不直接管理进程而是管理调度实体sched_entity。每个进程对应一个调度实体但如果开启了组调度CONFIG_FAIR_GROUP_SCHED每个控制组cgroup也会对应一个调度实体。这样 CFS 就可以在组之间做公平调度而不是在单个进程之间做公平调度。这个设计解决了一个实际问题假设用户 A 起了 100 个进程用户 B 起了 1 个进程如果按进程做公平调度用户 B 的进程只能拿到 1/101 的 CPU这显然不公平。按组调度的话用户 A 和用户 B 各占一个组各拿 50% 的 CPU用户 A 的 100 个进程再分这 50%每个进程拿 0.5%用户 B 的进程拿 50%。这才是真正的公平。调度实体的关键字段包括struct sched_entity { struct load_weight load; // 权重决定分配比例 struct rb_node run_node; // 红黑树节点 u64 vruntime; // 虚拟运行时间 u64 sum_exec_runtime; // 实际运行时间 // ... 其他字段 };load字段是权重由进程的 nice 值映射而来。nice 值范围是 -20 到 19nice 越小优先级越高权重越大。内核用一张sched_prio_to_weight表做映射nice 0 的权重是 1024nice 每降低 1权重增加约 25%。这个 25% 不是随便定的它保证 nice 值相差 1 的两个进程CPU 时间分配比例大约是 1.25:1。3. 调度时机什么时候会触发调度3.1 主动调度进程自己让出 CPU主动调度是指进程自己调用schedule()让出 CPU。最常见的场景是进程等待某个资源时比如等 I/O、等锁、等信号量。以等 I/O 为例进程调用read()读文件如果数据不在页缓存里进程会被挂起调用schedule()让出 CPU等 I/O 完成后再被唤醒。主动调度的入口是schedule()函数它会调用__schedule()完成实际工作。__schedule()的核心流程是关闭内核抢占获取当前 CPU 的rq运行队列调用pick_next_task()选出下一个任务如果下一个任务不是当前任务调用context_switch()做上下文切换重新开启内核抢占这里有个细节值得注意schedule()在调用时会检查preempt_count如果处于原子上下文比如持有自旋锁调用schedule()会触发警告。因为原子上下文里不能睡眠睡眠就意味着让出 CPU这会导致死锁。3.2 被动调度内核抢占与时间片耗尽被动调度是指进程没有主动让出 CPU但内核强制把它换下来。触发条件主要有两个时间片耗尽和内核抢占。时间片耗尽比较好理解CFS 给每个进程分配一个时间片叫sched_slice进程跑完这个时间片后会被标记为需要重新调度。时间片的计算方式是sched_slice (sched_period * se-load.weight) / cfs_rq-load.weight其中sched_period是调度周期默认是sysctl_sched_latency通常 6ms和sysctl_sched_min_granularity * nr_running的较大值。sysctl_sched_min_granularity默认是 0.75ms这个值保证每个进程至少能跑 0.75ms避免频繁切换导致开销过大。内核抢占是另一个触发点。当进程在内核态运行时如果满足抢占条件比如高优先级任务被唤醒内核会设置TIF_NEED_RESCHED标志在合适的时机比如中断返回、系统调用返回检查这个标志并触发调度。内核抢占的检查点包括中断处理程序返回时系统调用返回用户态时显式调用preempt_enable()时3.3 调度时机与延迟的权衡调度时机的选择直接影响系统延迟和吞吐量。如果调度太频繁上下文切换开销会吃掉大量 CPU如果调度太少交互式任务响应会变慢。CFS 用两个参数来平衡这个矛盾sysctl_sched_latency和sysctl_sched_min_granularity。前者是目标调度延迟后者是最小抢占粒度。当可运行任务数nr_running超过sysctl_sched_latency / sysctl_sched_min_granularity时调度周期会按nr_running * sysctl_sched_min_granularity来算保证每个任务至少能跑sysctl_sched_min_granularity。举个例子假设sysctl_sched_latency是 6mssysctl_sched_min_granularity是 0.75ms那么当nr_running超过 8 时调度周期会变成nr_running * 0.75ms。如果有 16 个任务调度周期就是 12ms每个任务平均跑 0.75ms。这个设计保证了任务数很多时单个任务不会因为时间片太短而频繁切换。注意sysctl_sched_latency和sysctl_sched_min_granularity可以通过/proc/sys/kernel/下的对应文件调整但生产环境不建议随意改除非你明确知道自己在做什么。改小了会增加切换开销改大了会增加延迟。4. 选任务CFS 怎么挑下一个上 CPU 的进程4.1 红黑树CFS 的核心数据结构CFS 用红黑树来管理可运行的调度实体按vruntime排序最左边的节点就是vruntime最小的也就是下一个该上 CPU 的进程。红黑树的好处是插入、删除、查找最左节点都是 O(log n) 复杂度在任务数很多时性能依然稳定。红黑树的节点是sched_entity里的run_node每个 CPU 的运行队列cfs_rq里有一棵红黑树tasks_timeline。选任务时调用__pick_first_entity()直接取红黑树最左节点static struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq) { struct rb_node *left rb_first_cached(cfs_rq-tasks_timeline); if (!left) return NULL; return rb_entry(left, struct sched_entity, run_node); }这里用了rb_first_cached红黑树会缓存最左节点避免每次都从根节点遍历到最左。这个优化在任务数很多时能省不少时间。4.2 vruntime 的计算与更新vruntime是 CFS 的核心它的计算方式是vruntime delta_exec * (NICE_0_LOAD / se-load.weight)其中delta_exec是本次实际运行时间NICE_0_LOAD是 nice 0 的权重1024se-load.weight是当前进程的权重。这个公式的含义是权重越大的进程vruntime增长越慢所以能跑更长时间权重越小的进程vruntime增长越快会被更快换下来。举个例子进程 A 的 nice 是 0权重 1024进程 B 的 nice 是 5权重 335。两个进程各跑了 1msA 的vruntime增加1 * 1024/1024 1msB 的vruntime增加1 * 1024/335 ≈ 3.06ms。所以 B 的vruntime增长更快下次调度时 A 会被优先选中。这符合预期nice 值高的进程优先级低应该少跑。vruntime的更新在update_curr()里完成这个函数在每次时钟中断和调度时都会被调用。它还会更新cfs_rq的min_vruntime这个值是新进程加入红黑树时的初始vruntime基准保证新进程不会因为vruntime太小而抢占太多 CPU。4.3 新进程的 vruntime 初始化防止新进程饥饿新进程创建时vruntime不能直接设为 0否则它会因为vruntime最小而一直抢占 CPU导致老进程被饿死。CFS 的做法是把新进程的vruntime设为cfs_rq-min_vruntime也就是当前红黑树里最小的vruntime。但这里有个细节如果新进程的vruntime直接等于min_vruntime它可能会比红黑树里最左节点的vruntime还小导致它插到最左边立刻抢占 CPU。为了避免这个问题CFS 会把新进程的vruntime设为min_vruntime sched_vslice其中sched_vslice是新进程一个时间片对应的虚拟时间。这样新进程的vruntime会略大于当前最小vruntime不会立刻抢占 CPU但也不会被饿死。这个设计体现了 CFS 的公平性哲学新进程有资格跑但不能因为新就获得额外优势。它需要和其他进程一样按vruntime排队。4.4 唤醒抢占交互式进程的快速响应进程从睡眠中被唤醒时CFS 会检查它是否应该抢占当前进程。检查逻辑在check_preempt_wakeup()里核心是比较唤醒进程和当前进程的vruntimeif (wakeup_preempt_entity(se, pse) 1) { // 唤醒进程的 vruntime 比当前进程小很多触发抢占 resched_curr(rq); }wakeup_preempt_entity()的判断条件是唤醒进程的vruntime加上一个补偿值sysctl_sched_wakeup_granularity后仍然小于当前进程的vruntime。这个补偿值默认是 1ms它的作用是避免频繁抢占如果两个进程vruntime差不多就不抢占减少上下文切换开销。这个机制对交互式进程很友好交互式进程经常睡眠vruntime增长慢唤醒时vruntime通常比 CPU 密集型进程小所以能快速抢占 CPU保证响应速度。这也是为什么 CFS 在桌面和服务器场景下都能表现不错的原因。5. 负载均衡多核时代 CFS 的必修课5.1 为什么需要负载均衡单核调度不够用CFS 的红黑树是 per-CPU 的每个 CPU 有自己的运行队列。如果只做单核调度会出现一个问题CPU 0 上堆了 10 个任务CPU 1 上只有 1 个任务CPU 0 的任务要等很久才能跑一次CPU 1 却大部分时间空闲。负载均衡就是解决这个问题的把任务从忙的 CPU 迁移到闲的 CPU让所有 CPU 的负载尽量均衡。负载均衡的触发时机主要有三个周期性负载均衡由scheduler_tick()触发每隔一段时间检查一次空闲负载均衡CPU 要进入空闲状态时触发试图从其他 CPU 拉任务过来新任务唤醒负载均衡新任务唤醒时选择最合适的 CPU 运行5.2 调度域与调度组负载均衡的层级结构负载均衡不是在所有 CPU 之间盲目迁移而是按调度域sched_domain分层组织的。调度域是一个树形结构最底层是 SMT 域同一个物理核的超线程往上是 MC 域同一个物理 CPU 的多个核再往上是 NUMA 域同一个 NUMA 节点最顶层是全局域。每个调度域有自己的负载均衡策略和参数。比如 SMT 域里超线程之间的迁移成本很低可以频繁迁移NUMA 域里跨节点迁移成本很高要尽量避免。这个分层结构保证了负载均衡既高效又不会引入过多迁移开销。调度域里的每个 CPU 属于一个调度组sched_group负载均衡在调度组之间进行。每个调度组有一个sg_lb_stats结构记录组内的负载、任务数、CPU 容量等信息。负载均衡时内核会比较各个调度组的负载从最忙的组里拉任务到最闲的组。5.3 负载计算不是简单数任务个数负载均衡的核心是负载的计算。早期内核用任务数作为负载但这样不准确一个 nice 0 的任务和一个 nice 19 的任务对 CPU 的压力完全不同。现在内核用负载权重load.weight来算负载nice 0 的任务权重 1024nice 19 的任务权重 15前者对负载的贡献是后者的 68 倍。但光有负载权重还不够还要考虑 CPU 的容量capacity。不同 CPU 的算力可能不同比如大小核架构里大核容量是 1024小核容量可能只有 512。负载均衡时要把负载和容量一起考虑用load / capacity作为实际压力指标。负载计算还涉及负载跟踪load tracking机制。早期内核用瞬时负载但瞬时负载波动太大容易导致误迁移。现在内核用 PELTPer-Entity Load Tracking算法对负载做指数衰减平均得到更平滑的负载值。PELT 的核心公式是load_avg load_avg * decay new_load * (1 - decay)其中decay是衰减因子由时间差决定。这个算法能同时反映历史负载和当前负载避免因为瞬时波动做出错误迁移决策。5.4 迁移决策什么时候该搬什么时候不该搬负载均衡的迁移决策要考虑两个因素不均衡程度和迁移成本。不均衡程度用imbalance表示是忙 CPU 负载和闲 CPU 负载的差值。迁移成本包括缓存失效、TLB 刷新、NUMA 远程访问延迟等。内核用sysctl_sched_migration_cost参数来估算迁移成本默认是 500us。如果任务在 CPU 上运行时间不到 500us认为它还在缓存热区迁移成本高不迁移如果运行时间超过 500us缓存已经冷了迁移成本低可以迁移。迁移决策的流程大致是计算当前 CPU 和候选 CPU 的负载差如果负载差小于sysctl_sched_nr_migrate对应的阈值不迁移如果负载差足够大尝试从忙 CPU 拉任务拉任务时优先选缓存冷的任务避免迁移热任务这里有个经验sysctl_sched_migration_cost和sysctl_sched_nr_migrate这两个参数对负载均衡效果影响很大。如果发现任务迁移太频繁可以适当调大sysctl_sched_migration_cost如果发现负载均衡不及时可以适当调小。但生产环境调整要谨慎最好先在测试环境验证。6. 实操排查调度问题的诊断思路与工具6.1 常用工具与观测指标排查调度问题首先要能观测。常用的工具和指标包括工具用途关键指标top / htop查看 CPU 使用率和负载load average, %CPUvmstat查看上下文切换和运行队列cs, r, bpidstat查看进程级调度统计%CPU, cswch/s, nvcswch/sperf sched分析调度延迟和迁移sched:sched_switch/proc/sched_debug查看 CFS 运行队列详情vruntime, nr_running/proc/sched_debug是排查 CFS 问题的利器它能显示每个 CPU 的cfs_rq信息包括nr_running、min_vruntime、红黑树里每个调度实体的vruntime和load。通过对比不同 CPU 的nr_running和min_vruntime能快速判断负载是否均衡。6.2 典型问题一CPU 没跑满但延迟高这个问题的典型表现是top里 CPU 使用率不高但业务延迟很高vmstat里r列运行队列长度很大。原因通常是任务在等 CPU但 CPU 又被某些任务占着不放。排查思路用pidstat -w看上下文切换次数如果nvcswch/s非自愿切换很高说明任务被强制换下可能是时间片太短用/proc/sched_debug看nr_running如果某个 CPU 的nr_running远大于其他 CPU说明负载不均衡用perf sched record抓一段调度事件用perf sched latency看调度延迟分布如果确认是负载不均衡可以检查sysctl_sched_migration_cost是否设得太大导致任务不愿意迁移。也可以检查是否有 CPU 亲和性设置taskset或cgroup cpuset把任务绑死在某个 CPU 上。6.3 典型问题二进程被饿死进程被饿死的表现是某个进程长时间得不到 CPUtop里%CPU一直是 0但进程状态是 R可运行。原因可能是高优先级调度类里有任务一直占着 CPU比如实时任务进程的 nice 值太高权重太低抢不过其他进程进程被 cgroup 限制了 CPU 配额排查思路用chrt -p pid看进程的调度策略如果是 SCHED_FIFO 或 SCHED_RR说明是实时任务会一直占 CPU 直到自己让出用cat /proc/pid/stat看 nice 值如果 nice 是 19权重只有 15确实抢不过 nice 0 的进程用cat /sys/fs/cgroup/cpu/group/cpu.cfs_quota_us看 cgroup 的 CPU 配额如果是 -1 表示不限制如果是具体数值说明有配额限制如果是实时任务导致的可以考虑用chrt调整实时任务的优先级或者用cgroup限制实时任务的 CPU 使用。如果是 nice 值问题可以用renice调整。如果是 cgroup 配额问题可以调整cpu.cfs_quota_us和cpu.cfs_period_us。6.4 典型问题三上下文切换过多上下文切换过多会导致 CPU 大量时间花在切换上而不是干活。vmstat里cs列很高比如超过 10 万pidstat -w里cswch/s很高都说明切换频繁。原因可能是任务数太多每个任务时间片太短锁竞争激烈任务频繁睡眠和唤醒中断太多每次中断都可能触发调度排查思路用perf stat -e context-switches看切换次数用perf record -e sched:sched_switch抓切换事件看是哪些任务在频繁切换用perf trace看系统调用和中断频率如果是任务数太多可以考虑合并任务或减少并发度。如果是锁竞争可以用perf lock分析锁热点。如果是中断太多可以检查网卡多队列、中断亲和性等配置。提示调整调度参数前一定要先备份当前值并在测试环境验证。生产环境直接改参数可能导致更严重的问题。比如把sysctl_sched_latency调得太小会导致上下文切换暴增调得太大会导致交互式任务响应变慢。7. 参数调优与内核编译选项7.1 关键 sysctl 参数速查CFS 的行为受多个 sysctl 参数控制这些参数在/proc/sys/kernel/下参数默认值作用调整建议sched_latency_ns6ms目标调度延迟交互式场景可调小吞吐场景可调大sched_min_granularity_ns0.75ms最小抢占粒度一般不动任务数多时可适当调大sched_wakeup_granularity_ns1ms唤醒抢占补偿交互式场景可调小减少唤醒延迟sched_migration_cost_ns500us迁移成本估算迁移频繁时调大负载不均时调小sched_nr_migrate32单次迁移最大任务数一般不动大核数机器可适当调大sched_tunable_scaling1参数自动缩放一般不动这些参数的调整要结合具体场景。比如跑数据库的服务器更看重吞吐量可以适当调大sched_latency_ns减少切换开销跑 Web 服务的服务器更看重响应延迟可以适当调小sched_latency_ns和sched_wakeup_granularity_ns。7.2 内核编译选项组调度与自动调度组CFS 有两个重要的编译选项CONFIG_FAIR_GROUP_SCHED开启组调度让 cgroup 之间公平分配 CPUCONFIG_SCHED_AUTOGROUP开启自动调度组自动为每个 TTY 会话创建调度组CONFIG_FAIR_GROUP_SCHED对桌面和服务器都很重要。桌面场景下它能保证前台应用和后台任务公平分 CPU服务器场景下它能保证不同 cgroup 之间的公平性。CONFIG_SCHED_AUTOGROUP主要面向桌面服务器一般不开因为服务器通常用 cgroup 手动管理。这两个选项在主流发行版的内核里默认都是开的。如果你自己编译内核建议保持开启除非有特殊需求。7.3 实际调优案例从延迟 200ms 到 20ms我之前遇到过一个案例一台跑着 Web 服务的机器P99 延迟一直在 200ms 左右但 CPU 使用率只有 30%。用perf sched latency分析后发现调度延迟从任务唤醒到实际运行的时间平均 50msP99 达到 180ms。排查过程用/proc/sched_debug看nr_running发现某些 CPU 的nr_running达到 20而其他 CPU 只有 2-3用perf sched record抓调度事件发现任务迁移很少检查sysctl_sched_migration_cost_ns发现是默认值 500us检查 CPU 亲和性发现 Web 服务进程被绑在了 CPU 0-3 上但机器有 16 个核问题根源是 CPU 亲和性设置不合理导致任务集中在 4 个核上其他 12 个核空闲。调整亲和性后nr_running分布均匀了调度延迟降到 20ms 以下。这个案例说明调度问题不一定是调度器本身的问题很多时候是配置问题。排查时要先看全局再看局部不要一上来就调调度参数。8. 几个容易踩的坑与经验总结8.1 坑一把 nice 值当成绝对优先级很多同学以为 nice 值决定了进程的绝对优先级nice 0 一定比 nice 5 先跑。实际上 CFS 是按vruntime调度的nice 值只影响vruntime的增长速度。如果 nice 5 的进程睡眠了很久vruntime很小它照样能抢占 nice 0 的进程。这个特性对交互式进程有利但对某些场景可能造成困扰。比如你希望某个批处理任务严格让位于在线任务光靠 nice 值不够还需要用 cgroup 的 CPU 配额或者实时调度策略。8.2 坑二忽略 cgroup 的 CPU 限制cgroup 的 CPU 子系统可以限制一个组的 CPU 使用。如果某个服务被限制了cpu.cfs_quota_us即使系统整体 CPU 空闲这个服务也跑不快。排查调度问题时一定要检查 cgroup 配置。cpu.cfs_quota_us和cpu.cfs_period_us的比值决定了 CPU 上限。比如quota_us是 50000period_us是 100000表示这个组最多用 50% 的 CPU。如果quota_us是 -1表示不限制。8.3 坑三实时任务把系统跑死实时任务SCHED_FIFO / SCHED_RR优先级高于 CFS如果实时任务进入死循环会一直占着 CPU导致 CFS 里的任务完全跑不了。更严重的是如果实时任务占满了所有 CPU连内核线程都跑不了系统会看起来像死机。内核有个保护机制叫 RT throttling默认给实时任务 95% 的 CPU 带宽留 5% 给 CFS。这个机制可以通过/proc/sys/kernel/sched_rt_runtime_us调整。如果发现实时任务导致系统卡顿可以检查这个参数是否被改成了 -1表示不限制。8.4 经验观测先行调整在后调度调优最忌讳的就是拍脑袋调参数。我见过太多人一遇到延迟高就调sched_latency_ns结果越调越糟。正确的做法是先用工具观测确认问题确实出在调度上分析是调度时机、选任务还是负载均衡的问题针对具体问题调整对应参数调整后继续观测验证效果调度器是一个复杂的系统参数之间相互影响。调一个参数可能影响其他行为所以每次只调一个参数观察一段时间再决定下一步。8.5 经验理解 sched_class 的优先级关系很多调度问题的根源是调度类优先级。实时任务永远优先于 CFS 任务CFS 任务永远优先于 idle 任务。如果你发现 CFS 任务跑不起来先检查有没有实时任务在占 CPU如果实时任务也跑不起来检查有没有 stop 类任务比如 CPU 热插拔在运行。理解这个层级关系能帮你快速定位问题在哪一层。/proc/sched_debug里会显示每个 CPU 上各个调度类的运行队列情况是排查这类问题的好帮手。9. 从源码到实践进一步深入的方向如果你想把 CFS 吃透光看文章不够还得动手。建议从这几个方向深入第一读源码。CFS 的核心代码在kernel/sched/fair.c这是个大文件建议按功能分块读先读update_curr()理解vruntime更新再读pick_next_task_fair()理解选任务最后读load_balance()理解负载均衡。读的时候配合trace_printk或者perf probe加断点观察实际运行时的变量值。第二做实验。用cgroup创建不同权重的组跑 CPU 密集型任务观察 CPU 分配比例是否符合权重比。用taskset绑定 CPU观察负载均衡行为。用chrt创建实时任务观察 RT throttling 的效果。实验能帮你把理论知识和实际行为对应起来。第三用工具分析。perf sched是分析调度行为的利器perf sched record抓事件perf sched latency看延迟perf sched map看任务在 CPU 间的迁移。/proc/sched_debug和/proc/schedstat能提供运行队列的详细信息。把这些工具用熟排查调度问题会快很多。第四关注内核社区。CFS 不是一成不变的内核社区一直在优化。比如最近几年引入的 EEVDFEarliest Eligible Virtual Deadline First调度器就是对 CFS 的改进用虚拟截止时间代替单纯的vruntime排序进一步提升了公平性和延迟表现。关注这些变化能让你对调度的理解保持更新。调度子系统的学习曲线比较陡但一旦理解了核心机制很多看似玄学的问题都会变得有迹可循。我在实际工作中最大的体会是不要怕看源码也不要怕做实验调度器的行为是确定的只要观测到位总能找到原因。
返回列表