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

Linux /proc 读取进程内存指标的方法

来源:17golang原创

时间:2026-10-17 08:36:55 194浏览 收藏

在 Linux 上读取进程内存,最实用的分层方式是:高频监控先读 /proc/PID/status,需要定位到具体内存映射时再读 /proc/PID/smaps。前者适合拿到 VmRSS、RssAnon、RssFile、RssShmem 和 VmSwap 等汇总指标,后者能把每个映射的 Rss、Pss、Private_Dirty、Swap 等信息展开,但读取成本更高。

把 status 当作低成本趋势数据,把 smaps 当作按需诊断数据;不要因为一次采集读不到文件,就把进程内存写成 0。
  • 进程级趋势:优先解析 /proc/PID/status。
  • 共享内存归因:关注 smaps 中的 Pss,而不是只看 Rss。
  • 高频采集:固定间隔读取 status,限制单次扫描字段。
  • 异常诊断:按需读取 smaps,并记录耗时、权限和 PID 是否仍存在。
Linux proc status 与 smaps 的内存指标关系说明图
图1:status 提供进程级汇总,smaps 展开到映射级内存指标。

先用 /proc/PID/status 做进程级汇总

/proc/PID/status 是面向人和程序都比较容易读取的文本快照。采集器通常不需要保存全部字段,可以只取内存相关键:VmSize 表示虚拟地址空间规模,VmRSS 表示驻留集近似值,RssAnon、RssFile、RssShmem 用来拆分驻留内存来源,VmSwap 则提示换出规模。

下面的 Python 片段只解析字段名和带单位的数值。注释说明了单位转换与未知字段处理;它没有把不存在的字段当成零,而是保留字段缺失状态。

from pathlib import Path

def read_status_memory(pid: int) -> dict[str, int]:
    # status 是文本快照;单位通常为 kB,统一换算成字节便于比较。
    path = Path(f"/proc/{pid}/status")
    wanted = {"VmSize", "VmRSS", "RssAnon", "RssFile", "RssShmem", "VmSwap"}
    result: dict[str, int] = {}
    try:
        for line in path.read_text().splitlines():
            key, sep, value = line.partition(":")
            if not sep or key not in wanted:
                # 忽略非目标字段,也兼容不同内核暴露的字段集合。
                continue
            parts = value.split()
            if not parts:
                continue
            # status 的内存数值带 kB;没有单位时不擅自换算。
            result[key] = int(parts[0]) * 1024 if len(parts) > 1 and parts[1] == "kB" else int(parts[0])
    except FileNotFoundError:
        # PID 可能在打开文件前退出;调用方应记录 missing,而不是写入内存为零。
        return {}
    except PermissionError:
        # 权限受限时保留空结果,让上层区分 permission_denied 与真实零值。
        return {}
    return result

这里的空字典只表示本次读取没有得到指标,生产采集器应同时返回状态码或错误类型。否则进程刚退出、容器 PID 命名空间不一致、权限被收紧,都会被误判为“内存突然降为 0”。

需要映射归因时读取 /proc/PID/smaps

smaps 按每个虚拟内存区域展开统计。它适合回答“这部分驻留内存属于哪类映射”“共享页应该如何分摊”这类 status 无法回答的问题。Rss 是映射当前驻留的页数,Pss 会按共享关系分摊;Private_Dirty、Shared_Dirty 和 Swap 则帮助区分私有脏页、共享脏页和换出页。

smaps 的信息量来自逐映射统计,因此不应像 status 一样对所有进程、所有周期无条件读取。下面的解析器只保留映射头和少数字段,遇到权限或进程消失时返回明确状态。

from pathlib import Path

def read_smaps(pid: int) -> tuple[str, list[dict[str, int | str]]]:
    # 每个地址区间开启一个记录,后续键值行归入该映射。
    current: dict[str, int | str] | None = None
    mappings: list[dict[str, int | str]] = []
    try:
        for line in Path(f"/proc/{pid}/smaps").read_text().splitlines():
            if "-" in line and line.split(" ", 1)[0].count("-") == 1:
                # 映射头只保存地址范围,避免把路径和权限当成指标。
                current = {"range": line.split()[0]}
                mappings.append(current)
                continue
            key, sep, value = line.partition(":")
            if current is None or not sep or key not in {"Rss", "Pss", "Private_Dirty", "Swap"}:
                continue
            parts = value.split()
            if parts:
                # 这些字段通常以 kB 表示,统一转成字节;缺失字段不补零。
                current[key] = int(parts[0]) * 1024 if len(parts) > 1 and parts[1] == "kB" else int(parts[0])
    except FileNotFoundError:
        return "missing", []
    except PermissionError:
        return "permission_denied", []
    return "ok", mappings

这段代码只展示解析边界,不代表需要把全部映射长期存储。实际系统通常只保留聚合结果,例如 Pss 总和、Private_Dirty 最大的若干映射,以及本次读取耗时。

把读取成本分成快照层和诊断层

一套可扩展的采集器可以分成两层:status 是快照层,按固定周期读取少数字段;smaps 是诊断层,只有进程超过内存阈值、趋势异常或用户主动排障时才读取。这样既能保持时间序列的连续性,也能避免每个采样周期都扫描大量映射。

规模化部署时还要记录三个运行信号:读取耗时、返回状态和采集字段数。smaps 读取慢不一定代表进程异常,也可能只是映射数量多;PID 消失不应参与内存聚合;权限拒绝则应进入采集器自身的错误指标。

Linux 进程内存采集的快照层与诊断层结构说明图
图2:采集器把 status 快照层与 smaps 诊断层分开,按成本选择数据粒度。

如果只想监控 RSS 趋势,status 已经足够;如果要解释共享库、匿名映射或脏页来源,才值得接受 smaps 的额外开销。不要把两者的数字直接相加:status 是进程级汇总,smaps 是映射级明细,统计口径不同。

PID 消失、权限和字段变化怎么处理

读取 /proc 不是对稳定文件的普通读取。进程可能在打开前退出,命名空间中的 PID 可能与宿主机不同,权限策略也可能让同一个路径在不同运行身份下不可见。采集器至少应区分 ok、missing、permission_denied 和 parse_error。

字段方面,应按键名选择性解析并允许未知字段存在。内核版本、配置和进程状态会影响可见字段;稳定做法是把缺失字段表示为“未提供”,只对确实读到的指标做聚合。若必须比较多台机器,先统一单位、采样周期和 PID 命名空间,再比较趋势。

常见问题

VmRSS 和 smaps 的 Rss 一定相等吗?

它们都描述驻留内存,但一个是进程级汇总、一个是映射级明细,读取时刻和统计口径可能不同,不应把瞬时差异直接当作故障。

为什么不每秒读取一次 smaps?

smaps 需要逐映射提供更多统计,映射多的进程读取成本更高。高频趋势使用 status,smaps 留给异常诊断或低频采样更稳妥。

读取失败时可以把指标写成 0 吗?

不可以。0 是真实指标,读取失败是采集状态;混用会制造假的内存下降,应保留错误类型并让上层决定是否插值。

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