Linux 性能调优系列(十四): CPU 这么忙,到底是谁决定“让谁先跑”?一文搞懂 Linux 调度器
1 | 作者:李晓辉 |
前面我们已经学习了:
- 硬件画像
- Linux 监控工具
- 内核可调参数
- TuneD
- cgroup
- perf
- strace
- SystemTap
- eBPF
这些工具解决的是不同层面的问题。比如:
1 | top |
可以告诉我们:
CPU 到底忙不忙?
1 | perf |
可以进一步告诉我们:
CPU 时间主要消耗在哪里?
1 | runqlat |
又可以帮助我们观察:
任务是不是因为没有 CPU 可用,一直在运行队列里排队?
但是到这里,一个更加底层的问题就出现了:
CPU 忙起来以后,Linux 内核到底是怎么决定“下一个让谁运行”的?
这就是今天要学习的:
Linux CPU 调度器(CPU Scheduler)
而如果你正在使用 RHEL 10,还需要特别注意一个变化:
RHEL 10 已经使用 EEVDF 调度器替代传统的 CFS。
EEVDF 全称:
Earliest Eligible Virtual Deadline First
中文通常可以理解为:
最早可运行虚拟截止时间优先
所以这一篇,我们就从最基础的:
1 | 任务 |
一路讲到:
1 | CFS |
什么是 CPU 调度?
先想象一个非常简单的场景。假设服务器只有 1 个 CPU 核心。现在同时有:
1 | nginx |
5 个任务都想使用 CPU。但是:
一个 CPU 核心在同一时刻只能真正执行一个线程。
那么问题来了:
其他任务怎么办?
它们不能全部同时运行,只能等待。于是 Linux 内核需要维护一个非常重要的东西:
运行队列(run queue)
可以简单理解成:
flowchart TD
CPU[CPU]
Scheduler[Linux Scheduler<br/>调度器]
RunQueue[Run Queue<br/>运行队列]
Nginx[nginx 线程<br/>Runnable]
MySQL[MySQL 线程<br/>Runnable]
Java[Java 线程<br/>Runnable]
CPU --> Scheduler
Scheduler -->|选择下一个任务| RunQueue
RunQueue --> Nginx
RunQueue --> MySQL
RunQueue --> Java运行队列里面放的,是:
当前处于可运行状态、正在等待 CPU 的任务。
调度器要做的事情非常核心:
从这些可运行任务中,选择下一个应该运行的任务。
Linux 调度的最小单位:线程
这里有一个非常容易搞错的概念。很多人会说:
“Linux 调度的是进程。”
严格来说,这个说法并不准确。Linux 内核真正调度的是:
线程(thread)
也就是内核中的 task_struct 对应的调度实体。一个进程可以拥有多个线程:
1 | 进程 PID 1000 |
这些线程共享进程的大部分资源,例如:
1 | 虚拟地址空间 |
但是:
每一个线程都可以独立进入运行队列并被调度。
所以以后看到:
1 | PID |
不要简单地把它们理解成两个不同的“进程编号”。可以简单记:
1 | 进程 |
也可以使用:
1 | [root@localhost ~]# ps -L |
查看进程中的线程。
每个 CPU 都有自己的运行队列
如果服务器只有一个 CPU,理解起来还比较简单。但现在服务器可能有:
1 | CPU0 |
这时候就不能简单理解成:
所有任务都排在一个巨大的队列里。
Linux 调度器会为每个 CPU 维护自己的运行队列。可以简单理解成:
1 | CPU0 CPU1 |
所以 CPU 调度实际上还涉及一个重要问题:
任务应该在哪一个 CPU 上运行?
不过这个问题涉及 CPU 亲和性、任务迁移、中断等内容,我们后面的文章会单独展开。这一篇先把注意力放在:
一个 CPU 上,调度器到底怎么决定“谁先运行”。
Linux 调度器到底在做什么?
可以把调度器理解成一个非常忙碌的“CPU 排班员”。假设现在有:
1 | 任务A |
全部都处于 Runnable 状态。调度器需要不断回答:
下一个运行谁?
而且这个决定不是只做一次。CPU 不断发生:
1 | 运行 |
所以调度器实际上一直在工作。可以把整个过程理解成:
flowchart LR
A[任务进入 Runnable] --> B[进入运行队列]
B --> C[调度器选择任务]
C --> D[CPU 执行]
D --> E{任务状态变化}
E -->|继续运行| C
E -->|等待 I/O| F[阻塞]
E -->|主动让出 CPU| B
E -->|被抢占| B
E -->|退出| G[任务结束]这就是 CPU 调度最基本的工作流程。
Linux 有哪些调度类别?
Linux 并不是所有任务都使用同一种调度算法。内核定义了多个 Scheduling Class(调度类别)。可以先把它理解成:
不同类型的任务,进入不同的“排班体系”。
从调度类别的优先关系来看,可以粗略理解成:
1 | Stop |
也就是说:
高优先级调度类别中的可运行任务,会优先于低优先级调度类别中的任务。
对应关系可以画成:
flowchart TD
A[Linux Scheduler] --> B[Stop]
A --> C[Deadline]
A --> D[Real-time]
A --> E[Fair]
A --> F[Idle]
B --> B1[Stop 调度类]
C --> C1[SCHED_DEADLINE]
D --> D1[SCHED_FIFO]
D --> D2[SCHED_RR]
E --> E1[SCHED_OTHER]
E --> E2[SCHED_BATCH]
E --> E3[SCHED_IDLE]
F --> F1[CPU 空闲线程]这里先建立整体概念即可。后面的实时任务调度文章,我们会专门讲:
1 | SCHED_FIFO |
Fair 调度类:普通程序主要就在这里
我们平时启动的大多数程序,例如:
1 | bash |
通常都属于普通的公平调度范畴。Fair 调度类包含:
1 | SCHED_OTHER |
其中:
1 | SCHED_OTHER |
是最常见的默认策略。Linux 内核源码中,SCHED_OTHER 实际对应的名称是:
1 | SCHED_NORMAL |
因此看到:
1 | SCHED_OTHER |
不用把它们当成两种不同的调度策略。它们是同一个普通调度策略的不同名称。
三种常见的 Fair 调度策略
SCHED_OTHER
这是普通任务最常见的调度策略。
例如:
1 | sleep 10 |
1 | bash |
1 | python app.py |
默认通常就是:
1 | SCHED_OTHER |
它会根据任务的调度属性和 nice 值参与公平调度。
SCHED_BATCH
SCHED_BATCH 面向的是:
CPU 密集型、非交互式的批处理任务。
例如:
1 | 后台数据处理 |
它和 SCHED_OTHER 一样属于普通调度范围,但是调度器会把它视为 CPU-intensive 任务,在唤醒行为上给予一定的调度惩罚。所以不要简单理解成:
“SCHED_BATCH 就是让任务运行更长时间片。”
更准确地说:
它告诉调度器:这个任务是批处理、非交互式任务,不需要特别照顾交互响应。
Linux sched(7) 对它的定义也是基于这种语义。
SCHED_IDLE
这个名字特别容易把人搞晕。因为 Linux 里面同时存在:
1 | SCHED_IDLE |
和:
1 | Idle scheduling class |
它们不是一回事。
SCHED_IDLE
这是一个可以用于任务的调度策略。它的优先级:
甚至低于
nice=19的普通任务。
非常适合:
1 | 低优先级后台任务 |
Linux 文档也明确指出,SCHED_IDLE 的 nice 值不参与这种策略的调度决策,而且其优先级低于 SCHED_OTHER/SCHED_BATCH 下的 nice +19 任务。
Idle 调度类别
则是调度器本身用于:
CPU 没有其他可运行任务时运行的空闲线程。
所以一定要记住:
1 | SCHED_IDLE |
SCHED_OTHER、SCHED_BATCH、SCHED_IDLE 的关系
可以用一张图理解:
flowchart TD
A[Fair 调度类] --> B[SCHED_OTHER]
A --> C[SCHED_BATCH]
A --> D[SCHED_IDLE]
B --> B1[普通任务]
B --> B2[受 nice 影响]
C --> C1[批处理任务]
C --> C2[CPU 密集型]
C --> C3[降低交互式唤醒优先级]
D --> D1[极低优先级]
D --> D2[低于 nice +19]
D --> D3[nice 对其无影响]这一点记住以后,后面的 nice 和 chrt 就比较容易理解了。
Linux 内部的优先级到底是什么?
Linux 内核内部有一套静态优先级体系。普通任务和实时任务使用的优先级范围并不一样。对于实时任务:
1 | 1 ~ 99 |
数字越大,实时优先级越高。对于普通调度策略:
1 | static priority = 100 ~ 139 |
对应:
1 | nice = -20 ~ +19 |
可以粗略理解:
1 | 实时任务 |
这里不要把:
1 | nice |
混成一个东西。它们是不同层面的表示方式。
nice 到底是什么?
如果你使用 Linux 很久,一定见过:
1 | nice |
它并不是简单意义上的:
“CPU 优先级。”
更准确地说:
nice 是影响普通调度任务 CPU 调度权重的一个属性。
范围:
1 | -20 ~ +19 |
其中:
1 | -20 |
表示更有利于获得 CPU。而:
1 | +19 |
表示更不利于获得 CPU。默认一般是:
1 | nice = 0 |
Linux sched(7) 也明确规定了这个范围。可以简单记:
1 | nice 越小 |
为什么叫 nice?
这个名字其实很好理解。假设服务器上正在运行:
1 | MySQL |
同时你想跑一个:
1 | 大规模压缩任务 |
如果压缩任务把 CPU 吃得非常厉害,就可能影响业务。这时候可以:
1 | nice -n 19 tar ... |
意思就是:
“这个任务我没那么着急,你先把 CPU 让给其他任务。”
使用 nice 启动程序
例如:
1 | nice -n 19 ./long_task |
就是让程序以:
1 | nice = 19 |
启动。
如果使用负数:
1 | nice -n -10 ./long_task |
则表示希望提高该任务的调度优先程度。不过普通用户通常没有权限随意降低 nice 值,也就是不能随意设置负 nice。权限由相关资源限制和能力机制控制;具有适当权限的进程才可以提高自身调度优先程度。
renice:修改已经运行的任务
nice 主要用于:
启动程序时设置 nice。
而:
1 | renice |
用于:
修改已经运行任务的 nice 值。
例如:
1 | renice 10 -p 372097 |
把 PID 372097 的 nice 设置为:
1 | 10 |
如果有足够权限,也可以降低 nice:
1 | renice -20 -p 372097 |
一个非常重要的细节:Linux 的 nice 是线程属性
这里再补充一个很容易被忽略的地方。在 Linux 中:
nice 实际上是线程级属性。
也就是说,同一个进程中的不同线程,在 Linux 上可以拥有不同的 nice 值。例如:
1 | PID 5000 |
所以排查多线程程序时,不要只盯着 PID。这也是为什么我们前面强调:
Linux 调度的核心对象是线程。
systemd 服务怎么设置 nice?
如果程序是 systemd 管理的服务,可以通过 unit 配置:
1 | [Service] |
例如:
1 | /etc/systemd/system/sshd.service.d/10-nice.conf |
内容:
1 | [Service] |
然后:
1 | systemctl daemon-reload |
systemd 的 Nice= 就是用来设置服务进程的 nice 值;CPUSchedulingPolicy= 则可以指定 CPU 调度策略。这里有一个实用原则:
修改 unit 配置文件以后,通常需要 reload,然后重启服务,新的进程才能使用新的配置。
EEVDF:RHEL 10 的重要变化
终于到了今天最重要的部分:
EEVDF。
如果你以前学习 Linux 性能分析,经常会看到:
1 | CFS |
但是在 RHEL 10 中:
CFS 已经被 EEVDF 替代。
Red Hat 官方文档明确说明:
1 | CFS |
并且:
1 | sched_min_granularity |
而:
1 | sched_wakeup_granularity |
在 EEVDF 中已经不再使用,因此被移除。所以以前很多针对 CFS 的调优文章,不能直接照搬到 RHEL 10。
EEVDF 到底是什么?
EEVDF 的全称是:
Earliest Eligible Virtual Deadline First
可以拆成三个关键词:
1 | Eligible |
不用第一次看到就被名字吓到。我们只需要先理解三个核心概念:
1 | 虚拟运行时间 |
然后调度器根据这些信息决定:
哪个任务现在更应该获得 CPU。
EEVDF 的核心思想
假设现在有两个任务:
1 | Task A |
它们都需要 CPU。调度器并不是简单地:
“你运行 10ms,我运行 10ms。”
而是会根据任务的:
1 | 权重 |
计算它们的调度状态。其中一个非常重要的概念就是:
lag
可以把它简单理解成:
任务实际获得的 CPU 时间,与按照自身权重应该获得的 CPU 时间之间的差距。
如果任务获得的 CPU 少于它应该获得的:
1 | 正 lag |
它就处于“欠 CPU”的状态。如果已经获得了超过自身应得份额:
1 | 负 lag |
则说明它暂时获得了更多 CPU。
什么是虚拟截止时间?
对于处于可调度状态的任务,EEVDF 会计算一个:
虚拟截止时间(virtual deadline)
调度器会重点考虑:
当前哪些任务已经具备运行资格,并且谁的虚拟截止时间更早。
于是可以把它简化成:
flowchart TD
A[Runnable 任务] --> B[计算调度状态]
B --> C{任务是否 Eligible?}
C -->|否| D[暂时不选择]
C -->|是| E[比较 Virtual Deadline]
E --> F[选择更早的任务]
F --> G[CPU 执行]
G --> H[任务运行状态变化]
H --> B所以千万不要把 EEVDF 简单理解成:
“谁 deadline 最近谁运行。”
真正的机制还涉及:
1 | eligible |
只是对于入门和性能排查来说,可以先抓住一个核心:
EEVDF 会综合任务的 CPU 份额与调度状态,为可运行任务计算虚拟截止时间,并据此选择任务。
为什么 EEVDF 对交互式任务比较友好?
举一个生活中的例子。
假设:
1 | 任务 A:CPU 密集型计算 |
A 一直在计算:
1 | 计算 |
而 B 经常:
1 | 运行一点 |
B 经常主动让出 CPU。当它再次变成 Runnable 时,调度器会考虑它之前的 CPU 获得情况。所以对于这种:
1 | 短时间运行 |
的任务,EEVDF 的设计能够更好地处理其调度需求。这也是 EEVDF 相比传统 CFS 调度逻辑的重要变化之一。
EEVDF 的调优参数发生了什么变化?
这是 RHEL 10 管理员特别需要注意的地方。
以前经常看到:
1 | kernel.sched_latency_ns |
但在新的内核中,部分旧参数已经发生变化。
其中:
1 | sched_min_granularity |
Red Hat 官方明确说明:
sched_base_slice定义了任务可以被延迟运行的最小时间。
在支持 debugfs 的环境中,可以看到:
1 | cat /sys/kernel/debug/sched/base_slice_ns |
也可以看到:
1 | ls /sys/kernel/debug/sched/ |
需要注意:
不要简单把
base_slice_ns理解成“任务固定运行这么长时间”。
它更准确的含义是:
调度器用于控制任务调度延迟/切片行为的基础参数。
migration_cost_ns 还存在吗?
这里也需要纠正一个很容易被旧资料误导的地方。很多旧文章会说:
1 | kernel.sched_migration_cost_ns |
现在在较新的内核中,这类调度参数可能已经从:
1 | /proc/sys/kernel/ |
迁移到:
1 | /sys/kernel/debug/sched/ |
例如:
1 | cat /sys/kernel/debug/sched/migration_cost_ns |
当前 Linux 内核源码仍然提供这个 debugfs 调度参数。
因此不能简单写成:
“EEVDF 已经完全没有 migration_cost_ns。”
更准确的说法是:
部分传统 scheduler 参数的管理位置和语义发生了变化,需要根据实际内核版本确认。
这也是为什么生产环境调优不能直接复制十年前的:
1 | sysctl -w kernel.sched_xxx=... |
debugfs 中查看调度器参数
可以先确认 debugfs 是否挂载:
1 | [root@localhost ~]# mount | grep debugfs |
然后查看:
1 | [root@localhost ~]# ls /sys/kernel/debug/sched/ |
例如:
1 | [root@localhost ~]# cat /sys/kernel/debug/sched/base_slice_ns |
以及:
1 | [root@localhost ~]# cat /sys/kernel/debug/sched/migration_cost_ns |
如果你的系统没有这些文件,不要直接认为:
“我的 EEVDF 坏了。”
因为具体可用接口还取决于:
1 | 内核版本 |
所以实际环境中:
以当前系统实际暴露出来的接口为准。
为什么 debugfs 参数重启后会丢?
因为这些参数位于:
1 | /sys/kernel/debug/ |
这个目录属于:
debugfs
它不是传统意义上的持久化配置文件。因此:
1 | echo 3000000 > /sys/kernel/debug/sched/base_slice_ns |
这种修改通常只是:
当前运行内核中的临时修改。
重启之后不会自动保留。如果确实需要在启动时设置,可以通过 systemd 的 oneshot 服务执行。
例如:
1 | [Unit] |
然后:
1 | systemctl daemon-reload |
不过这里强烈建议:
不要为了“学习 EEVDF”就随便修改生产服务器的调度参数。
调度器属于系统核心路径。除非你已经明确知道:
1 | 为什么修改 |
否则不要为了追求某个所谓的“最佳值”而调整。
怎么看 Linux 调度器统计信息?
可以查看:
1 | cat /proc/schedstat |
它提供的是调度器相关的统计数据。不过:
/proc/schedstat的具体字段会随着内核版本变化。
因此不要直接死记某一个字段。如果需要深入分析调度器行为,更应该结合:
1 | /proc/schedstat |
一起分析。
chrt:查看和修改调度策略
如果:
1 | nice |
主要用于调整普通任务的 nice,那么:
1 | chrt |
则可以直接查看和修改线程的调度策略。例如:
1 | chrt -p 5467 |
查看某个线程的调度策略。常见参数:
| chrt 参数 | 调度策略 |
|---|---|
-o | SCHED_OTHER |
-b | SCHED_BATCH |
-i | SCHED_IDLE |
-f | SCHED_FIFO |
-r | SCHED_RR |
-d | SCHED_DEADLINE |
Linux 支持的普通调度策略包括:
1 | SCHED_OTHER |
而实时/截止时间相关策略包括:
1 | SCHED_FIFO |
普通策略的静态调度优先级必须为:
1 | 0 |
而 SCHED_FIFO 和 SCHED_RR 使用:
1 | 1 ~ 99 |
的实时优先级。
使用 chrt 修改普通任务
例如把线程设置为:
1 | SCHED_BATCH |
可以:
1 | chrt -b -p 0 5890 |
查看:
1 | chrt -p 5890 |
设置:
1 | SCHED_IDLE |
可以:
1 | chrt -i -p 0 5890 |
恢复成普通:
1 | SCHED_OTHER |
可以:
1 | chrt -o -p 0 5890 |
这里再次强调:
SCHED_IDLE 是一种极低优先级的调度策略,不等于 CPU 的 Idle 调度类别。
systemd 也可以设置调度策略
对于 systemd 管理的服务,可以使用:
1 | [Service] |
例如:
1 | /etc/systemd/system/plocate-updatedb.service.d/10-idle.conf |
写入:
1 | [Service] |
然后:
1 | systemctl daemon-reload |
systemd 的 CPUSchedulingPolicy= 可以用于设置 CPU 调度策略。这里同样提醒:
不要把
idle理解成“让服务停止运行”。
它表示的是:
让该服务采用
SCHED_IDLE调度策略。
上下文切换是什么?
现在我们再来看一个 CPU 调度里非常重要的指标:
Context Switch(上下文切换)
假设:
1 | 任务 A 正在 CPU 上运行 |
突然调度器决定:
1 | 任务 B 应该运行 |
于是 CPU 就需要从:
1 | 任务 A |
切换到:
1 | 任务 B |
这个过程就涉及:
上下文切换。
可以简单理解为:
sequenceDiagram
participant CPU
participant A as 任务 A
participant S as Scheduler
participant B as 任务 B
CPU->>A: 执行任务 A
A->>S: 状态发生变化
S->>A: 保存/切出
S->>B: 选择任务 B
B->>CPU: 开始执行上下文切换本身不是错误。
Linux 系统中:
上下文切换是非常正常的事情。
真正需要关注的是:
上下文切换是不是异常频繁。
自愿上下文切换和非自愿上下文切换
Linux 会区分两类上下文切换。
1. voluntary context switch
也就是:
1 | voluntary_ctxt_switches |
任务主动放弃 CPU。例如:
1 | 等待 I/O |
2. nonvoluntary context switch
也就是:
1 | nonvoluntary_ctxt_switches |
任务并不是主动让出 CPU,而是:
被调度器切走。
例如:
1 | 被其他任务抢占 |
可以查看:
1 | grep -E 'voluntary|nonvoluntary' /proc/4350/status |
输出类似:
1 | voluntary_ctxt_switches: 123 |
这里不要简单理解成:
“nonvoluntary 越多系统越差。”
上下文切换是否异常,需要结合:
1 | CPU 使用率 |
一起判断。
把整个 CPU 调度过程串起来
到这里,我们可以把今天的知识串起来了。假设:
1 | 服务器上有 100 个线程 |
但是:
1 | CPU 只有 8 个核心 |
于是大量线程可能处于:
1 | Runnable |
状态。它们进入不同 CPU 的运行队列。然后:
1 | Linux Scheduler |
可以用一张图总结:
flowchart TD
A[大量 Runnable 线程] --> B[进入 CPU 运行队列]
B --> C[Linux Scheduler]
C --> D{调度类别}
D -->|Stop| E[Stop 调度类]
D -->|Deadline| F[SCHED_DEADLINE]
D -->|Real-time| G[SCHED_FIFO / SCHED_RR]
D -->|Fair| H[公平调度类]
D -->|Idle| I[Idle 调度类]
H --> J[SCHED_OTHER]
H --> K[SCHED_BATCH]
H --> L[SCHED_IDLE]
J --> M[EEVDF]
K --> M
L --> M
M --> N[选择下一个可运行任务]
N --> O[CPU 执行]CPU 调度问题应该怎么排查?
现在回到最开始的问题:
“服务器 CPU 很高,到底发生了什么?”
以前我们可能只会:
1 | top |
看到:
1 | %CPU = 98% |
然后说:
“CPU 很高。”
但是现在应该进一步问:
这些 CPU 时间到底被谁使用?
以及:
有没有任务因为 CPU 不够而排队?
这时候就可以结合我们前面已经学过的工具。
例如:
1 | top |
先看整体情况。
然后:
1 | vmstat 1 |
观察系统整体运行状态。
再结合:
1 | perf |
分析 CPU 时间主要消耗在哪里。
如果怀疑:
大量任务正在等待 CPU。
就可以使用前面 eBPF 文章学过的:
1 | /usr/share/bcc/tools/runqlat |
观察:
任务在运行队列中等待 CPU 的时间。
所以整个思路可以变成:
flowchart TD
A["CPU / Load 异常"] --> B["top / vmstat"]
B --> C{"进一步观察"}
C -->|"CPU 时间被程序大量消耗"| D["perf"]
C -->|"任务排队等待 CPU"| E["runqlat"]
C -->|"调度策略 / nice 异常"| F["chrt / ps"]
C -->|"上下文切换异常"| G["/proc/PID/status"]
D --> H["继续定位具体问题"]
E --> H
F --> H
G --> H这时候你会发现:
CPU 调度并不是一个孤立的知识点。
它正好把我们前面学过的很多性能工具串了起来。
几个非常容易混淆的概念
最后把这一篇最容易混淆的几个概念集中放在这里。
1. 进程 ≠ 调度单位
Linux 真正调度的是:
1 | 线程 |
不是我们平时口语中的“进程”。
2. SCHED_IDLE ≠ Idle 调度类
1 | SCHED_IDLE |
是一个极低优先级的任务调度策略。
而:
1 | Idle scheduling class |
属于调度器内部的空闲类别。两者完全不是一回事。
3. nice ≠ 实时优先级
1 | nice |
主要影响普通调度任务。
而:
1 | rtprio |
对应实时调度优先级。不要混在一起。
4. CFS ≠ EEVDF
在传统 Linux 内核中:
1 | CFS |
是经典公平调度器。而现代 Linux 内核已经逐渐转向:
1 | EEVDF |
RHEL 10 官方已经明确采用 EEVDF。所以以后看到:
1 | sched_min_granularity_ns |
不要直接套用到 RHEL 10。
这一篇真正应该记住什么?
如果今天的内容全部忘掉,只记住下面这张图:
flowchart TD
A[线程 Runnable] --> B[CPU Run Queue]
B --> C[Linux Scheduler]
C --> D{Scheduling Class}
D --> E[Stop]
D --> F[Deadline]
D --> G[Real-time]
D --> H[Fair]
H --> I[SCHED_OTHER]
H --> J[SCHED_BATCH]
H --> K[SCHED_IDLE]
I --> L[EEVDF]
J --> L
K --> L
L --> M[选择下一个任务]
M --> N[CPU 执行]
N --> O{状态变化}
O -->|等待 I/O| P[阻塞]
O -->|被抢占| B
O -->|继续运行| M
O -->|退出| Q[结束]然后记住几个最关键的命令:
1 | # 查看线程 |
如果服务器出现 CPU 负载高的问题,也不要再停留在:
1 | top |
而应该逐渐形成这样的思维:
1 | CPU 很高 |
这才是真正开始理解:
Linux CPU 调度。
