我一直想把 Brendan Gregg 《Linux Load Averages: Solving the Mystery》 那篇文章里的三条核心主线真正”做出来”,而不只是停留在”背定义”的层面。于是我在一台 Dell T430(双路 Intel Xeon E5-2682 v4 @ 2.50GHz,32GB × 2 内存,运行 RockyLinux 8.10 / 4.18.0-553 内核)上,设计并执行了下面这套实验。
我想验证的三条主线:
load average不是单纯 CPU 忙碌度,而是 Linux 的”system load”。load average既会被 runnable tasks 拉高,也会被 uninterruptible tasks(D state) 拉高。iowait只是 CPU 统计口径里的一个字段,不能单独拿来当根因。
安全提示:这套实验我建议在实验 VM 或 lab 主机上做,不要直接在生产 root filesystem 上做。特别是
fio和fsfreeze这两组实验,都会明显制造卡顿。
0. 准备实验环境
RockyLinux 8.10 默认在 4.18.0-553 这条内核线上,足够做 Gregg 文里提到的大多数观察,包括 perf、ftrace/tracefs,以及可选的 BCC off-CPU 分析。
安装基础工具
1
2
3
sudo dnf install -y sysstat fio util-linux
# 可选高级观察
sudo dnf install -y bcc-tools
准备一次性实验文件系统
我不想把 rootfs 搞脏或冻住,所以用 loopback + XFS 做了一个独立挂载点:
1
2
3
4
5
sudo mkdir -p /var/tmp/loadlab /mnt/loadlab
sudo truncate -s 8G /var/tmp/loadlab/loadlab.img
LOOPDEV=$(sudo losetup -f --show /var/tmp/loadlab/loadlab.img)
sudo mkfs.xfs -f "$LOOPDEV"
sudo mount "$LOOPDEV" /mnt/loadlab
fsfreeze 适用于 ext3/4、XFS、JFS、ReiserFS 等本地文件系统;Rocky/RHEL 8 默认用 XFS,做这个实验最自然。
1. 固定一组联立观测窗口
这一步很关键。我后面不是”看单个指标”,而是做联立观测。我开了四个终端同时运行以下命令。
窗口 A:load 与 /proc/stat
1
2
3
4
5
6
7
8
9
10
watch -n 1 '
echo "=== /proc/loadavg ==="
cat /proc/loadavg
echo
echo "=== /proc/stat ==="
grep -E "procs_running|procs_blocked" /proc/stat
echo
echo "=== top ==="
top -b -n 1 | head -50
'
/proc/loadavg 的前三个数字是 1/5/15 分钟 load,第四个字段前半部分是当前 runnable scheduling entities 的数量。/proc/stat 里的 procs_running 和 procs_blocked 分别给出 runnable 线程总数与当前”waiting for I/O to complete”的进程数。
窗口 B:vmstat
1
vmstat 1
r = runnable processes,b = blocked waiting for I/O to complete。这两个字段是我理解 Gregg 文章时最重要的地面对应物。
窗口 C:iostat
1
iostat -xz 1
我主要看 await(排队时间 + 服务时间)、aqu-sz(平均队列长度)、%util。
窗口 D:D-state 任务
1
watch -n 1 'ps -eLo pid,tid,stat,wchan:32,comm --sort=stat | awk '\''$3 ~ /D/ {a[++n]=$0} END {print "TOTAL:", n; for(i=1;i<=n;i++) print a[i]}'\'''
ps 将 D 定义为 uninterruptible sleep (usually I/O)。注意这个”usually”很重要:D-state 常见于 I/O,但不是只可能由磁盘吞吐导致。
2. 实验一:单线程 CPU 负载——验证”1 分钟平均值不是 60 秒简单平均”
这是 Gregg 文章里最经典的一个认知点。所谓 1-minute load 不是”过去 60 秒的普通算术平均”,而是每 5 秒采样一次的指数衰减和(LOAD_FREQ = 5*HZ+1);因此单线程 CPU burner 跑满 60 秒后,1-minute load 只会接近 0.62,不会到 1.0。
操作
1
2
3
4
5
6
7
8
9
10
11
taskset -c 0 bash -c 'while :; do :; done' &
CPU1_PID=$!
for i in $(seq 1 18); do
printf "%s " "$(date +%T)"
cat /proc/loadavg
sleep 5
done
kill $CPU1_PID
wait $CPU1_PID 2>/dev/null
我观察到的现象
loadavg的第一个值缓慢上升。- 运行满 60 秒后,它并没有到 1.00,而是接近约 0.62。
vmstat中r接近 1;b基本为 0。iostat没有明显await。- D-state 基本没有出现。
分析
这一步帮我把两个概念钉死了:
- load 可以由 runnable demand 单独拉高。
- 1/5/15 不是简单平均,而是指数衰减”记忆”。后面我看到某台机器从高负载恢复时,1-minute load 不会立刻掉下去,就是这个原因。
3. 实验二:纯 runnable load——制造”高 load 但无 D-state”
这一步我要把”CPU runnable 负载”和”D-state 阻塞负载”做一个干净的分离。
操作
1
2
3
4
5
6
7
8
9
10
11
12
N=$(nproc)
pids=()
for i in $(seq 1 $N); do
yes > /dev/null &
pids+=($!)
done
sleep 90
kill "${pids[@]}"
wait "${pids[@]}" 2>/dev/null
我观察到的现象
loadavg持续向N靠拢,但不会瞬间等于N(指数衰减的效果很明显)。vmstat的r明显上升。vmstat的b保持接近 0。ps里几乎看不到 D-state。iostat的await/aqu-sz没有成为主要矛盾。
分析
这是最纯粹的 nr_running 路径。Gregg 文章前半段说得很清楚:如果系统只有 runnable load,那么 load 的含义就非常接近传统”CPU demand”。这个实验让我确认了这一点。
4. 实验三:同步阻塞 I/O——制造”CPU 不一定满,但 load 与 D-state 一起升”
操作
我在实验文件系统上跑阻塞型 I/O:
1
2
3
4
5
6
7
8
9
10
11
12
13
fio --name=sync-randwrite \
--directory=/mnt/loadlab \
--filename=labfio.dat \
--size=4G \
--rw=randwrite \
--bs=4k \
--direct=1 \
--ioengine=sync \
--iodepth=1 \
--numjobs=32 \
--time_based=1 \
--runtime=120 \
--group_reporting
我观察到的现象
vmstat b上升了。/proc/stat里的procs_blocked也上升了。ps里出现了一些fio线程处于 D-state。loadavg上升,即使 CPU 使用率不一定打满。iostat -xz 1的await、aqu-sz明显上升。
分析
这一步正对应 Gregg 文中最重要的一句话:Linux load 不只追踪 runnable tasks,也追踪 uninterruptible tasks;因此磁盘或 NFS I/O 负载可以把 load 拉高。
同时我也看到了:wa 确实上升了,但它只是 CPU accounting 里的一个字段。内核 /proc 文档 明确提醒了三件事:
- CPU 不会真的”等 I/O”。
- 多核下 iowait 很难精确归因。
/proc/stat里的 iowait 在某些条件下甚至会下降。
所以我的结论是:不要把 %wa 当成唯一真相。真正更值得信的是 b / procs_blocked、D-state 数量、await / aqu-sz、以及具体阻塞栈和 wait channel。
5. 实验四:用 fsfreeze 做”不是磁盘太慢,也能进 D-state”的实验
这是整套实验里最让我建立高级直觉的一步。
5.1 为什么我选 fsfreeze
Gregg 在文章里明确指出:现代内核里进入 TASK_UNINTERRUPTIBLE 的 code path 已经不只磁盘 I/O,还包括某些锁原语。我需要一个稳定、可控、低风险的方法来证明:
D-state 不是”磁盘吞吐不够”的同义词,而是”内核路径上的不可中断等待”。
fsfreeze 的语义是冻结文件系统上的新写入请求,并让写入者阻塞直到解冻。冻结后,尝试写入(或其他会修改文件系统的调用)的进程会被阻塞在文件系统写路径入口处的 sb_start_write()/__sb_start_write()。
5.2 关键机理:为什么”D 拉高 load,但 iowait / vmstat b 仍可能为 0”
在做实验之前,我先理清了几个关键概念。
load average 的精确定义:
/proc/loadavg明确:前三个数字是 R 状态和 D 状态任务数的指数衰减平均。- 内核实现:
global load average = 指数衰减平均(nr_running + nr_uninterruptible),每LOAD_FREQ = 5*HZ+1(约 5 秒)采样更新一次。
iowait 为什么可以一直很低:
- 在 fsfreeze 实验里,
dd往往在进入真实块 I/O 之前就被文件系统写入口拦住(__sb_start_write),因此可能几乎没有新的 block request 被下发。%wa仍接近 0 并不矛盾。
为什么 vmstat b / procs_blocked 可能完全不涨:
vmstat b的定义:blocked waiting for I/O to complete。/proc/stat procs_blocked的定义:同上。- fsfreeze 场景:
dd虽然进入 D,但等待的对象是”文件系统解冻/写入口许可”(percpu_rwsem_wait),不属于”waiting for I/O to complete”的统计口径,因此b/procs_blocked可能保持 0。
这也正是 Gregg 强调的点:Linux load average 把 TASK_UNINTERRUPTIBLE 也算进去,而现代内核中该状态不仅用于磁盘 I/O,还会被某些锁/内核路径使用,因此”load 高但 CPU/iowait 不高”是完全可能的。
5.3 实验工作流
flowchart TD
A[准备: loopback+XFS 挂载 /mnt/loadlab] --> B[基线采样 10s]
B --> C[fsfreeze -f /mnt/loadlab]
C --> D[启动多路 dd 写入 frozen FS]
D --> E[采证据: loadavg / procstat / vmstat / iostat / ps / stack]
E --> F{观察到: D增多 + load上升 + iowait低 + r/b不涨?}
F -->|是| G[fsfreeze -u 解冻 → dd恢复/退出]
F -->|否| H[调大 dd 数量 / 延长冻结 / 确认写对挂载点]
G --> I[清理 umount + losetup -d]
5.4 操作步骤
步骤一:基线采样(冻结前)
我先记录了一份基线数据:
1
2
3
4
5
date
cat /proc/loadavg
grep -E "procs_running|procs_blocked" /proc/stat
vmstat 1 3
iostat -xz 1 3
果然:load 低、D 少、procs_blocked=0、vmstat b=0、wa≈0。
步骤二:冻结文件系统
1
sudo fsfreeze -f /mnt/loadlab
冻结后,新写与其他修改操作会被 halt,写入者阻塞直到解冻。
如果
fsfreeze -f很慢,通常是因为要先完成/刷出正在进行的事务、脏数据与日志。
步骤三:启动多路 dd 写入(让它们卡住)
1
2
3
for i in $(seq 1 16); do
dd if=/dev/zero of=/mnt/loadlab/frozen.$i bs=1M count=1024 oflag=dsync status=none &
done
我启动了 16 路 dd。正如预期,这些进程全部”卡住不退出”,并逐渐在 ps/top 中显示为 D。
步骤四:采集证据链
1) 确认 D 数量与 load 上升的关系
1
2
cat /proc/loadavg
watch -n 1 'ps -eLo pid,tid,stat,wchan:32,comm --sort=stat | awk '\''$3 ~ /D/ {a[++n]=$0} END {print "TOTAL:", n; for(i=1;i<=n;i++) print a[i]}'\'''
我观察到:
D_count接近我启动的 dd 数量(16)。loadavg的 1-min 值开始上升(注意它每 ~5 秒更新一次,且是指数衰减平均)。- 第四字段中 runnable 可以为 0,但 load 仍然在升高。
2) 对照:为什么 iowait / vmstat r,b 仍几乎为 0
1
2
vmstat 1 5
grep -E "procs_running|procs_blocked" /proc/stat
我观察到:
vmstat r=0(dd 不 runnable)。vmstat b=0与procs_blocked=0(等待”解冻许可”不计入”waiting for I/O complete”的口径)。%wa仍低(冻结后未持续产生 I/O)。
这个结果与我之前理清的机理完全吻合。
3) 证明 dd 卡在 __sb_start_write/percpu_rwsem_wait
1
2
3
PID=$(ps -eLo stat,pid,comm | awk '$1 ~ /^D/ && $3=="dd" {print $2; exit}')
echo "PID=$PID"
sudo cat /proc/$PID/stack
我看到的栈形态:
1
2
3
4
5
6
percpu_rwsem_wait
__percpu_down_read
__sb_start_write
vfs_write
ksys_write
...
这与内核文档描述的”冻结时 sb_start_write 等待 thaw”完全吻合。等待点是 superblock 侧的 percpu rwsem,而不是块层 I/O 完成等待。
4) 定位 dd 到底在写哪个文件与挂载点
1
2
3
sudo ls -l /proc/$PID/fd | head
sudo readlink -f /proc/$PID/fd/* | head
findmnt -T /mnt/loadlab/frozen.1
我用这一步排除了”dd 并没写到被冻结的文件系统”导致现象不一致的可能。
5) 验证 kill -9 对 D-state 进程不生效
1
2
3
sudo kill -9 $PID
sleep 1
ps -p $PID -o pid,stat,comm
果然仍然是 D。原因:TASK_UNINTERRUPTIBLE 不会因为信号而提前返回;只有等待条件满足(例如解冻后被显式唤醒)才会返回可运行态,届时挂起的 SIGKILL 才会被处理。
6) 可选:用 tracefs 证明”冻结后 dd 阶段几乎无 block I/O”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 若不存在则挂载
sudo mount -t tracefs nodev /sys/kernel/tracing 2>/dev/null || true
# 清空并开启 block 事件
echo 0 | sudo tee /sys/kernel/tracing/tracing_on >/dev/null
echo | sudo tee /sys/kernel/tracing/trace >/dev/null
echo 1 | sudo tee /sys/kernel/tracing/events/block/block_rq_issue/enable >/dev/null
echo 1 | sudo tee /sys/kernel/tracing/events/block/block_rq_complete/enable >/dev/null
echo 1 | sudo tee /sys/kernel/tracing/tracing_on >/dev/null
sleep 5
echo 0 | sudo tee /sys/kernel/tracing/tracing_on >/dev/null
sudo tail -50 /sys/kernel/tracing/trace
我看到的结果:冻结”完成之后”,block 事件很稀少甚至没有。这进一步支持了”dd 卡住并非在等待块 I/O 完成”的判断。
7) 可选:看 dmesg 是否出现 hung task
1
dmesg -T | tail -200
如果 dd 在 D 中持续超过 hung task 超时,内核可能打印”task blocked for more than … seconds”并附带 backtrace,常能直接看到 __sb_start_write。
步骤五:解冻并收尾
1
sudo fsfreeze -u /mnt/loadlab
一旦解冻,被阻塞的写操作继续推进;之前发送的 kill -9 在它们醒来后果然生效了,进程退出了。
5.5 这个实验让我明白的原理
fsfreeze 实验训练了我区分两个层次:
| 层次 | 含义 | 典型表现 |
|---|---|---|
| I/O device slow | 块设备本身吞吐不足 | iostat await/aqu-sz 高,vmstat b 高 |
| kernel code path / filesystem path / wait point slow | 文件系统或锁路径阻塞 | D-state 增多、load 升高,但 iostat/vmstat b 可能不高 |
两者都可能让任务进 D-state,并都可能把 load 拉高。Gregg 文里后半段专门强调了这件事:现代 Linux 的 TASK_UNINTERRUPTIBLE code path 已经很多,包含某些锁等待。
6. 实验五:把 D-state 的成因抓出来
想真正掌握这类问题,我必须从”看数值”走到”看 code path”。
抓 wait channel
我拿了一个 D-state PID:
1
2
PID=<某个D状态进程PID>
ps -o pid,tid,stat,wchan:32,comm,args -p $PID -L
wchan 对应的是任务当前在内核里睡眠的等待点。
抓内核栈
1
sudo cat /proc/$PID/stack
/proc/pid/stack 提供该进程的符号化 kernel stack(前提是内核启用了 CONFIG_STACKTRACE)。
我的读法总结
| 实验场景 | 典型栈特征 | 含义 |
|---|---|---|
| 实验三(fio 同步 I/O) | 块层、文件系统、写回相关函数 | “真 I/O 路径阻塞” |
| 实验四(fsfreeze) | freeze、superblock、__sb_start_write、percpu_rwsem_wait |
“文件系统控制路径阻塞” |
| 真实故障 | rwsem_*、mutex_*、down_* |
“某些锁原语走 TASK_UNINTERRUPTIBLE” |
Gregg 文章甚至给出了 rwsem_down_read_failed() 这类例子。这一步是整套实验里最接近”实战排障能力”的部分。
7. 实验六:用 ftrace 看块层 issue/complete
我想把 iostat await 背后的”issue 到 complete 之间到底发生了什么”变成可见事件。
如果 /sys/kernel/tracing 不存在,先挂载 tracefs
1
sudo mount -t tracefs nodev /sys/kernel/tracing 2>/dev/null || true
在 fio 运行期间开始追踪
1
2
3
4
5
6
7
8
9
10
echo 0 | sudo tee /sys/kernel/tracing/tracing_on
echo | sudo tee /sys/kernel/tracing/trace
echo 1 | sudo tee /sys/kernel/tracing/events/block/block_rq_issue/enable
echo 1 | sudo tee /sys/kernel/tracing/events/block/block_rq_complete/enable
echo 1 | sudo tee /sys/kernel/tracing/tracing_on
sleep 10
echo 0 | sudo tee /sys/kernel/tracing/tracing_on
sudo head -200 /sys/kernel/tracing/trace
分析
await是平均值。Tracepoint 是单个 request 的生命周期事件。- 当我把
block_rq_issue与block_rq_complete对上时,我真正理解了:loadavg 上去的背后,不是”磁盘慢”四个字,而是一批请求在排队、完成、唤醒线程,再次提交请求。
8. 实验七:按 Gregg 的思路做 off-CPU / uninterruptible 分析
Gregg 在文章里展示的是只过滤 TASK_UNINTERRUPTIBLE 的 off-CPU flame graph。他还明确写了:offcputime.py --state 2 需要 Linux 4.8+。Rocky 8.10 的 4.18 内核满足该前提。
我安装了 bcc-tools 后执行:
1
sudo /usr/share/bcc/tools/offcputime -K --state 2 -f 30 > /tmp/unint.stacks
如果还准备了 FlameGraph 脚本,可以继续转成 flame graph。即便不做图,这一步也已经把”TASK_UNINTERRUPTIBLE 的 off-CPU time”单独抓出来了。
对我而言,这一步的意义是:我不再只是说”有 D-state”,而是能说”D-state 的时间主要耗在这些内核路径上”。
9. 可选扩展:NFS hard mount 实验
Gregg 文中明确说过,Linux load average 可以被 disk 或 NFS I/O workload 拉高。
如果有第二台 VM,这个扩展实验非常值得做:
- 服务器导出 NFS。
- 客户端挂载 NFS。
- 在客户端持续
ls/find/dd/tar访问该挂载点。 - 暂时阻断网络或暂停服务端。
我预期会看到:
- 客户端 shell、
ls、df等操作卡住。 - 本地
iostat不一定很夸张。 - 但客户端却有明显 D-state 和高 load。
这一步能彻底摆脱”D-state = 本地磁盘一定打满”的误解。这个扩展实验也非常贴近真实故障场景。
10. 工具与指标对照表
| 工具/文件 | 命令 | 关键字段 | fsfreeze 实验中的典型解读 |
|---|---|---|---|
| load average | cat /proc/loadavg |
前三项均值;第四项 runnable/total | load 上升即代表 nr_running+nr_uninterruptible 均值上升;runnable 可为 0 |
| runnable/blocked 计数 | grep -E 'procs_running\|procs_blocked' /proc/stat |
procs_running、procs_blocked |
procs_blocked 只统计”waiting for I/O complete”,冻结等待不一定计入 |
| vmstat | vmstat 1 |
r、b、wa |
b 同”waiting for I/O complete”;冻结等待时 b 可能为 0 |
| iostat | iostat -xz 1 |
await、%util |
冻结后阶段可能几乎无新 I/O |
| 进程状态 | ps -eLo stat,pid,tid,... |
D |
D = 不可中断睡眠;可由 I/O 或锁/冻结路径触发 |
| 等待点 | cat /proc/$PID/wchan |
符号名 | 看到 __sb_start_write / percpu_rwsem_wait 可定性为冻结互斥等待 |
| 内核栈 | cat /proc/$PID/stack |
调用链 | 看 __sb_start_write vs 块层函数来区分等待类型 |
| tracefs 事件 | /sys/kernel/tracing/... |
block_rq_issue/complete |
冻结后无持续 block 事件 ≈ 不是在等块 I/O 完成 |
| dmesg | dmesg -T |
hung task / backtrace | 冻结太久可能报 hung task 并显示等待栈 |
11. 我的优先级诊断清单
在生产环境中遇到”load 高”的告警时,我现在会按以下顺序逐步排查:
| 顺序 | 目的 | 命令 |
|---|---|---|
| 1 | 判断 load 是否来自 D-state | cat /proc/loadavg + ps -eLo stat \| awk '$1~/^D/' |
| 2 | 区分 runnable vs blocked | vmstat 1 看 r/b;grep procs_ /proc/stat |
| 3 | 定位 D-state 的 code path | sudo cat /proc/<pid>/stack |
| 4 | 证明是否真有块 I/O 活动 | iostat -xz 1;可选 tracefs block 事件 |
| 5 | 查日志/超时 | dmesg -T \| tail -200 |
| 6 | 应急处置 | 如果确认是 freeze:fsfreeze -u <mountpoint>;NFS 则排查网络/服务端 |
12. 时间关系图:freeze、__sb_start_write 阻塞与 loadavg 采样如何错位
sequenceDiagram
participant U as 用户态 dd
participant V as VFS(vfs_write)
participant S as Superblock gate(__sb_start_write)
participant K as Scheduler/loadavg
participant P as /proc观测(vmstat/proc_stat)
Note over K: loadavg 每 ~5秒更新一次 (LOAD_FREQ=5*HZ+1)
U->>V: write()
V->>S: __sb_start_write()
Note over S: 若已冻结: 等待 thaw (percpu_rwsem_wait)
S-->>U: 线程进入 TASK_UNINTERRUPTIBLE (D)
P-->>P: vmstat 每 1s 采样 r/b;procs_blocked 只计 waiting for I/O complete
K-->>K: 采样 nr_running+nr_uninterruptible → loadavg 上升
Note over P: iowait 可能仍低:冻结后阶段无持续 block I/O
13. 处置策略:止血 → 改进 → 修复
立即止血
- 立刻解冻(最优先):
sudo fsfreeze -u /mnt/loadlab。冻结语义就是”写者阻塞直到解冻”,所以解冻是最快恢复方式。 - 解冻后再 kill:D 中
kill -9不会让它立刻退;解冻后醒来才会处理信号。 - 避免冻结根分区:可能让系统服务/日志/登录都异常。
中期改进
- 降低脏页高水位:
vm.dirty_*直接影响冻结前 flush 的时长与冲击。 - 用 cgroup 做隔离/限流:对做快照/备份的后台任务限流,减少”冻结窗口压力”。
- 把”冻结”当成必须显式受控的运维动作:快照/备份链路要做超时、失败回滚(确保一定 thaw)。
长期修复
- 尽量避免在高并发在线写入场景使用文件系统级冻结,转用更细粒度的一致性策略(数据库 checkpoint、逻辑快照、WAL/redo 机制)。
- 业务写路径尽量减少同步阻塞点(
fsync/小同步写过密),采用异步/批量化。 - 可选:启用 PSI 做”真正 stall”监控。RHEL8 系常默认禁用 PSI(
CONFIG_PSI_DEFAULT_DISABLED),可用内核参数psi=1启用。
14. 我做完这套实验后形成的结论
结论 1:load average 是”系统 demand”,不是简单 CPU utilization
Gregg 文章和 /proc/loadavg man page 都非常明确:Linux load 统计 runnable tasks + D-state tasks。
结论 2:高 load 要先拆成两半看
我现在碰到 load 告警,会先问自己:
- 是
r高,还是b高? - 是
procs_running在涨,还是 D-state 数在涨?
结论 3:iowait 只能当辅证,不能当裁判
内核 /proc 文档已经把它说得很死:iowait 不可靠,不能单独代表真实阻塞程度。真正更有解释力的是:b/procs_blocked、D-state 数量、await/aqu-sz、wchan、/proc/<pid>/stack、tracepoint / off-CPU 栈。
结论 4:D-state 的成因要按 code path 分层理解
我至少要把它分成三类:
| 类别 | 说明 | 代表性 wchan/stack |
|---|---|---|
| 真实块设备 / FS I/O 路径阻塞 | 磁盘吞吐不足 | 块层函数、writeback 相关 |
| 文件系统控制路径阻塞 | freeze、writeback、metadata path | __sb_start_write、freeze 相关 |
| 内核锁 / wait primitive 阻塞 | 某些 rwsem、mutex | rwsem_down_*、mutex_lock_* |
15. 实验记录表模板
每个实验我都记了这些字段:
| 字段 | 说明 |
|---|---|
| 时间点 | 记录采样时刻 |
/proc/loadavg |
1/5/15 分钟值 + runnable/total |
vmstat: r b wa |
runnable / blocked / iowait |
/proc/stat: procs_running procs_blocked |
内核统计 |
iostat: await aqu-sz %util |
I/O 等待与队列 |
| D-state 线程数 | ps 统计 |
代表性 wchan |
等待点函数名 |
代表性 /proc/<pid>/stack 顶部 5-10 行 |
内核调用栈 |
| 结论 | runnable load / uninterruptible load / 混合 |
做了两三轮之后,我对 load / iowait / D-state 的关系建立了非常牢固的直觉。
16. 实验结束后的清理
1
2
3
sudo umount /mnt/loadlab
sudo losetup -d "$LOOPDEV"
sudo rm -f /var/tmp/loadlab/loadlab.img
参考资料
- Brendan Gregg - Linux Load Averages: Solving the Mystery
- proc_loadavg(5) - Linux manual page
- vmstat(8) - Linux manual page
- iostat(1) - Linux manual page
- ps(1) - Linux manual page
- The /proc Filesystem — Linux Kernel documentation
- proc_pid_stat(5) - Linux manual page
- proc_pid_stack(5) - Linux manual page
- ftrace - Function Tracer — Linux Kernel documentation
- fsfreeze(8) — Arch manual pages
- Rocky Linux Release and Version Guide