第22天:CFS 调度:完全公平调度的核心原理
从抢跑到公平Linux 进程调度的进化之路想象一下这样的场景在一个繁忙的餐厅里厨师需要为几十桌客人准备饭菜。如果厨师只专注于一桌客人的菜其他桌的客人可能会等得不耐烦但如果厨师频繁切换又会影响做菜的效率。Linux 内核中的进程调度器就像这位厨师它需要在多个进程之间分配 CPU 时间既要保证每个进程都能得到执行机会又要尽可能提高系统整体性能。在 Linux 2.6.23 版本之前内核使用的是基于优先级的 O(1) 调度器。这种调度器虽然效率很高但存在一个严重的问题高优先级进程可能会饿死低优先级进程。为了解决这个问题Ingo Molnar 引入了 CFSCompletely Fair Scheduler完全公平调度器它的核心思想是让每个进程都能公平地获得 CPU 时间。CFS 的核心原理1. 公平的本质虚拟运行时间CFS 的核心创新在于引入了虚拟运行时间Virtual Runtime的概念。虚拟运行时间不是进程实际占用的 CPU 时间而是经过权重加权后的时间。/* 虚拟运行时间的计算逻辑 */ static u64 sched_vruntime(struct task_struct *p) { struct sched_entity *se p-se; /* 虚拟时间 实际运行时间 * NICE_0_LOAD / 进程权重 */ return se-vruntime; }这里的关键公式是虚拟运行时间 实际运行时间 × (1024 / 进程权重)其中NICE_0_LOAD 1024是默认进程nice0的权重。2. 红黑树高效的调度队列CFS 使用红黑树来管理所有可运行的进程。树的键值是进程的虚拟运行时间vruntime树的最左节点就是虚拟运行时间最小的进程也就是下一个应该被调度的进程。struct cfs_rq { struct rb_root tasks_timeline; /* 红黑树的根节点 */ struct rb_node *rb_leftmost; /* 最左节点下一个要调度的进程*/ unsigned int nr_running; /* 队列中的进程数量 */ unsigned int h_nr_running; /* 高优先级进程数量 */ u64 min_vruntime; /* 最小虚拟运行时间 */ /* ... */ };3. 调度时机何时选择新进程CFS 会在以下几种情况下重新选择下一个要运行的进程进程主动放弃 CPU如调用sleep()时间片耗尽更高优先级进程唤醒周期性时钟中断通常每毫秒static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr) { unsigned long ideal_runtime, delta_exec; /* 计算理想运行时间 调度周期 / 进程数量 */ ideal_runtime sched_slice(cfs_rq, curr); /* 计算当前进程自上次调度以来的运行时间 */ delta_exec curr-sum_exec_runtime - curr-prev_sum_exec_runtime; /* 如果超过理想时间片触发抢占 */ if (delta_exec ideal_runtime) resched_curr(rq_of(cfs_rq)); }关键数据结构解析task_struct 中的调度相关字段每个进程都有一个task_struct结构其中与调度相关的关键字段如下struct task_struct { /* 调度实体包含虚拟运行时间等信息 */ struct sched_entity se; /* 进程优先级相关 */ int prio; /* 动态优先级0-139*/ int static_prio; /* 静态优先级100-139*/ int normal_prio; /* 常规优先级 */ unsigned int rt_priority; /* 实时优先级 */ /* 调度策略 */ unsigned int policy; /* SCHED_NORMAL/SCHED_FIFO/SCHED_RR */ /* 进程状态 */ volatile long state; /* TASK_RUNNING/TASK_INTERRUPTIBLE 等 */ /* ... */ };sched_entity调度实体struct sched_entity { struct load_weight load; /* 进程权重影响虚拟时间计算 */ struct rb_node run_node; /* 红黑树节点 */ struct list_head group_node; /* 组调度链表节点 */ u64 vruntime; /* 虚拟运行时间 */ u64 prev_vruntime; /* 上一次调度时的虚拟时间 */ u64 sum_exec_runtime; /* 累计实际运行时间 */ u64 prev_sum_exec_runtime; /* 上一次调度时的累计运行时间 */ /* ... */ };实战观察 CFS 调度行为查看进程调度信息使用ps命令查看进程的调度策略和优先级# 查看所有进程的调度策略 ps -eo pid,tid,class,rtprio,pri,nice,cmd # 输出示例 # PID TID CLS RTPRIO PRI NI CMD # 1234 1234 TS - 19 0 bash # 5678 5678 FF 99 1 -20 realtime_app使用 chrt 命令修改调度策略# 将进程设置为实时 FIFO 调度策略优先级为 50 chrt -f -p 50 1234 # 将进程恢复为普通 CFS 调度 chrt -o -p 0 1234查看内核调度统计# 查看调度器统计信息 cat /proc/schedstat # 查看特定进程的调度信息 cat /proc/[pid]/schedCFS 的优势与局限性优势公平性每个进程都能获得公平的 CPU 时间片低延迟交互进程响应迅速自适应能够根据系统负载自动调整简单优雅相比 O(1) 调度器代码更简洁局限性红黑树操作开销虽然是 O(log n)但在进程数极多时仍有开销cache 友好性频繁的进程切换会影响 CPU cache 效率实时性CFS 是通用调度器实时性能不如 RT 调度器互动讨论思考题如果系统中有一个 CPU 密集型进程和一个 I/O 密集型进程CFS 会如何分配 CPU 时间为什么 I/O 密集型进程通常能获得更好的响应性实践题尝试使用chrt命令修改一个进程的调度优先级然后使用top或htop观察它的 CPU 占用率变化。你能观察到什么现象请帮忙点赞收藏关注内容持续更新感谢大家~~~