本机 Linux 两页实验里,第一页的最后一个字节是 offset 4095,写它没有问题。再往后一字节,offset 4096 已经进入第二页:同一组 Linux 映射的第二页普通可写时,这次越界把用于观察改写的 sentinel(哨兵字节)从 0xA5 改成 0x5A,没有信号,子进程正常退出;第二页设为 PROT_NONE 后,同一落点触发 SIGSEGV / SEGV_ACCERR,故障地址相对映射起点的偏移为 4096,写入没有完成。
两次危险写都在 fork 子进程的匿名私有映射中执行。基线写受写时复制隔离,没有改动父进程视图;信号处理器也只记录结果并立即退出,不把段错误当成可以继续业务的恢复协议。
不可访问页将静默写入转换为故障
可以把两页地址想成走廊里相邻的两间房。第一页容纳逻辑缓冲区,第二页仍可写时,越过门槛的指针只是走进另一间开放房间,操作系统不知道它已经越过数组边界。Linux 实验用 mprotect(..., PROT_NONE) 锁住第二页后,这一页才成为 Guard Page,也就是一页不能访问的地址区。
本机 Linux 页大小是 4096 字节,所以 4095 仍属于第一页,4096 刚好落在禁区起点。实验恢复第二页读写权限后,4096 又能写入。Linux 实验的两组都对 offset 4096 发起单字节写入,关键差别是第二页的访问权限。

同一个越界落点,页面权限不同,终态也不同。
故障地址因此比一个笼统的“程序异常终止”更有用:它能核对访问是否真的落进了预先布置的禁区,而不是把任何段错误都归功于 Guard Page。
保护范围只覆盖真正命中的访问
Guard Page 不知道数组有多长,它只知道某个虚拟页能不能访问。对象末端若停在可写页中间,小幅越界仍可能留在同一页;错误指针若一次跳过整页禁区,落到另一段可写映射,也可能没有触发这道陷阱。因此,保护效果首先取决于布局:对象边界要贴近页边界,错误地址还必须真正落入保护区。
xv6 开放书稿在用户栈下方和内核栈映射之间安排 guard 区,意图是让命中的溢出更早 page fault 或 panic,而不是继续覆盖别处。这说明了早失败的价值,却不能代替 Linux 或 Windows 的实现证明。
Guard Page 买到的不是绝对安全,而是把命中保护区的一部分静默越界,换成更早、更可定位的失败。
本次实验确认的是一页大小的虚拟地址区间及其访问保护。它没有测量物理 RAM、RSS、页表或 VMA、TLB 与性能;xv6 的一种实现甚至先分配页再撤掉用户访问权,所以“固定浪费一页物理内存”和“完全不占物理内存”都不是通用结论。
Linux 与 Windows 使用不同保护契约
Linux 实验中的 PROT_NONE 在权限被显式改回前持续拒绝访问。Windows 官方文档中的 PAGE_GUARD 则是附加在底层页面保护上的一次性告警;首次访问产生 STATUS_GUARD_PAGE_VIOLATION 后,guard 标志会被清除,之后能否访问由底层保护决定。它也不能和 PAGE_NOACCESS 组合。本轮没有 Windows 实测,不能把这段官方契约写成本机复现。

同叫保护页,持续时间、异常类型和重新布防责任并不相同。
工程验收要把三件事连起来看:先让被保护对象的末端与页面布局匹配,再用固定越界落点断言异常类型、相对故障地址和写入终态,最后核对保护是持续还是一次性,并把地址空间、物理驻留、内核元数据与性能分别测量。线程栈、调试分配器、运行时栈和隔离共享内存都能采用这种思路,但不能把所有内存安全问题交给一页禁区。
规范依据来自 Linux man-pages 6.19 的 mprotect(2)、xv6-riscv 开放书稿固定版本与 Microsoft Creating Guard Pages。
