MemAvailable 还有 29GB,系统却报内存压力?——Ubuntu 24.04 CIFS 内核 Page Cache 泄漏排查实录

搬运并适配自国外社区真实案例。原文出处见文末。

现象

Ubuntu 24.04 生产服务器,跑 Java 应用 + PostgreSQL + Nginx,挂载了 3 个 CIFS 网络共享做备份和文件交换。

每运行约一个月,系统开始出现以下症状:

  • free -h 显示 MemAvailable 还有 29GB(总共 48GB),看起来内存充裕
  • 但 syslog 疯狂刷 systemd-journald: Under memory pressure, flushing caches.
  • drop_caches 只能释放约 200MB,完全不符合预期
  • 服务器逐渐失去响应,SSH 连不上,最终只能强制重启

最诡异的地方:重启后一切正常,meminfo 各项指标也对得上。但跑一段时间后,问题必现。

排查过程

第一回合:常规检查------全部正常

c 复制代码
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           47Gi        17Gi       1.5Gi        15Mi        28Gi        29Gi

available 有 29G,看起来不像内存不足。查看 /proc/meminfo 关键字段:

bash 复制代码
MemTotal:       49285376 kB
MemFree:         1611852 kB
MemAvailable:   29837696 kB   # 29GB,看起来很多
Buffers:               0 kB
Cached:           367752 kB   # ⚠️ 只有 367MB?
Active(file):    7379008 kB   # 7GB
Inactive(file): 20216280 kB   # 20GB

第二回合:发现矛盾

到这里,有经验的同学应该已经发现问题了。

正常的 Linux 内存统计中,Active(file) + Inactive(file) 约等于 Cached(都是 file-backed pages),差值通常在几百 MB 以内。

但这里:

  • Active(file) + Inactive(file) ≈ 7GB + 20GB = 27GB
  • Cached = 367MB

差了将近 27GB! 这 27GB 的 page cache 去哪儿了?

更诡异的是 MemAvailable 依然显示 29GB------这个值理论上应该是 MemFree + 大部分 file pages + 可回收 slab

第三回合:深入内核统计

/proc/vmstatnr_file_pages

bash 复制代码
$ grep nr_file_pages /proc/vmstat
nr_file_pages 91938    # 91938 pages × 4KB ≈ 367MB

这和 Cached 的 367MB 对得上。但 Active(file) + Inactive(file) 的 27GB 是哪里来的?

结论 :内核的 page cache 记账出了问题。Cached 字段的计算公式为:

c 复制代码
Cached = nr_file_pages - SwapCached - Buffers

(源码:fs/proc/meminfo.c

nr_file_pages 没有被正确更新。内核在回收这些页面时,没有减少 Active(file)/Inactive(file) 的 LRU 计数,导致了"幽灵内存"。

第四回合:定位根因

进一步排查发现,这个 VM 上挂载了 3 个 CIFS 共享来做自动备份:

bash 复制代码
$ cat /proc/self/mountinfo | grep cifs
//x/x /mnt/y cifs rw,vers=3.1.1,cache=strict,...
//y/y /mnt/x cifs rw,vers=3.1.1,cache=strict,...
//y/x /home/backups/x cifs rw,vers=3.1.1,cache=strict,...

buddyinfopagetypeinfo 显示严重的内存碎片化(大量小块可用,但缺乏连续大块):

bash 复制代码
Node 0, zone Normal    424  22383  233  # Unmovable / Movable / Reclaimable 块数

碎片化本身不是根因,而是内存泄漏的次生灾害

根因:CIFS 内核模块的 Page Cache 泄漏

经过 Ubuntu 社区和内核开发者的追踪,最终定位到问题:

Linux 内核 6.8.0-32 中引入的一个 CVE 安全修复补丁,意外导致了 CIFS 客户端的 page cache 泄漏。

具体来说:每次在 CIFS 挂载点上关闭一个文件时,内核 CIFS 模块会泄漏 "lease key" 对象关联的 page cache 页面。这些页面虽然不再被引用,但:

  • nr_file_pages 计数器没有被更新(所以 Cached 显示不变)
  • LRU 链表的 Active(file)/Inactive(file) 计数也没有被减少(所以看起来还有 27GB)
  • 内存实际已经泄漏,无法被回收利用

影响的版本

  • 引入版本:kernel 6.8.0-32(Ubuntu 24.04 默认内核线的某个更新)
  • 修复版本:kernel 6.8.0-64
  • 影响范围:Ubuntu 24.04 LTS 全线(generic/azure/aws 等 flavor),任何使用 CIFS 挂载的系统

泄漏速度

根据社区实测:

  • 使用 cache=strict(默认):最快,每天可能泄漏数 GB
  • 使用 cache=loose:泄漏略慢,但依然会触发 OOM(甚至在 45% 内存占用时就触发)
  • 高频文件操作(如备份脚本中反复 write→close→delete)泄漏最快

解决方案

方案一:升级内核(根本解决)

bash 复制代码
# 确认当前内核版本
uname -r

# 如果 < 6.8.0-64,升级
sudo apt update
sudo apt install linux-image-generic linux-modules-extra-generic

# 如果稳定版仓库还没到 6.8.0-64,可以用 -proposed
sudo apt install linux-image-generic-proposed linux-modules-extra-generic-proposed

sudo reboot

重启后验证:

bash 复制代码
uname -r
# 应显示 6.8.0-64-generic 或更高

方案二:回退到安全内核(临时处理)

如果当前环境不能立即升级(如没有重启窗口),回退到已知安全版本:

bash 复制代码
sudo apt install linux-image-6.8.0-31-generic linux-modules-6.8.0-31-generic
sudo apt-mark hold linux-image-generic linux-headers-generic
sudo reboot

等 6.8.0-64 进入稳定仓库后,再解除 hold 升级。

方案三:挂载选项 workaround(不推荐生产)

如果实在无法换内核,可以临时用 cache=none 挂载:

bash 复制代码
mount -o remount,cache=none /mnt/y

代价巨大:CIFS 吞吐量下降约 3.5 倍(实测 mongodump 从 1 小时变成 3.5 小时)。

方案四:加 swap 缓解碎片(辅助手段)

即使修复了泄漏,内存碎片化问题可能遗留。建议添加小量 swap(256-512MB)来缓解碎片:

bash 复制代码
fallocate -l 512M /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

如果担心关键应用被 swap out,在 systemd service 中设置:

ini 复制代码
[Service]
MemoryMin=4G

如何自检你是否中招

如果你的 Ubuntu 24.04 服务器使用了 CIFS 挂载,跑一下这个一行的 awk:

bash 复制代码
awk '/MemAvailable:|MemFree:|Buffers:|Cached:/ {a[$1]=$2} END {print a["MemAvailable:"] - (a["MemFree:"] + a["Buffers:"] + a["Cached:"])}' /proc/meminfo

如果这个值持续增长(每天增加几百 MB 到几 GB),你就中招了。

更精确的检测------看 Active(file)+Inactive(file)Cached 的差值:

bash 复制代码
awk '/^Active\(file\):|^Inactive\(file\):|^Cached:/ {a[$1]=$2}
     END {diff = a["Active(file):"] + a["Inactive(file):"] - a["Cached:"];
          printf "Active(file)+Inactive(file) = %d kB\nCached = %d kB\n差值 = %d kB (%.1f GB)\n",
          a["Active(file):"] + a["Inactive(file):"], a["Cached:"], diff, diff/1048576}' /proc/meminfo

正常系统的差值应该在几百 MB 以内。如果你的差值高达十几 GB,恭喜,你遇到了这个 bug。

启示

  1. MemAvailable 不是绝对可靠的。它是内核根据当前状态估算的"可分配"内存,但当内核自身的记账机制出错时,这个数字会严重失真。
  2. 不要只看 top/free 的表面数字 。当 MemAvailable 显示充裕但系统行为反常时,深挖 /proc/meminfo 的明细字段,交叉验证。
  3. CVE 安全修复也可能引入新 bug。这个泄漏正是由某个 CVE 修复引入的。升级前做好回归测试。
  4. CIFS 在 Linux 上的 page cache 行为与本地文件系统不同。遇到 CIFS 相关问题时,首先怀疑缓存层。

原始出处


本文首发于 CSDN 专栏《运维漏洞指南》,专注于搬运和适配国外社区已验证的生产级运维排障方案。欢迎关注。

相关推荐
starrysky8102 小时前
KeyError: 'xxx' —— 字典里没这个键,但你的代码以为有
angular.js
starrysky8102 小时前
FP8量化实战:vLLM与SGLang部署DeepSeek显存减半、吞吐翻倍——Agent推理引擎篇(二)
angular.js
starrysky81010 小时前
vLLM与SGLang启动参数调优实战:从默认配置到生产级吞吐量翻倍——Agent推理引擎篇
angular.js
starrysky81011 小时前
【03】ImportError: cannot import name 'X' —— 模块在,名字没了
angular.js
starrysky81011 小时前
systemd-journald日志限速导致生产日志丢失:Suppressed XXXX messages完整排查
angular.js
Jolyne_2 天前
Angular基础速通
前端·angular.js
starrysky8102 天前
Agent 的终端安全怎么做?6 种沙箱后端 + 危险命令审批 + sudo 无痕处理的完整拆解
angular.js
starrysky8102 天前
Flash Attention 安装地狱六重崩溃:CUDA_HOME not set、undefined symbol、预编译轮子不兼容、pip 编译两小时失败——逐一击破
angular.js
starrysky8103 天前
nvidia-smi 显示 8GB 空闲,为什么 PyTorch 报 CUDA out of memory?——CUDA 缓存分配器底层原理
angular.js