登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux procfs smaps_rollup 统计进程内存的读取方法

来源:17golang原创

时间:2026-10-17 08:21:27 394浏览 收藏

排查 Linux 服务的内存上涨时,如果只看 VmRSS,经常还不够回答“共享库和共享页到底算给谁”。更直接的入口是 /proc//smaps_rollup:它把进程所有内存映射的统计项合并成一份文本,适合做一次采集和趋势记录。

最小结论是:先读取目标 PID 的 smaps_rollup,重点记录 Rss、Pss、Pss_Anon、Pss_File 和 Pss_Shmem,再根据权限错误、PID 消失和字段缺失分别处理。Pss 不是 Rss 的别名,它会按共享页比例分摊,更适合估算进程实际承担的内存。

官方参考地址:https://docs.kernel.org/filesystems/proc.html

smaps_rollup 到底汇总了什么

smaps 会按每个虚拟内存区域列出明细,smaps_rollup 则把这些映射对应字段相加,保留一个汇总入口。Linux Kernel 文档还特别列出了 Pss_Anon、Pss_File 和 Pss_Shmem,它们分别帮助区分匿名页、文件页和共享内存页。

阅读时先抓住三个区别:

  • Rss 表示当前驻留在物理内存中的页总量,共享页会在多个进程的 RSS 中分别出现。
  • Pss 会按共享关系分摊页面,因此多个进程的 PSS 相加通常比直接相加 RSS 更接近整体占用。
  • 所有数值通常以 kB 展示,做监控入库时要保留单位或统一转换,不能把数字直接当作字节。
smaps_rollup 从进程内存映射到 Rss 和 Pss 汇总字段的结构说明图
图1:smaps_rollup 汇总字段与内存映射的结构说明图,不是终端或运行截图。

先用命令读取关键字段

临时排查不必先解析完整 smaps。下面的命令只保留常用汇总项,输出仍是文本示意,实际字段是否存在要以当前内核和权限为准。

# 把目标 PID 换成需要排查的进程号,并只查看汇总字段
pid=1234
grep -E '^(Rss|Pss|Pss_Anon|Pss_File|Pss_Shmem):' "/proc/$pid/smaps_rollup"

# 只看总量时保留第一行之外的关键字段,避免把单位误当成字节
awk '/^(Rss|Pss|Pss_Anon|Pss_File|Pss_Shmem):/ { print $1, $2, $3 }' \
  "/proc/$pid/smaps_rollup"

如果命令返回“权限不允许”,先确认采集进程与目标进程的用户、容器 PID 命名空间和系统的 ptrace 访问限制;如果返回文件不存在,也可能是 PID 已经退出,而不一定是 procfs 没有挂载。不要把一次读取失败直接记成“内存为 0”。

在 Python 中解析并区分失败原因

应用采集时,可以把每一行拆成字段名和带单位的值。读取器只关心汇总文件的键值格式,不依赖字段排列顺序;缺少非核心字段时保留已有结果,便于跨内核版本运行。

from pathlib import Path


def read_smaps_rollup(pid: int) -> dict[str, int]:
    # 只读取指定 PID 的汇总文件,不展开每个 VMA 的 smaps 明细
    path = Path(f"/proc/{pid}/smaps_rollup")
    values: dict[str, int] = {}

    try:
        # 一次读取文本后逐行解析,数值统一保存为 kB
        for line in path.read_text(encoding="utf-8").splitlines():
            if ":" not in line:
                continue
            key, raw = line.split(":", 1)
            parts = raw.split()
            if not parts or parts[0].isdigit() is False:
                continue
            # 只接受内核输出的整数值,避免把异常文本写入指标
            values[key] = int(parts[0])
    except PermissionError as exc:
        # 权限失败要单独上报,不能伪装成进程内存为零
        raise RuntimeError(f"cannot read {path}: permission denied") from exc
    except FileNotFoundError as exc:
        # PID 可能已经退出,也可能位于另一个 PID 命名空间
        raise RuntimeError(f"process {pid} is gone or procfs is unavailable") from exc
    except ProcessLookupError as exc:
        # 进程在读取窗口内退出时,调用方应稍后重试或丢弃本次样本
        raise RuntimeError(f"process {pid} disappeared during collection") from exc

    return values


memory = read_smaps_rollup(1234)
rss_kb = memory.get("Rss", 0)
pss_kb = memory.get("Pss", 0)

这里的返回值仍是 kB。如果监控系统要求字节,可以在写入前统一乘以 1024,但要把字段名写成 rss_bytes、pss_bytes,避免下游误解。Rss 或 Pss 缺失时使用 0 只是代码示例里的默认值,生产采集最好同时记录“字段缺失”状态。

Python smaps_rollup 读取器的字段解析、单位转换和错误出口结构说明图
图2:smaps_rollup 读取器的字段解析与错误边界说明图,不是代码编辑器截图。

权限、PID 消失与接口边界

读取 /proc/ 是观察接口,不是稳定的历史数据库。采集器应把下面三类情况分开记录:

现象更可能的原因处理建议
Permission denied采集身份无权查看目标进程调整采集权限或改用允许的目标范围,不把值写成 0
No such file or directoryPID 已退出、命名空间不同或 procfs 不可用记录采集失败原因,必要时重新枚举 PID
字段不完整内核接口差异、权限或读取窗口问题保留已读字段,并把缺失键作为样本元数据

smaps_rollup 适合回答“一个进程当前承担了多少映射内存”以及“PSS 中匿名页和文件页怎样变化”。它不替代 cgroup 的资源限制统计,也不能告诉你是哪一个具体 VMA 造成了增长;需要定位映射来源时,再按权限和成本评估读取完整 smaps。

采集前后的速查清单

  • 先确认 PID 属于当前采集器可见的 PID 命名空间。
  • 优先记录 Rss、Pss、Pss_Anon、Pss_File、Pss_Shmem 和单位。
  • 权限错误、PID 消失、字段缺失分别打标签,不要统一转换成零值。
  • 趋势采集使用 smaps_rollup,映射级定位再考虑完整 smaps。

相关问题

smaps_rollup 和 status 里的 VmRSS 应该选哪个?

需要快速、低成本的粗略指标时可以看 VmRSS;需要 PSS 及匿名页、文件页拆分时选 smaps_rollup。两者的统计语义和读取成本不同,不建议混成一个指标。

为什么多个进程的 PSS 相加仍然和机器内存不完全一致?

PSS 只描述进程映射相关的分摊结果,内核缓存、不可计入的内存、采集时间差和其他资源仍会造成差异。它更适合比较进程趋势,而不是当作整机内存总账。

读取 smaps_rollup 失败要不要重试?

如果原因是 PID 在采集窗口内退出,短暂重试可能有意义;如果是权限或命名空间错误,重试不会修复根因,应先调整采集身份和可见范围。

把 smaps_rollup 当成“进程级汇总接口”,把 Pss 当成“共享页分摊后的估算”,再把失败原因保留下来,基本就能避免 Linux 内存采集里最常见的误读。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>