进程地址空间是映射区域及权限的集合,并非一条固定的“代码、数据、堆、栈”连续分区。/proc/PID/maps 描述哪些地址当前有映射,smaps 则补充驻留与共享等统计。先区分虚拟地址、底层文件或匿名存储以及实际驻留页,才能正确解释内存使用。
64 位不意味着可用全部 64 位地址
指针宽度、CPU 实现的地址位数、内核页表配置和用户态地址空间范围是不同概念。x86-64 常见四级或五级页表配置,arm64 又有不同的虚拟地址宽度与页粒度组合。不能把“每个 Linux 进程都是 48 位或 57 位”写成跨架构事实。
地址空间中有未映射空洞、保留区域和受策略限制的范围。映射成功后也不一定已经为每一页分配独立物理内存;缺页、零页、文件缓存与写时复制会逐步决定实际页状态。
读懂 maps 的一行
70000000-70004000 r-xp 00002000 08:01 12345 /opt/demo
这是人为构造的格式示例。地址区间为半开区间,长度是 0x4000;映射从文件偏移 0x2000 开始。r/w/x 表示读写执行权限,p 表示 private,s 表示 shared。设备号和 inode 有助于确认底层对象;路径可能带 deleted,名字本身并非稳定身份。
若访问地址为 0x70000120,对这段文件支持区域的相应偏移为 0x2000 + 0x120 = 0x2120。但 .bss 等没有对应文件字节的部分,不能一概套成可读取的磁盘偏移。proc_pid_maps(5)
ELF 按程序头 segment 加载,不能将每行 VMA 与一个 section 一一对应。一个 PT_LOAD 可因权限调整等原因拆成多行;.bss 尾部可与文件末页混合,再接匿名零填充映射。结合 readelf -lW 的 offset、filesz、memsz 解释。
private 不是始终独占物理页
MAP_PRIVATE 表示修改不会按共享映射方式写回给其他映射使用者。fork 后的私有匿名页通常先共享物理内容,写入时才复制;私有文件映射的未修改页也可共享文件缓存。
因此 malloc 用 mmap 得到的普通私有匿名区域,在 fork 后仍可能发生 COW。使用 mmap 不是“fork 友好所以没有 COW”的充分条件。真正共享修改的 MAP_SHARED 例子见 共享内存与信号量。
用父子进程区分私有修改与共享修改
下面先为两页分别建立 MAP_PRIVATE 与 MAP_SHARED 匿名映射,写入相同初值 7,再 fork。子进程把两处都写成 9;父进程等待子进程结束后读取,私有页仍为 7,共享页已为 9。fork 的内存关系
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
long page_size = sysconf(_SC_PAGESIZE);
if (page_size <= 0) return 1;
size_t size = (size_t)page_size;
unsigned char *private_page = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (private_page == MAP_FAILED) return 1;
unsigned char *shared_page = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (shared_page == MAP_FAILED) { munmap(private_page, size); return 1; }
private_page[0] = shared_page[0] = 7;
pid_t child = fork();
if (child == 0) {
private_page[0] = 9;
shared_page[0] = 9;
_exit(private_page[0] == 9 && shared_page[0] == 9 ? 0 : 1);
}
int failed = child == -1;
if (!failed) {
int status;
pid_t waited;
do { waited = waitpid(child, &status, 0); }
while (waited == -1 && errno == EINTR);
failed = waited != child || !WIFEXITED(status) || WEXITSTATUS(status) != 0;
if (!failed) {
printf("parent: private=%u shared=%u\n",
(unsigned)private_page[0], (unsigned)shared_page[0]);
failed = private_page[0] != 7 || shared_page[0] != 9;
}
}
if (munmap(private_page, size) == -1) failed = 1;
if (munmap(shared_page, size) == -1) failed = 1;
return failed;
}
保存为 cow-demo.c,执行 cc -Wall -Wextra cow-demo.c -o cow-demo 后运行。预期 parent: private=7 shared=9。父子进程看到相同的虚拟指针值,却可以通过私有映射看到不同内容;因此仅比较地址数值,不能判断两个进程是否共享修改。
本例用 waitpid 确认子进程已完成写入,父进程没有在子进程写入期间并发轮询。实际共享内存队列还需要进程间同步,不能把“映射为 shared”当成自动完成发布、排序和通知。实验验证的是可见性语义,不能从这两个字节直接测出内核到底复制了多少物理页。
栈:主线程与 pthread 栈不同
主线程栈在 exec 时建立,常以 [stack] 标识,并可在符合条件的缺页时扩展,受资源限制、相邻映射和保护规则约束。它不是由 brk 分配,随意访问栈附近任意地址也不会都被内核自动接纳。
pthread 栈通常由线程库建立匿名映射,大小与 guard 策略可通过属性调整,不能简单照搬主栈的自动增长模型。默认大小依启动时限制、平台和线程库而变,不是固定 8 MiB。
普通无名字的匿名 VMA 可能属于线程栈、分配器 arena、JIT 或其他对象。现代 maps 不再普遍给每个线程栈显示 [stack:TID];不能看到一段 rw-p 就凭位置确认它一定是栈。
堆:malloc 对象不都在 [heap]
[heap] 通常对应 program break 管理的传统堆范围。malloc 也会使用其他匿名映射供 arena 或单独大块分配,所以统计这一行无法得到全部动态分配量。
小分配可能从线程缓存或 arena 已有空闲块取得,不会每次调用 brk。是否使用独立 mmap 受分配大小、当前空闲块、阈值、运行历史与具体分配器影响,不能用固定 128 KiB 一刀切。malloc 内部
vDSO、vvar 与历史 vsyscall
vDSO 是内核提供给用户空间的一小段 ELF 代码,可在支持的架构与内核上加速特定调用;相关只读数据映射常显示为 vvar。它们是特殊内核提供的映射,不应当成普通 malloc 匿名页。
函数集合、符号版本和是否回退到系统调用由实现决定。即使 clock_gettime 常走 vDSO,也不能推断任何时钟和任何内核都不会执行 syscall。通过辅助向量等约定定位,不要硬编码地址。vdso(7)
x86-64 的历史 vsyscall 使用固定地址范围,可能由内核模拟或禁用,具体由配置决定。它与可随机化的 vDSO 不是同一套接口,arm64 也不照搬该固定地址。
观察自己的实际布局
以下程序打印几个对象地址,并读取自身 maps。cat /proc/self/maps 中 self 指的是 cat 进程,不是启动它的 shell;查看目标时应使用目标 PID,或像这里在目标内部读取。
#include <stdio.h>
#include <stdlib.h>
static int global_value = 7;
int main(void) {
int local_value = 3;
void *allocated = malloc(4096);
if (allocated == NULL) return 1;
printf("global=%p stack=%p allocated=%p\n",
(void *)&global_value, (void *)&local_value, allocated);
FILE *maps = fopen("/proc/self/maps", "r");
if (maps == NULL) { free(allocated); return 1; }
char line[1024];
int failed = 0;
while (fgets(line, sizeof line, maps) != NULL) {
if (fputs(line, stdout) == EOF) { failed = 1; break; }
}
if (ferror(maps)) failed = 1;
if (fclose(maps) == EOF) failed = 1;
free(allocated);
if (fflush(stdout) == EOF) failed = 1;
return failed;
}
编译后找出三个地址各落在哪个区间。多次独立启动时基址可能变化,但具体地址差异不是语言语义保证。没有初始化并访问每一页的分配,也不适合作为“分配多少就立刻增加多少 RSS”的实验。
VmSize 描述虚拟地址范围规模,RSS 统计驻留页,共享页在多个进程的 RSS 中可能重复计算。PSS 按共享者分摊,有助于比较一组进程的驻留贡献。smaps_rollup 提供汇总,smaps 保留逐映射细节。
这些指标仍不等于分配器的在用字节,也不等于 cgroup 记账;页表、内核对象、共享缓存及统计时点会造成不同视角。映射与计数会随进程运行改变,跨多个 /proc 文件读取不自动形成同一原子快照。内核 /proc 文档
用页的账本解释 PSS,而不是把它当成另一种 malloc 大小
假设父子进程共同映射 4 个 4 KiB 驻留页,没有其他共享者。每个进程 RSS 都计算完整的 4 页,PSS 则每页计算一半。子进程对其中 1 页发生 COW 后,留下 3 个共同页,加上父子各自的 1 个私有页。smaps 指标
from fractions import Fraction
page = 4096
before_rss_each = 4 * page
before_pss_each = 4 * Fraction(page, 2)
after_rss_each = (3 + 1) * page
after_pss_each = 3 * Fraction(page, 2) + page
assert before_rss_each == after_rss_each == 16384
assert before_pss_each == 8192
assert after_pss_each == 10240
assert 2 * before_pss_each == 4 * page
assert 2 * after_pss_each == 5 * page
该模型解释为什么复制发生后,单看 RSS 可能看不出变化。真实 smaps 还会遇到共享者变化、页面回收、巨大页及统计精度等因素;不要把此处 2 KiB 的变化当作每次运行 cow-demo 必须出现的整进程差值。
从打印地址反查 VMA,要验证区间包含关系
前面程序的 global、stack 与 allocated 地址可以逐个与 maps 的起止地址比较,使用 start ≤ address < end。若某地址恰好等于 end,它已经不属于这一行。路径名字可以辅助说明映射来源,但不能代替此判断,也不能证明对象仍然存活。
| 地址落点 | 已确认的信息 | 仍要补充的信息 |
| 文件映射内 | 所属文件区域与权限 | ELF 段、符号和是否经过私有修改 |
| [heap] 内 | 位于传统 program break 区域 | 对应哪个分配对象、是否已 free |
| 无名 rw-p 内 | 私有可写匿名区域 | 线程栈、arena 或其他用途 |
| 无映射 | 此时没有该地址的普通 VMA | 地址来源、生命周期和映射变化 |
把同色同名方块理解为同一物理页的两条映射。每发生一次 COW,子进程仍映射四页,所以 RSS 不变;但其中一页不再与父进程分摊,PSS 从半页计费变成整页计费。这里统计的只是这四页,不能与真实进程全部 smaps 差值直接等同。
PSS=p∈resident∑sharing count(p)page size(p)
正在呈现知识画面
RSS 没变,物理页为什么增加了?COW 使原先共享的物理页分裂为两个私有副本。两进程 RSS 可以不变,PSS 和独立物理页总量却增加。
{"id":"cow-page-accounting","title":"RSS 没变,物理页为什么增加了?","summary":"COW 使原先共享的物理页分裂为两个私有副本。两进程 RSS 可以不变,PSS 和独立物理页总量却增加。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div id=\"pages\" class=\"choices\"><button data-page=\"0\"><span data-i18n=\"write\"></span> 0</button><button data-page=\"1\"><span data-i18n=\"write\"></span> 1</button><button data-page=\"2\"><span data-i18n=\"write\"></span> 2</button><button data-page=\"3\"><span data-i18n=\"write\"></span> 3</button></div><div class=\"track-row\"><span data-i18n=\"parent\"></span><div id=\"parent\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"child\"></span><div id=\"child\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"rss\"></span><strong id=\"rss\"></strong></div><div><span data-i18n=\"pss\"></span><strong id=\"pss\"></strong></div><div><span data-i18n=\"physical\"></span><strong id=\"physical\"></strong></div><div><span data-i18n=\"copies\"></span><strong id=\"copies\"></strong></div></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><p class=\"formula\" id=\"equation\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let copied=new Set();function draw(){const n=copied.size;cells('parent',[...'ABCD'].map(text=>({text,cls:'on'})));cells('child',[...'ABCD'].map((text,i)=>({text:copied.has(i)?text+'′':text,cls:copied.has(i)?'gold':'on'})));document.querySelectorAll('[data-page]').forEach(e=>{e.disabled=copied.has(+e.dataset.page);e.setAttribute('aria-pressed',String(e.disabled));});set('rss',16);set('pss',8+2*n);set('physical',16+4*n);set('copies',n);set('equation','PSS = (4 − '+n+') × 4/2 + '+n+' × 4 = '+(8+2*n)+' KiB');}document.querySelectorAll('[data-page]').forEach(e=>e.onclick=()=>{copied.add(+e.dataset.page);draw()});$('#reset').onclick=()=>{copied.clear();draw()};draw();","audio":false,"strings":{"child":"子进程映射","copies":"已复制页数","heading":"RSS 没变,物理页为什么增加了?","intro":"点击任意页,让子进程对该私有页写入一次。","parent":"父进程映射","physical":"独立物理页(KiB)","pss":"每进程 PSS(KiB)","reset":"重置","rss":"每进程 RSS(KiB)","scope":"4个4 KiB驻留普通页,只有父子两个共享者,无回收或巨大页。字母标识物理页身份,不是虚拟地址。","scopeLabel":"模型范围与约定","write":"子进程写入"}}
从地址定位问题
先确认故障地址是否有映射,再看权限、所属对象、偏移与生命周期;“地址位于 rw-p 映射内”不能证明对象访问合法,可能仍是同页内越界或释放后使用。
理解布局能缩小定位范围,对象级错误仍需调试符号与 内存检测工具。随机化与执行权限的保护作用见 ASLR 与安全防护。
关键词:virtual memory, VMA, maps, smaps, RSS, PSS, stack, brk, mmap, COW, vDSO, ASLR