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

Linux procfs 怎样查看进程内存映射

来源:17golang原创

时间:2026-11-12 10:35:13 441浏览 收藏

我排查进程内存占用时,最先想看的通常不是调试器,而是 Linux 自带的 procfs。对一个已知 PID,/proc/PID/maps 能告诉我们进程当前有哪些虚拟地址区域、每个区域具有什么访问权限、对应哪个文件偏移,以及是否关联了文件路径。要看每个区域的 RSS、PSS 等更细的统计,再切换到 /proc/PID/smaps。

官方文档地址:https://docs.kernel.org/filesystems/proc.html

这篇只解决“怎样查看进程内存映射”这一件事:先读懂 maps 的一行记录,再处理 PID 和权限,最后判断什么时候需要 smaps。它不涉及修改进程内存、内存转储或调试器命令。

先看 /proc/PID/maps 返回什么

最小读取方式就是把 PID 放进路径:

pid="1234"

# 读取目标进程当前的虚拟内存映射;输出每行代表一个区域。
cat "/proc/$pid/maps"

官方文档给出的记录形态可以概括为:

address           perms offset  dev   inode      pathname
7f2c10000000-7f2c10021000 r--p 00000000 08:01 12345 /usr/lib/libexample.so
7f2c10021000-7f2c1008a000 r-xp 00021000 08:01 12345 /usr/lib/libexample.so
55a00000-55a21000 rw-p 00000000 00:00 0      [heap]

从左到右看,一行里最重要的是六组信息:

  • address:左侧是起始虚拟地址,右侧是结束地址,区间通常按半开区间理解。
  • perms:四个字符分别表达读、写、执行,以及共享 s 或私有写时复制 p。
  • offset:如果区域来自文件,它表示映射在该文件中的偏移。
  • dev 与 inode:帮助把映射关联到设备和文件对象;匿名区域常见 inode 为 0。
  • pathname:可能是库文件,也可能是 [heap]、[stack]、[vdso] 等内核提供的特殊标记,或者为空。
Linux procfs 的 maps 记录字段与进程地址区域关系说明图
图1:/proc/PID/maps 字段关系说明图;它帮助理解地址区域和字段的对应关系,是静态说明图,不是终端截图。

我第一次看这类输出时容易只盯着路径。实际上,r-xp 和 rw-p 往往代表同一个文件的不同映射区域;要判断某个区域能否写入或执行,应该先看权限列,再看路径列。

用 self 和指定 PID 读取映射

如果只想查看当前命令本身对应的进程,可以用 /proc/self/maps,这样不需要先手动找 PID:

# self 会解析为“正在读取这个路径的进程”。
cat /proc/self/maps

# 只保留带文件路径的映射,便于先观察可执行文件和共享库。
awk 'NF >= 6 { print }' /proc/self/maps

查看其他进程时,把已知 PID 放入路径即可:

pid="1234"

# 先确认目标目录存在,避免把进程已退出误判成权限问题。
if [ -d "/proc/$pid" ]; then
  # 读取映射;失败时保留内核返回的权限错误。
  cat "/proc/$pid/maps"
else
  # PID 可能已经退出,/proc 下的目录也会随之消失。
  printf '进程不存在:%s\n' "$pid" >&2
  exit 1
fi

读取自己的 /proc/PID/* 通常不需要额外权限;读取其他进程的只读信息时,内核文档提到需要满足相应的 CAP_SYS_PTRACE 与 PTRACE_MODE_READ 检查,某些环境还可由 CAP_PERFMON 满足。容器、不同用户和受限安全策略都可能让同一条命令在不同环境中得到 Permission denied。

按权限和特殊路径定位区域

拿到输出后,可以先按权限分组,而不是把每个地址都当成一个独立故障。下面的命令只做文本筛选,不改变进程:

pid="1234"

# 显示可执行区域,常用于定位程序正文、共享库代码段或 JIT 相关区域。
grep -E ' [r-][w-]x[sp] ' "/proc/$pid/maps"

# 显示堆和栈的特殊映射,帮助区分常规文件映射与进程运行时区域。
grep -E '\[heap\]|\[stack' "/proc/$pid/maps"

常见标记可以这样理解:

标记或权限通常代表排查时看什么
r-xp私有、可读、可执行区域程序或共享库的代码映射,结合 pathname 和 offset 判断来源
rw-p私有、可读写区域数据段、堆或私有写时复制区域
[heap]进程堆观察地址范围,不把它当成磁盘文件
[stack]主线程栈区分主线程栈与其他线程的栈映射
[vdso]内核为用户态提供的虚拟动态对象它没有普通共享库路径,不能按磁盘文件处理

这里的“通常”很重要:不同架构、内核配置、进程运行方式和映射来源都会影响实际输出。maps 适合先回答“哪些区域存在、权限是什么、来自哪里”,不适合单独回答“每个区域当前占用了多少物理内存”。

需要内存统计时切换到 smaps

当问题从“有哪些映射”变成“哪个映射消耗了多少内存”,我会改读 smaps:

pid="1234"

# smaps 在 maps 的每个区域后面增加 Size、Rss、Pss 等统计字段。
cat "/proc/$pid/smaps"

# 只提取区域首行和 RSS/PSS,减少排查时的视觉噪声。
awk '/^[0-9a-f]+-[0-9a-f]+ / || /^Rss:|^Pss:/' "/proc/$pid/smaps"

smaps 是基于 maps 的扩展。一个区域的首行仍然包含地址、权限、偏移、设备号、inode 和路径,后面会跟着 Size、Rss、Pss、共享/私有脏页等字段。简单选择可以记成:

  • 只想知道区域范围、读写执行权限和文件来源:看 maps。
  • 想知道区域有多少页驻留、共享成本如何分摊:看 smaps。
  • 只想要全进程汇总:可以关注 smaps_rollup,但它仍然属于统计读取,成本和权限要单独评估。
Linux procfs 自身进程和其他 PID 的权限边界以及 maps 与 smaps 区别说明图
图2:procfs 访问权限与 maps/smaps 选择说明图;它表达信息粒度和访问边界,不是截图或实际执行结果。

读取 smaps 比读取 maps 更重,脚本如果只需要判断区域来源,就没有必要默认扫描全部统计字段。先用 maps 缩小范围,再对少量目标或必要时刻读取 smaps,通常更容易控制开销。

处理读取时的权限与一致性边界

这一步是我后来补上的经验:能打开文件,不代表拿到的是不会变化的“内存快照”。官方文档明确指出,读取 maps 或 smaps 本身可能与进程修改地址空间同时发生竞态;完整读取可以获得内核定义的保证,但分段读取时不能假设所有行来自同一时刻。

因此排查脚本至少要记录三件事:

  1. 读取开始时的 PID,以及目标是否在读取期间退出。
  2. 读取的是 maps 还是 smaps,不能把二者的字段混为一谈。
  3. 当前用户、容器和能力边界;对其他 PID 失败时,先区分权限拒绝和进程消失。
pid="1234"

# 把权限失败、进程退出和成功读取区分开,便于日志记录。
if [ ! -r "/proc/$pid/maps" ]; then
  printf '无法读取 maps:请检查 PID 是否存在及当前权限\n' >&2
  exit 2
fi

# 单次读取后再处理文本,避免脚本多次打开文件扩大观察窗口。
maps_data=$(cat "/proc/$pid/maps") || {
  printf '读取过程中进程可能已退出或映射发生变化\n' >&2
  exit 3
}
printf '%s\n' "$maps_data"

这段脚本不能把 procfs 变成严格的事务快照,但能让失败原因更接近真实情况。特别是长时间运行的进程,PID 可能在读取前后被回收再分配;要做持续监控时,最好同时记录进程启动标识或其他身份信息,不要只把 PID 当作永久身份。

整理一份可复用的排查清单

  1. 先确认 PID 仍然存在;只看当前进程时优先用 /proc/self/maps。
  2. 读取 maps,把地址区间、权限、offset、dev、inode 和 pathname 分开看。
  3. 遇到 [heap]、[stack]、[vdso] 或空路径时,不要按普通库文件推断。
  4. 需要 RSS、PSS 或脏页信息时,再读取 smaps 或评估 smaps_rollup。
  5. 读取其他 PID 失败时,同时排查权限、容器边界和进程在读取期间退出。
  6. 对结果做时间序列比较时,记录读取时间、PID 身份和读取文件类型,避免把竞态差异当成确定的内存变化。

对我来说,procfs 的价值在于它把“进程内存”拆成了可以直接阅读的区域记录:maps 负责告诉你区域在哪里、能做什么、来自哪里;smaps 再补充这些区域的内存统计。先选对文件,再解释权限和一致性边界,排查结果会比只看一个总内存数字可靠得多。

相关问题

为什么 /proc/PID/maps 里有些行没有 pathname?

这通常表示映射没有关联到普通文件,或者属于匿名映射。可以结合权限、offset、inode 和相邻区域判断,不要把空路径当成读取失败。

maps 和 smaps 可以互相替代吗?

不能。maps 描述地址区域和权限,smaps 在此基础上提供每个映射的内存统计,读取成本和输出规模也更大。

读取其他进程的 maps 被拒绝怎么办?

先确认 PID 没有退出,再检查当前用户、容器边界和内核能力。不要为了读取一个普通排障字段就无条件扩大权限,先明确任务是否真的需要目标进程的映射信息。

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