# 第 18 章实验:真实进程里的 VPN 与 offset ## 做什么 `vpn.c` 打印进程中几类对象(代码、全局变量、堆、栈)的虚拟地址,并按本机页大小(通常 4KB) 把它们拆成 **VPN**(地址右移 12 位)和 **offset**(低 12 位)。然后用 `mmap` 申请 16 页新内存, 通过 Linux 的 `/proc/self/pagemap` 查看这些页在写入前后是否"存在"(pagemap 记录的 bit 63 就是存在位)。 ## 怎么跑 ```bash make ./vpn ``` ## 该观察到什么 1. x86-64 用户态地址是 48 位,按 4KB 页拆分后 VPN 有 36 位,offset 12 位。 2. `heap` 和 `heap+1` 在同一页:VPN 相同,offset 只差 4。 3. 新 `mmap` 的 16 页在写入前 **0 页存在**,写入后 **16 页存在**:OS 在第一次访问(缺页)时才真正分配物理页帧, 这就是后面第 21 章要讲的按需分配。 4. pagemap 里的 PFN 对普通用户显示为 0:自 Linux 4.0 起内核对非特权用户隐藏物理帧号 (防止利用物理地址信息进行 Rowhammer 之类的攻击)。用 `sudo ./vpn` 可以看到真实 PFN。 ## 为什么 页表对程序是透明的:程序看到的永远是虚拟地址。VPN/offset 的拆分完全由页大小决定; PFN 是 OS 决定的、随时可能变化(例如被换出后再换入到另一个帧),所以程序不应该也无法依赖它。 每次运行地址都会不同:这是地址空间布局随机化(ASLR)。