故障案例分析:系统负载居高不下,多次出现 OOM

文章目录

    • [故障分析案例:RK3588 NVR 设备内存耗尽与 OOM 频发](#故障分析案例:RK3588 NVR 设备内存耗尽与 OOM 频发)
    • 一、故障现象
      • [1.1 系统级现象](#1.1 系统级现象)
      • [1.2 业务级现象](#1.2 业务级现象)
      • [1.3 关键监控数据快照](#1.3 关键监控数据快照)
      • [1.4 OOM 事件时间线](#1.4 OOM 事件时间线)
    • 二、排查过程
      • [2.1 第一层:确认瓶颈类型](#2.1 第一层:确认瓶颈类型)
      • [2.2 第二层:内存状态深度分析](#2.2 第二层:内存状态深度分析)
        • [2.2.1 内存分布拆解](#2.2.1 内存分布拆解)
        • [2.2.2 kswapd 行为分析](#2.2.2 kswapd 行为分析)
        • [2.2.3 bi/bo 异常解读](#2.2.3 bi/bo 异常解读)
      • [2.3 第三层:OOM 历史回溯](#2.3 第三层:OOM 历史回溯)
        • [2.3.1 OOM 触发者与受害者分析](#2.3.1 OOM 触发者与受害者分析)
        • [2.3.2 OOM 后的二次崩溃](#2.3.2 OOM 后的二次崩溃)
      • [2.4 第四层:进程级内存分析](#2.4 第四层:进程级内存分析)
        • [cdvserver 异常行为](#cdvserver 异常行为)
      • [2.5 第五层:代码变更关联分析](#2.5 第五层:代码变更关联分析)
    • 三、根因分析
      • [3.1 直接原因](#3.1 直接原因)
      • [3.2 深层原因](#3.2 深层原因)
      • [3.3 因果链](#3.3 因果链)
      • [3.4 故障定性与排除项](#3.4 故障定性与排除项)
      • [3.5 责任分层](#3.5 责任分层)
    • 四、解决方案
      • [4.1 修复优先级](#4.1 修复优先级)
      • [4.2 方案一:zram(最推荐)](#4.2 方案一:zram(最推荐))
      • [4.3 方案二:JVM 内存参数调优(立竿见影)](#4.3 方案二:JVM 内存参数调优(立竿见影))
      • [4.4 操作命令](#4.4 操作命令)
      • [4.5 部署 zram 前后效果比对](#4.5 部署 zram 前后效果比对)
    • 五、经验总结
      • [5.1 方法论沉淀](#5.1 方法论沉淀)
      • [5.2 改进措施(Action Item)](#5.2 改进措施(Action Item))
    • 附录:
      • 排查命令速查
      • [安装 zram 的代码](#安装 zram 的代码)
      • [卸载 zram 的代码](#卸载 zram 的代码)

故障分析案例:RK3588 NVR 设备内存耗尽与 OOM 频发

项目 内容
设备型号 Rockchip RK3588C NVR DEMO LP4 V10 Board(ARM64)
操作系统 openEuler,内核 5.10.66
总内存 7.6 GB(7677 MiB)
Swap 0 GB(未配置)
故障发生时间 2026-09-10
业务场景 C 系列新产品 UMS 综合管理平台

一、故障现象

1.1 系统级现象

  • Load Average 持续飙高 :200.83, 181.72, 139.35
  • CPU I/O 等待极高 :wa 占比 60%~81%
  • kswapd0 CPU 占用 39%:内核换页守护进程持续满负荷运转
  • 可用内存极低 :free 仅 79 MB / 7677 MB
  • 阻塞队列堆积 :b(uninterruptible sleep)高达 197~212

1.2 业务级现象

  • cdvserver 进程 CPU 近 400%(4 核满载)
  • java 进程被反复 OOM Kill(dmesg 中 6 次以上记录)
  • 系统响应严重迟缓:ssh 登录、命令执行延迟明显
  • page allocation failure:连 512KB 连续物理内存都分配失败

1.3 关键监控数据快照

plaintext 复制代码
# vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
15 212      0  78480   2944 1060260    0    0   599   850    2    1 17 14 62  6  0
 7 178      0  77232   2896 1056584    0    0 78780  1472 39197 65471 24 16  0 60  0
 1 182      0 119528   1848 1005356    0    0 60952  1292 35261 57105 23 15  0 62  0
 3 201      0 117308   3004 1007516    0    0 107180   100 39477 56354  5 17  0 79  0
12 197      0  76504   2596 1062892    0    0 105856   692 39296 58770  4 14  0 81  0
plaintext 复制代码
# top 关键行
top - 16:17:08 up 10 days,  5:59,  7 users,  load average: 200.83, 181.72, 139.35
Tasks: 427 total,   2 running, 424 sleeping,   0 stopped,   1 zombie
%Cpu(s): 32.4 us, 55.1 sy,  0.0 ni,  0.0 id, 10.9 wa,  0.0 hi,  1.5 si,  0.0 st
MiB Mem :   7677.2 total,     79.0 free,   6591.1 used,   1007.1 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.    185.3 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
3332062 root      20   0 4566644 173740      0 S 399.7   2.2   3059:21 cdvserver
3960797 root      20   0 6943076 186944      0 S  55.7   2.4 375:04.30 bus_all_in_one
    132 root      20   0       0      0      0 S  39.1   0.0 189:47.49 kswapd0
2547560 root      20   0 9058832 233040     32 S  38.8   3.0   1165:55 java
3334338 root      20   0 4930572 107660      0 S  16.3   1.4   2450:36 msserver
    457 root      20   0 1853596  14168      0 S  16.0   0.2  28:43.61 containerd
3494077 root       0 -20       0      0      0 I  15.7   0.0   1:09.40 kworker/6:1H-kblockd
3433802 root      20   0   35.6g   1.4g     80 S  10.8  18.5  39:15.42 java
3514454 root      20   0 1768264  15152      0 D   9.2   0.2   0:53.62 swGoServer
   4254 root      20   0  831488  48268      0 S   8.9   0.6 665:49.13 cadvisor
3963112 root      20   0 2487704  28444      0 D   7.7   0.4 117:12.96 fw_base
3334905 root      20   0 6071704 624196      0 S   2.8   7.9 496:51.48 java
3335610 root      20   0   17.9g 501020     64 S   1.8   6.4 313:56.40 java

1.4 OOM 事件时间线

shell 复制代码
[root@openEuler ~]# grep -inw oom /var/log/messages
35586:Sep 10 13:46:34 openEuler kernel: [876463.580958] mali fb000000.gpu: OOM notifier: dev mali0  0 kB
35587:Sep 10 13:46:34 openEuler kernel: [876463.586975] civetweb-worker invoked oom-killer: gfp_mask=0x3100dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO|0x2000000), order=0, oom_score_adj=0
35878:Sep 10 13:46:35 openEuler kernel: [876466.040834] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/sshd.service,task=java,pid=1558603,uid=0
40301:Sep 10 15:16:34 openEuler kernel: [881887.619303] mali fb000000.gpu: OOM notifier: dev mali0  0 kB
40302:Sep 10 15:16:34 openEuler kernel: [881887.859679] mali fb000000.gpu: OOM notifier: dev mali0  0 kB
40303:Sep 10 15:16:34 openEuler kernel: [881887.879838] PullMessageServ invoked oom-killer: gfp_mask=0x1100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
40577:Sep 10 15:16:34 openEuler kernel: [881890.166313] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/sshd.service,task=java,pid=3278850,uid=0
43560:Sep 10 16:37:19 openEuler kernel: [886743.941864] mali fb000000.gpu: OOM notifier: dev mali0  0 kB
43561:Sep 10 16:37:19 openEuler kernel: [886743.947793] dmesg invoked oom-killer: gfp_mask=0x40cc0(GFP_KERNEL|__GFP_COMP), order=3, oom_score_adj=0
43892:Sep 10 16:37:19 openEuler kernel: [886746.695121] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/sshd.service,task=java,pid=3433802,uid=0

要点 :三次 OOM 分别发生在 13:46、15:16、16:37,均在下午时段;每次 OOM 前都伴随 mali fb000000.gpu: OOM notifier ------ GPU 驱动同样感知到全局内存压力,佐证这是系统级内存枯竭而非单一进程问题。


二、排查过程

2.1 第一层:确认瓶颈类型

指标 数值 瓶颈类型判定
us 32.4% 中等 用户态 CPU 消耗存在,但不是主要矛盾
sy 55.1% 极高 内核态消耗过半,ksoftirqd/kswapd 等内核线程繁忙
wa 10.9% → 81% 爆炸性增长 I/O 等待是主要瓶颈,CPU 大部分时间在等 I/O
id 0% 无 CPU 完全无空闲

初步判断 :不是纯 CPU 瓶颈,而是 I/O 阻塞型瓶颈,根因指向内存子系统。

2.2 第二层:内存状态深度分析

2.2.1 内存分布拆解
plaintext 复制代码
总内存:     7677 MB
  ├─ used:   6591 MB (85.8%)  ← 进程实际占用
  ├─ cache:  ~1000 MB (13.1%) ← 文件缓存,可回收
  ├─ buff:     ~3 MB (0.04%)   ← 块设备缓冲,可忽略
  └─ free:     79 MB (1.0%)    ← 真正空闲

关键发现:

  • free 仅剩 79MB,已跌破内核低水位线(low watermark)
  • buff/cache 仅 1GB,且 kswapd 已在疯狂回收
  • used 高达 6.6GB,其中绝大部分是匿名页(anon-rss),无法在无 swap 时回收
2.2.2 kswapd 行为分析

kswapd0 CPU 占 39.1%,说明内核内存回收守护进程持续满负荷运转。其回收路径:

plaintext 复制代码
1. 扫描 LRU 链表,寻找可回收页面
2. 对 clean pagecache → 直接丢弃(无需 I/O)
3. 对 dirty pagecache → 先写回磁盘(writeback),再丢弃
4. 对匿名页(anon)→ 有 swap 则换出,无 swap 则无法回收

当前系统 Swap=0,所以匿名页完全无法回收。

2.2.3 bi/bo 异常解读
指标 数值 含义
bi 60,000~107,000 blocks/s 巨量块设备读
bo 100~1472 blocks/s 块设备写极少

疑问 :为什么 bo 极低,而 bi 爆炸?

答案:

  • bo 低 = 没有脏页需要写回(clean cache 居多),且 Swap=0,匿名页无处可写
  • bi 高 = kswapd 回收了 pagecache 页后,进程立刻触发缺页中断 ,被迫从磁盘重新读入 → "回收-重读"乒乓效应

即:kswapd 在做无用功------回收一页,进程立刻读回一页,白消耗 CPU 和 I/O。

2.3 第三层:OOM 历史回溯

2.3.1 OOM 触发者与受害者分析
触发者(Trigger) 受害者(Victim) 说明
civetweb-worker java (pid=1558603) 触发者是 Web 服务,但被杀的是内存大户 java
PullMessageServ java (pid=3278850) 消息拉取服务触发,被杀的还是 java
dmesg java (pid=3433802) 连 dmesg 这种小工具申请内存都触发 OOM

结论:

  • 触发者是随机的------任何进程在申请内存时都可能触发
  • 受害者永远是 java------OOM killer 的打分算法总是选中内存占用最大的进程
  • java 进程 anon-rss 1~2GB,且长期运行,oom_score 最高
2.3.2 OOM 后的二次崩溃
plaintext 复制代码
[886746.710795] Out of memory: Killed process 3433802 (java) ...
[886748.872875] systemd-journal: page allocation failure: order:7

关键发现:

  • java 被杀后,内核尝试生成 core dump(崩溃转储)
  • core dump 需要分配 order:7 = 128 页 = 512KB 连续物理内存
  • 但系统内存已碎片化到连 512KB 连续空间都分配不出
  • systemd-journal 报 page allocation failure

恶性循环:

plaintext 复制代码
内存耗尽 → OOM 杀 java → 内核做 core dump → 申请 512KB 连续内存失败 → 系统更加不稳定
  ↑___________________________________________________________________________↓

2.4 第四层:进程级内存分析

进程 VIRT RES %MEM 说明
java (pid=3433802) 35.6g 1.4g 18.5% UMS 主服务,RSS 远超 -Xmx,堆外内存膨胀
java (pid=3334905) 6.0g 624MB 7.9% 另一个 JVM 实例
java (pid=3335610) 17.9g 501MB 6.4% 第三个 JVM 实例
cdvserver 4.5g 174MB 2.2% CPU 399%,独立的 CPU 异常问题(见 2.5)
msserver 4.9g 108MB 1.4% 存储服务
其他 400+ 进程 - ~3GB - Docker 容器、系统服务等

java 进程合计占用 > 2.5GB 物理内存。

cdvserver 异常行为

cdvserver CPU 占用 399.7% (近 4 核满载),但 RES 仅 174MB(2.2%),说明:

  • 这是独立的 CPU 问题(如死循环、忙等),不是内存问题
  • 与本次 OOM/内存耗尽故障无直接因果关系
  • 需要 cdvserver 开发单独排查,与 pgdog 连接池适配版本无关联

2.5 第五层:代码变更关联分析

结合同日代码审查(SVN r129024 → r129122),当日上库的 pgdog 连接池适配变更:

  • server.sh、update、initme.sh 引入 pg_dog_functions.sh 公共库
  • 新增 3 个 8G 环境备份/恢复脚本
  • cdvserver 属于存储组件(CDV),不在本次 pgdog 修改范围内

结论 :cdvserver 的 CPU 异常与当日代码变更无直接关联,属于独立问题。


三、根因分析

3.1 直接原因

系统总内存 7.6GB 不足以承载当前业务负载,且无 Swap 缓冲。

内存收支不平衡:

plaintext 复制代码
收入(可用): 7677 MB 总内存 - 内核保留 ≈ 7500 MB
支出(占用):
  ├─ java × 3                 ≈ 2500 MB
  ├─ 各类服务进程(含 cdvserver 174MB)≈ 1500 MB
  ├─ Docker 容器               ≈ 1000 MB
  ├─ PG + pgdog                ≈ 500 MB
  ├─ 系统/内核                 ≈ 1000 MB
  └─ 其他 400+ 进程            ≈ 1000 MB
  ──────────────────────────────
  合计                        ≈ 7500 MB  (刚好吃满)

任何额外的内存分配请求(日志写入、网络缓冲、临时文件)都会触发 OOM。

3.2 深层原因

因素 说明
无 Swap Swap=0 导致匿名页无法换出,内存紧张时无任何缓冲空间
JVM RSS 超出堆配置 单个 java RES 1.4GB,而 ums-8G.sh 中 -Xmx 仅 768MB,说明堆外内存(DirectBuffer/Metaspace/线程栈)膨胀
kswapd 效率瓶颈 无 swap 时只能回收 pagecache,但回收速度 < 分配速度,形成"回收-重读"乒乓
内存碎片化 长期运行后连续物理内存不足 512KB,连 core dump 都失败,OOM 后二次崩溃
业务密度过高 427 个进程(含 Docker 容器)挤在 8GB 内存上,无隔离与限额
cdvserver CPU 异常 独立的 CPU 问题(RES 仅 174MB),不参与内存因果链,需单独排查

关于 VIRT 的说明 :原始记录中"java VIRT 35.6g → -Xmx 设置过大"这一推断不够准确。VIRT 是虚拟地址空间大小,包含 JVM 预留的堆上限、代码缓存、mmap 区域等,不代表实际物理占用,也不等于 -Xmx 配置值。真正的异常信号是 RES(1.4GB) 明显大于 -Xmx(768MB),即堆外内存膨胀,这才是 JVM 侧需要治理的方向。

3.3 因果链

plaintext 复制代码
业务负载增长 / JVM 堆外内存膨胀
  (cdvserver CPU 异常为独立问题,不参与内存因果链)
        ↓
匿名内存占用 6.5GB + pagecache 1GB = 内存耗尽
        ↓
free < low watermark → kswapd 被唤醒
        ↓
kswapd 扫描 LRU,回收 clean pagecache(无 I/O)
        ↓
进程触发缺页中断,重新从磁盘读入 → bi 爆炸、wa 飙至 81%
        ↓
回收速度 < 分配速度 → kswapd 无法入睡 → CPU 39%
        ↓
任意进程申请内存 → 全局 OOM → java 被杀
        ↓
内核尝试 core dump → 512KB 连续内存分配失败
        ↓
系统更加不稳定 → 业务中断 → 用户感知"卡顿/无响应"

3.4 故障定性与排除项

本次故障为典型的"无 Swap + 内存容量不足 + 内存碎片化"复合型系统级故障。

  • 非单一进程故障:OOM 的根本原因是全局内存不足
  • 非磁盘 I/O 故障 :wa 高是内存换页引起,磁盘本身无坏道或性能问题
  • 非代码级 bug :当日上库代码存在若干问题(如 update 脚本中 PGUSRE 拼写错误),但与本次系统级故障无直接因果关系

3.5 责任分层

层级 问题 责任方
系统配置 Swap=0,内存无缓冲 系统运维/交付
应用配置 JVM 堆外内存无上限,RSS 超出 -Xmx 应用开发/部署
进程异常 cdvserver CPU 399%,独立的 CPU 死循环/忙等问题 cdvserver 开发
硬件规格 8GB 内存对这套业务负载偏紧张 产品规划/硬件选型

四、解决方案

4.1 修复优先级

优先级 措施 预期效果
P0(立即) 添加 Swap(4GB)或启用 zram 立刻缓解 OOM,让 kswapd 能换出匿名页
P1(当天) 调低 JVM 堆与堆外内存上限 减少 java 内存占用,降低 OOM 频率
P1(当天) 重启 cdvserver,观察 CPU 是否恢复 临时恢复 CPU 占用,需开发排查根因
P2(本周) cdvserver 开发排查 CPU 死循环/忙等原因 独立问题,不影响 OOM,但影响系统响应
P2(本周) 禁用 core dump 或限制大小 避免 OOM 后的二次内存分配失败
P3(长期) 评估硬件升级至 16GB 根本解决内存容量不足

除添加 Swap 外,另有 2 项不依赖磁盘 I/O 的优化方案,按优先级排列如下。

4.2 方案一:zram(最推荐)

原理:用内存的一部分做压缩块设备,匿名页换出时先压缩再存入该"内存盘",全程不落盘。

bash 复制代码
# 启用 zram,大小设为内存的 50%(约 3.8GB)
modprobe zram
echo 3.8G > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon /dev/zram0

优点:

  • 零磁盘 I/O,不磨损存储(对 eMMC/NAND 类嵌入式存储尤其重要)
  • 压缩比通常 2:1~3:1,3.8GB zram 可存 7~11GB 匿名页
  • Android 系统大量使用,ARM 平台成熟稳定
  • 速度比磁盘 swap 快 10 倍以上

缺点:占用少量 CPU 做压缩/解压(但远小于 kswapd 疯狂扫描 LRU 的开销)。

4.3 方案二:JVM 内存参数调优(立竿见影)

当前配置(ums-8G.sh):

bash 复制代码
-Xms256m -Xmx768m   # 但实际 RES 1.4GB,说明堆外内存(DirectBuffer/Metaspace)膨胀

优化方向:

bash 复制代码
-Xms256m -Xmx512m              # 限制堆
-XX:MaxMetaspaceSize=128M      # 限制元空间
-XX:MaxDirectMemorySize=256M   # 限制堆外内存

效果:单个 java 进程有望从 1.4GB 降到 600MB 左右,3 个 java 共释放 2GB+。

4.4 操作命令

bash 复制代码
# 临时添加磁盘 swap(无 zram 时的备选)
dd if=/dev/zero of=/swapfile bs=1M count=4096
mkswap /swapfile
swapon /swapfile

# 释放缓存
echo 3 > /proc/sys/vm/drop_caches

# 禁用 core dump
echo 0 > /proc/sys/kernel/core_pattern

4.5 部署 zram 前后效果比对

可以看到部署 zram 后, 系统负载极大下降, 可用内存明显增高, majflt, pgscank,pgscand 明显降低, 效果十分显著.


五、经验总结

5.1 方法论沉淀

  1. wa 高不等于磁盘故障 。本例中 wa 飙到 81%,但 bo 极低,说明不是写盘压力,而是内存回收引发的"回收-重读"乒乓。判断 I/O 瓶颈前,先看 bi/bo 的方向和比例。
  2. OOM 的受害者不等于元凶。三次 OOM 的触发者各不相同(civetweb-worker、PullMessageServ、dmesg),被杀的却永远是 java。查 OOM 要找"是谁把内存吃光的",而不是"谁被杀了"。
  3. 无 Swap 等于没有缓冲带。7.6GB 内存 + 0 Swap,一旦匿名页占满,内核除了杀进程别无选择。嵌入式/拷机环境应默认配置 zram。
  4. 警惕 OOM 后的二次故障。core dump 需要连续物理内存,碎片化严重时反而会加剧不稳定,需提前禁用或限制。
  5. VIRT 大不是问题,RSS 大才是。不要拿 VIRT 判断 JVM 配置是否合理;RSS 明显超出 -Xmx 时,应查堆外内存(DirectBuffer、Metaspace、线程栈)。
  6. sy 高要定位到具体内核线程 。本例 55.1% 的 sy 中,kswapd0 独占 39.1%,直接把方向引向内存子系统。
  7. 先看内存构成再看进程。先区分 Anon/File/Slab,再定位到进程 RSS,避免在 427 个进程里无目标地抓"内存大户"。

5.2 改进措施(Action Item)

序号 改进项 责任方 时限
1 拷机/交付环境默认启用 zram,纳入基线镜像 系统运维/交付 立即
2 治理 JVM 参数:限制堆、元空间、堆外内存上限 应用开发/部署 当天
3 cdvserver CPU 399% 问题单独开单排查(死循环/忙等) cdvserver 开发 本周
4 禁用或限制 core dump,避免 OOM 二次崩溃 系统运维 本周
5 接入内存水位、PSI(/proc/pressure/memory)、load 的监控与告警 运维 本周
6 评估 16GB 硬件规格,重新核定该业务负载的内存基线 产品规划/硬件选型 长期

附录:

排查命令速查

bash 复制代码
# 查看内存使用详情
free -h
cat /proc/meminfo | grep -E "MemTotal|MemFree|Buffers|Cached|SwapTotal|SwapFree"

# 查看 OOM 历史
dmesg -T | grep -i oom
grep -inw oom /var/log/messages

# 查看内存压力
cat /proc/pressure/memory

# 查看最耗内存的进程
ps aux --sort=-%mem | head -20
smem -r | head -20

# 查看某进程内存详情
cat /proc/<pid>/status | grep -E "VmRSS|VmSize|VmSwap"

# 查看 kswapd 统计
cat /proc/vmstat | grep -E "pgscan|pgsteal|pgmajfault"

# 临时添加 swap
dd if=/dev/zero of=/swapfile bs=1M count=4096
mkswap /swapfile
swapon /swapfile

# 释放缓存
echo 3 > /proc/sys/vm/drop_caches

# 禁用 core dump
echo 0 > /proc/sys/kernel/core_pattern

安装 zram 的代码

bash 复制代码
#!/bin/bash
# =====================================================================
# zram swap 部署脚本(192.117.10.60 定制)
# 参数:zram0 / 3G / zstd / 优先级100
#       swappiness=80 / page-cluster=0 / min_free_kbytes=262144(256M)
# 特性:幂等,可重复执行(自动清理旧 zram 配置后重建)
# 用法:bash deploy_zram.sh
# =====================================================================
set -e

# ---------------- 配置区(调整参数只改这里) ----------------
ZRAM_SIZE="3G"        # zram 容量上限(≈40% 物理内存,实际占用=压缩后数据)
ZRAM_ALGO="zstd"      # 压缩算法(备选 lz4:CPU 更省、压缩比更低)
ZRAM_PRIO=100         # swap 优先级(高于未来可能加的磁盘 swap)
# ---- 内核参数 ----
SWAPPINESS=80         # 换冷页、保 cache,但别 100 免得 CPU 颠簸
PAGE_CLUSTER=0        # 关掉给磁盘设计的预读,按需解压
MIN_FREE_KB=262144    # 256MB:兼顾原子分配兜底池与 kswapd 提前回收

# ---------------- Step 1: 前置检查 ----------------
if ! modinfo zram >/dev/null 2>&1; then
    echo "[ERROR] Kernel does not support zram, abort."
    exit 1
fi
echo "[OK] zram kernel module supported."

# ---------------- Step 2: 清理旧配置(保证幂等) ----------------
# 若 zram0 已是活动 swap,先安全下线:确认 MemAvailable 足够容纳原始数据量
if swapon -s 2>/dev/null | grep -q zram0; then
    # 用 mm_stat 第2列 orig_data(未压缩原始量,swapoff 回填内存的真实体积)
    ORIG_DATA_B=$(awk '{print $2}' /sys/block/zram0/mm_stat 2>/dev/null || echo 0)
    MEM_AVAIL_B=$(awk '/MemAvailable/{print $2*1024}' /proc/meminfo)
    SAFETY_MARGIN=$((256*1024*1024))
    if [ $((MEM_AVAIL_B - SAFETY_MARGIN)) -lt "$ORIG_DATA_B" ]; then
        echo "[ERROR] MemAvailable(${MEM_AVAIL_B}B) - margin < zram data(${ORIG_DATA_B}B), abort."
        exit 1
    fi
    swapoff /dev/zram0
    echo "[OK] Old zram0 swap offloaded."
fi
if [ -e /sys/block/zram0 ]; then
    echo 1 > /sys/block/zram0/reset 2>/dev/null || true
fi
rmmod zram 2>/dev/null || true

# ---------------- Step 3: 创建并启用 zram swap ----------------
modprobe zram num_devices=1
# 算法必须在 disksize 之前设置(disksize 一旦设置设备即进入使用态)
echo "$ZRAM_ALGO" > /sys/block/zram0/comp_algorithm
echo "$ZRAM_SIZE" > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon -p "$ZRAM_PRIO" /dev/zram0
echo "[OK] zram0 enabled: size=$ZRAM_SIZE algo=$ZRAM_ALGO prio=$ZRAM_PRIO"

# ---------------- Step 4: 内核参数(立即生效 + 持久化) ----------------
sysctl -w vm.swappiness="$SWAPPINESS"
sysctl -w vm.page-cluster="$PAGE_CLUSTER"
sysctl -w vm.min_free_kbytes="$MIN_FREE_KB"

cat > /etc/sysctl.d/99-zram-swap.conf <<EOF
# zram swap 配套内核参数(由 deploy_zram.sh 维护,卸载脚本会删除本文件)
# 换冷页、保 cache,但别 100 免得 CPU 颠簸
vm.swappiness = $SWAPPINESS
# 关掉给磁盘设计的预读,按需解压
vm.page-cluster = $PAGE_CLUSTER
# 256MB:兼顾原子分配兜底池与 kswapd 提前回收(出厂自算值仅 11MB)
vm.min_free_kbytes = $MIN_FREE_KB
EOF
echo "[OK] sysctl applied and persisted to /etc/sysctl.d/99-zram-swap.conf"

# ---------------- Step 5: systemd 开机自启(仅管理 zram 设备) ----------------
cat > /etc/systemd/system/zram-swap.service <<EOF
[Unit]
Description=Configure zram swap device ($ZRAM_SIZE, $ZRAM_ALGO)
Documentation=man:zramctl(8)
After=systemd-sysctl.service multi-user.target

[Service]
Type=oneshot
RemainAfterExit=yes
# 依次:加载模块 -> 设算法 -> 设容量 -> mkswap -> swapon(内核参数走 sysctl.d,此处不重复)
ExecStart=/bin/bash -c 'modprobe zram num_devices=1; echo $ZRAM_ALGO > /sys/block/zram0/comp_algorithm; echo $ZRAM_SIZE > /sys/block/zram0/disksize; mkswap /dev/zram0; swapon -p $ZRAM_PRIO /dev/zram0'
# 回退:swapoff 解压回内存后卸载模块(需保证内存足够)
ExecStop=/bin/bash -c 'swapoff /dev/zram0; rmmod zram'

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable zram-swap.service >/dev/null 2>&1
echo "[OK] zram-swap.service enabled at boot."

# ---------------- Step 6: 验证输出 ----------------
echo "================ VERIFY ================"
swapon -s
echo "----------------------------------------"
zramctl
echo "----------------------------------------"
sysctl vm.swappiness vm.page-cluster vm.min_free_kbytes
echo "----------------------------------------"
free -m
echo "========================================"
echo "[DONE] zram swap deployed."

卸载 zram 的代码

bash 复制代码
#!/bin/bash
# =====================================================================
# zram swap 卸载脚本(与 deploy_zram.sh 配套)
# 功能:安全下线 zram -> 重置设备 -> 卸载模块 -> 移除自启 -> 还原内核参数
# 还原:swappiness=60 / page-cluster=3 / min_free_kbytes=11211(出厂自算值)
# 用法:bash uninstall_zram.sh
# =====================================================================
set -e

# 出厂默认值(该设备内核自算 min_free_kbytes ≈ 11211KB)
DEF_SWAPPINESS=60
DEF_PAGE_CLUSTER=3
DEF_MIN_FREE_KB=11211

# ---------------- Step 1: 确认 zram swap 存在 ----------------
if ! swapon -s 2>/dev/null | grep -q zram0; then
    echo "[WARN] zram0 is not an active swap, skip swapoff."
else
    # ---------------- Step 2: 安全校验(关键) ----------------
    # swapoff 回填量 = orig_data(未压缩原始量,mm_stat 第2列),非压缩后大小
    ORIG_DATA_B=$(awk '{print $2}' /sys/block/zram0/mm_stat 2>/dev/null || echo 0)
    MEM_AVAIL_B=$(awk '/MemAvailable/{print $2*1024}' /proc/meminfo)
    SAFETY_MARGIN=$((256*1024*1024))
    if [ $((MEM_AVAIL_B - SAFETY_MARGIN)) -lt "$ORIG_DATA_B" ]; then
        echo "[ERROR] MemAvailable(${MEM_AVAIL_B}B) - margin < zram data(${ORIG_DATA_B}B)."
        echo "        Unsafe to swapoff now. Free memory first (stop services or drop cache),"
        echo "        then retry. Abort."
        exit 1
    fi
    echo "[OK] Memory check passed: avail=${MEM_AVAIL_B}B, zram-data=${ORIG_DATA_B}B."

    # ---------------- Step 3: 下线 swap ----------------
    swapoff /dev/zram0
    echo "[OK] zram0 swap offloaded."
fi

# ---------------- Step 4: 重置并卸载 zram 模块 ----------------
if [ -e /sys/block/zram0 ]; then
    echo 1 > /sys/block/zram0/reset 2>/dev/null || true
fi
rmmod zram 2>/dev/null || true
echo "[OK] zram module removed."

# ---------------- Step 5: 移除 systemd 开机自启 ----------------
systemctl disable --now zram-swap.service >/dev/null 2>&1 || true
rm -f /etc/systemd/system/zram-swap.service
systemctl daemon-reload
echo "[OK] zram-swap.service removed."

# ---------------- Step 6: 还原内核参数并删除持久化文件 ----------------
sysctl -w vm.swappiness="$DEF_SWAPPINESS"
sysctl -w vm.page-cluster="$DEF_PAGE_CLUSTER"
sysctl -w vm.min_free_kbytes="$DEF_MIN_FREE_KB"
rm -f /etc/sysctl.d/99-zram-swap.conf
echo "[OK] sysctl restored: swappiness=$DEF_SWAPPINESS page-cluster=$DEF_PAGE_CLUSTER min_free_kbytes=$DEF_MIN_FREE_KB"

# ---------------- Step 7: 验证输出 ----------------
echo "================ VERIFY ================"
swapon -s                       # 应无 zram0 条目
ls /sys/block/ | grep zram || echo "no zram device left"
sysctl vm.swappiness vm.page-cluster vm.min_free_kbytes
free -m
echo "========================================"
echo "[DONE] zram swap uninstalled."
相关推荐
2401_862880821 小时前
Linux中断、中断子系统、内核同步互斥机制
linux·驱动开发·嵌入式硬件
姓刘的哦1 小时前
GT911触摸芯片
linux·stm32
Android系统攻城狮1 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_read调用流程与实战(七十五)
linux·运维·服务器·gstreamer音视频·gstreamer音视频进阶
一条破秋裤2 小时前
04_控制GPIO通用化_从寄存器到gpiod
linux
星恒随风2 小时前
Linux进程信号(二):深入理解信号阻塞、未决、sigaction、中断机制、可重入函数与SIGCHLD
linux·运维·服务器·笔记·学习
额额额对了2 小时前
Linux 驱动实战:ADXL345 驱动
linux·运维·服务器
Android系统攻城狮10 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_delay调用流程与实战(七十二)
linux·运维·服务器·gstreamer音视频·音视频进阶
快乐的韭菜根根11 小时前
ubuntu 安装slurm并配置
linux·运维·ubuntu
倒头就睡的小比特12 小时前
Linux基础指令3
linux·服务器