| name | swap-thrashing-diagnosis |
| description | Swap 与虚拟内存异常深度诊断技能(双轨:现场指标 + 内核语义)。当用户提到 swap 耗尽、内存抖动、 thrashing、kswapd 高 CPU、swappiness、zswap/zram 异常、swap 文件损坏、SSD 磨损等关键词时, 必须使用本技能。覆盖场景:swap 空间耗尽、swap 频繁换入换出(thrashing 检测)、swappiness 配置不当、 swap 文件/分区损坏、swap on SSD 磨损、kswapd CPU 占用过高、zswap/zram 配置异常、内存不足触发 OOM 等虚拟内存相关故障。即使用户只说"服务器变慢了/内存不足了/系统卡顿"也要触发本技能。 支持内核源码级根因分析(有源码时必须走双轨并行并交叉验证)。
|
Swap Thrashing 与虚拟内存异常深度诊断(双轨:现场指标 + 内核语义)
第一节:故障目录结构
swap_thrashing_case/ # 故障文件夹
├── src/ # 【可选,优先】内核源码路径,存在时走源码分析主路径
│ ├── mm/ # 内存管理子系统源码
│ └── ...
├── sysinfo/ # 系统运行时指标收集(sar/atop/proc 等)
├── perf_data/ # perf 采样数据(如有)
└── probe_scripts/ # 自定义 eBPF/systemtap 探测脚本
bash scripts/01_baseline_info.sh
第二节:分析策略(并行双轨,交叉验证)
现场指标分析和内核语义分析应同时进行,而非二选一。 两条轨道相互独立推进,最终交叉比对以确认根因。
┌─────────────────────────────────────────────────────────────────┐
│ 并行双轨分析模型 │
│ │
│ 轨道一:现场指标分析(逆向) 轨道二:内核语义分析(正向) │
│ ───────────────────────── ──────────────────────── │
│ 从系统指标快照出发,逆向推理 从内核内存管理逻辑出发, │
│ 识别异常模式 正向追踪配置与代码路径 │
│ │
│ 回答:系统处于什么状态? 回答:为什么会出现这种 │
│ 哪些指标异常? 状态?内核配置/ │
│ 代码逻辑原因? │
│ │
│ ↓ ↓ │
│ └────────────── 交叉验证 ────────────┘ │
│ │
│ 见:第三节(统一分析流程:分支决策→双轨并行→交叉验证→输出) │
└─────────────────────────────────────────────────────────────────┘
两条轨道的分工与互补:
| 现场指标轨道 | 内核语义轨道 |
|---|
| 优势 | 真实的系统运行时状态、量化指标(si/so 速率、CPU wait、内存压力)、时序趋势可见 | 完整因果模型、内核配置逻辑可见、代码路径可分析、根因帧可定位 |
| 局限 | 只有结果快照,内核内部决策过程不可见;采样间隔可能遗漏尖峰 | 需要内核版本精确匹配;性能调优参数依赖具体场景 |
| 典型盲区 | 应用层行为不透明(不知道哪个进程在泄漏)/ 短期抖动被平均掩盖 | 多因素耦合难以单独判断(多个应用同时加压) |
何时两条轨道都必须做:有源码时,两条轨道必须同时进行,最终通过交叉验证收敛到高置信度结论。
何时只能走现场指标轨道:无源码时,仅走现场指标分析,但应明确标注分析局限性,并在结论中说明哪些环节依赖推断。
第三节:统一分析流程(分支决策 → 双轨并行 → 交叉验证 → 输出)
执行约束:所有分析脚本的默认超时时间为 5 分钟(300s)。
Step 1:启动(基线信息收集 + 分支推荐)
运行:
bash scripts/01_baseline_info.sh [output_dir]
记录输出中的四类关键信息(后续所有步骤都围绕它们推进):
- 系统内存概况:总内存/可用内存/已用内存、Swap 总量/已用量、缓存/缓冲区
- Swap 活动指标:
si/so(swap in/out)速率、pgscank/pgscand(kswapd/direct reclaim 扫描页数)
- 关键进程:内存占用 Top-N 进程、D 状态进程、oom_score 高分进程
- 异常线索:
oom-kill 记录、kworker 高 CPU、内存 cgroup 限制命中、D state 进程卡在 swap
Step 2:故障类型定界(选择分支脚本一键跑)
按 Step 1 输出推荐,执行对应分支脚本:
bash scripts/branch_X_xxx.sh [output_dir]
说明:每个 branch_*.sh 已内置现场指标侧的检查命令序列(覆盖 M1–M4),并在检测到 src_dir 存在时给出源码侧追踪指引(覆盖 K0–K5)。
若 Step 1 输出推荐多个分支脚本,必须按输出顺序全部执行,不可只选其一。
脚本对应执行参考如下:
系统指标
├─ swap 使用率 > 90% + swap 空间不足 → 分支A: Swap 空间耗尽
├─ si/so 速率持续 > 1000 pages/s + CPU iowait 高 → 分支B: Swap Thrashing
├─ swappiness 值设置不合理(>60 或 <1) → 分支C: Swappiness 配置不当
├─ swap 设备 I/O 错误 + dmesg 含 "swap" 错误 → 分支D: Swap 文件/分区损坏
├─ swap 设备为 SSD + 写入量异常高 → 分支E: SSD 磨损
├─ kswapd CPU > 30% + 内存压力大 → 分支F: kswapd CPU 占用高
├─ zswap/zram 命中率低 + compress 效率差 → 分支G: zswap/zram 配置异常
├─ oom-killer 被触发 + swap 未耗尽 → 分支C+F: 综合诊断
└─ 其他指标异常但不明确 → 分支B: 通用 thrashing 检测
Step 3:现场指标逆向(回答"系统处于什么状态 + 哪些指标异常")
在分支脚本输出基础上,完成并固化四步证据链:
- M1 系统态还原:确认内存压力状态、swap 活跃度、CPU/iowait 分布
- M2 指标时序重建:用 sar/atop 或 /proc 快照确认指标变化趋势,识别突变点
- M3 进程级归因:定位内存占用 Top 进程、swap 换页主要贡献者、OOM score 分布
- M4 独立归因:仅基于现场指标数据,给出"异常指标是什么、首次出现在哪个时间点、当前状态是否在恶化"
输出(供后续交叉验证使用):
系统状态:<正常/紧张/OOM临界/thrashing>
Swap 活动:si=<pages/s> so=<pages/s> 持续时长=<duration>
关键进程PID:<PID> 内存占用=<size> swap占用=<size> oom_score=<score>
异常指标:<swap 使用率 / si so 速率 / 内存分配延迟 / ...>
现场指标归因假设:<一句话>
Step 4:内核语义正向(有源码时必做;回答"为什么会这样 + 内核配置与代码逻辑原因")
K0:内核版本与配置验证(防止版本/配置不匹配导致误判)
uname -r
cat /proc/version
zcat /proc/config.gz 2>/dev/null || cat /boot/config-$(uname -r)
K1–K2:以 swap 异常点为入口,完成"指标-内核-语义对齐"
- K1 锚定入口:取 Step 3 的异常指标与关键进程,定位到对应内核行为
- K2 对齐确认:以内核 mm/ 子系统的实际行为逻辑为准理解指标含义,避免把表象当根因
常见理解陷阱与处理:
| 认知误区 | 实际内核行为 | 应对方式 |
|---|
| swap 未用满就不算异常 | kswapd 可能在后台大量扫描 LRU 链表,导致 CPU 飙升 | 检查 pgscan/pgsteal 比率 |
| si/so 速率高就是 thrashing | 也可能是大量进程同时启动(透明大页回收) | 结合 allocation latency 判断 |
| swappiness=0 就不会 swap | 内核在内存极端紧张时仍会 swap | 理解 direct reclaim 路径 |
| zram 压缩率高就好 | 压缩率高可能伴随解压 CPU 开销大 | 检查 compress_ratio vs alloc_pages |
| swap 设备 I/O 错误只在 dmesg | 也有静默 writeback 错误导致脏页丢失 | 检查 /proc/meminfo 的 Dirty/Writeback |
K3:内核调用链逐层追踪(不允许省略链路)
原则:从最底层 swap 行为向上追溯至顶层触发进程,逐层完成三件事:
① 找到对应的内核代码路径(reclaim/swap/pagefault)
② 用系统指标验证该路径的参数是否异常(扫描页数、回收效率、分配延迟)
③ 判断:该层是"症状表现层"还是"根因触发层"?
症状表现层 vs 根因触发层必须严格区分:症状层是异常传播的终点,根因层是异常最初引入点。
典型分层(自底向上):
| 层次 | 内核模块 | 典型指标 | 角色 |
|---|
| 应用层 | 用户进程 | RSS/VSZ 增长 | 常为根因触发层(内存泄漏/不合理使用) |
| syscall 层 | mmap/brk/malloc | page fault 速率 | 症状表现层或触发层 |
| page allocator | page_alloc.c | allocation latency | 症状表现层 |
| reclaim 层 | vmscan.c | pgscan/pgsteal/pgrefill | 核心决策层 |
| swap 层 | swap_state.c/swapfile.c | si/so 速率 | 症状表现层 |
| block IO 层 | block/ | bidi/bio 延迟 | 症状表现层 |
K4:数据流溯源(异常指标生命周期追踪)
围绕 Step 3 的"异常指标",在源码与配置中追踪:
内存分配请求 → 分配器尝试 → 水位线检查 → 唤醒 kswapd/kworker
→ LRU 扫描 → 页回收/swap out → 内存释放 → 分配完成(或 OOM)
重点审查的根因高发区:
- 应用层内存泄漏:进程 RSS 持续增长不释放
- 内存 cgroup 限制:cgroup memory.max 过小导致频繁 reclaim
- khugepaged 透明大页压缩:大量内存被折叠为大页后碎片化加剧
- vm.overcommit 策略:overcommit 导致 OOM 更频繁(但 swap 未耗尽)
- numa 失衡:进程集中在一个 NUMA 节点,该节点 swap 而其他节点空闲
- 文件页缓存失控:
dirty_ratio/dirty_background_ratio 导致大量脏页回写占用 IO
K5:反事实验证(强制;不能止步于"找到可疑指标")
用内核语义假设正向推演,并与现场指标逐条对齐:
✓ 推演的swap行为 == 现场指标 si/so 速率?
✓ 推演的CPU消耗 == 实际 kswapd/kworker CPU 占比?
✓ 推演的IO模式 == 实际 bidi/bio 延迟分布?
✓ 推演的进程状态 == 实际进程 D 状态/RSS 分布?
四条全 ✓ 才能判定"根因确认"。
内核语义轨道输出格式:
内核版本:<version>
关键配置:<swap相关配置项>
异常行为:<例如 kswapd 在内存未耗尽时频繁扫描>
根因类型:[应用层内存泄漏 | cgroup限制 | swappiness不当 | numa失衡 | IO拥塞 | 配置不当]
内核缺陷:<精确描述>
触发条件:<前置状态>
因果链:[触发事件] → [内核行为] → [异常指标] → [系统表现]
Step 5:交叉验证(双轨汇合,冲突仲裁,置信度收敛)
对每条证据做对齐检查:
| 验证维度 | 现场指标结论 | 内核语义结论 | 是否吻合? |
|---|
| 异常类型 | swap 使用率 / si so 速率 / CPU | LRU 扫描 / direct reclaim / 水位线 | □ 吻合 □ 不符 |
| 关键进程 | PID RSS/swap 占用 | 该进程的缺页/回收/缺页行为 | □ 吻合 □ 不符 |
| 时间序列 | 指标在 时刻突变 | 对应内核事件在 时刻发生 | □ 吻合 □ 不符 |
| 根因层 | 应用层进程行为 | 应用层/syscall 层首次引入异常 | □ 吻合 □ 不符 |
| OOM 判定 | oom_score 最高进程 | 实际被 kill 的进程 | □ 吻合 □ 不符 |
不一致时的仲裁原则:
异常指标/时间点:优先信任现场指标(客观量测数据)
根因层不符:保留多假设并补证据(分析 other potential processes/检查 cgroup hierarchy)
置信度收敛:
- 高:两轨完全吻合 + 反事实验证通过
- 中:两轨基本吻合,但有 1 个维度依赖推断;或仅完成现场指标轨道
- 低:两轨存在矛盾且无法解释;或证据链缺失超过两环节
- 需硬件排查:swap on NVMe/SSD 出现大量 ECC 错误(排除 flash 介质问题)
常见误判陷阱(用于复核结论质量):
- si/so 高 ≠ thrashing:也可能是大量进程冷启动需要加载数据
- swappiness=0 ≠ 不用 swap:内核在 memory pressure 紧缺时仍会触发匿名页回收
- kswapd CPU 高 ≠ kswapd 异常:可能是内存回收效率低(LRU 大量脏页/不可回收页)
- swap 使用率高 ≠ swap 故障:swap 本就是给不活跃页用的,被用满是正常的
- OOM 被 kill ≠ root cause:kill 的是 oom_score 高的进程,不一定是造成 OOM 的进程
软件/应用配置问题优先,但以下情况提升内核/硬件怀疑优先级:
① 指标无明显异常但系统随机卡顿
② 不同硬件平台均再现该 swap 行为
③ 大量 I/O timeout 伴随 swap 活动
→ 参考分支 D/E 分析流程
Step 6:最终输出(按第九节模板落盘)
将 Step 3/4/5 的输出填入第九节报告结构,并显式写清:结论、证据链、排除项、修复建议、验证建议。
第四节:轨道一 —— 现场指标分析(逆向推理)
本节已合并进第三节的统一流程(Step 3)。
第五节:轨道二 —— 内核语义分析(正向追踪)
本节已合并进第三节的统一流程(Step 4)。
第六节:交叉验证与结论收敛(双轨汇合)
本节已合并进第三节的统一流程(Step 5)。
第七节:故障类型决策树(两条轨道共用)
本节内容已合并进第三节的统一流程(Step 2)。
第八节:注意事项与置信度评级
本节内容已合并进第三节的统一流程(Step 5)。
第九节:最终报告结构
## 故障概要
故障模式:<Swap 耗尽 / Thrashing / Swappiness 不当 / 损坏 / SSD 磨损 / kswapd 异常 / zswap 异常>
置信度:<高/中/低/需硬件排查>
分析轨道:[双轨(指标 + 内核语义)| 单轨(仅指标,无源码)]
内核版本:<版本号>
## 现场指标轨道结论
系统内存状态:<MemTotal / MemAvailable / MemFree / Cached / Buffers>
Swap 状态:<SwapTotal / SwapFree / SwapUsed / 使用率>
Swap 活动:si=<pages/s> so=<pages/s> 峰值时间:<timestamp>
关键进程 Top(按RSS):<PID: RSS: 进程名>
关键进程 Top(按swap):<PID: SwapUsage: 进程名>
kswapd CPU 占用:<avg%> kswapd 扫描页数:<pgscan/s>
现场指标归因假设:<一句话>
## 内核语义轨道结论(有源码时填写)
根因代码/配置位置:<配置项/源文件:line>
根因类型:[应用层泄漏 | cgroup限制 | swappiness不当 | numa失衡 | 配置不当]
内核缺陷描述:<精确描述>
触发条件:<需要什么前置状态>
因果链:[触发事件] → [内核行为] → [异常指标] → [系统表现]
## 交叉验证结果(有源码时填写)
异常类型吻合: □ 是 □ 否(差异说明:<...>)
关键进程吻合: □ 是 □ 否(差异说明:<...>)
时间序列吻合: □ 是 □ 否(差异说明:<...>)
根因层吻合: □ 是 □ 否(差异说明:<...>)
综合判断:<两轨结论是否一致,若有矛盾如何解释>
## 完整因果链(双轨收敛后)
[触发条件] → [根因] → [内核行为]
→ [异常指标] → [系统表现]
## 排除的替代假设
- <假设X>:排除原因 <...>
## 修复建议
最小修复(立即可做):
<具体配置修改或OOM调整,命令形式>
根本修复(设计层面):
<应用层重构/cgroup调整/硬件升级等方向>
## 验证建议
<如何确认根因 + 如何验证修复有效>
第十节:参考文件
references/swap_commands.md:Linux swap 相关命令速查手册
references/kernel_swap_params.md:内核 swap 相关参数与 mm/ 子系统源码参考
references/analysis_patterns.md:常见 swap/thrashing 问题模式速查