什么是内存保护?越界访问如何被检测和阻止
这是“操作系统系列教程”的第三十八篇。前面几篇我们讲了虚拟内存、分页、分段、页表、缺页中断、页面置换、内存碎片、交换空间、内存映射和内存分配算法。这些机制解决的是“内存怎么用”的问题。但还有一个同样重要的问题:内存怎么保护?如果一个程序试图访问不属于它的内存,操作系统如何发现并阻止?这就是——内存保护要解决的问题。
一个“越界”的严重后果
先来看一段简单的 C 代码:
int arr[10];
arr[100] = 42; // 越界写入
这行代码试图写入数组 arr 的第 100 个元素,但数组只有 10 个元素。这是一个典型的越界访问(Out-of-Bounds Access)。
如果没有内存保护,这行代码可能会:
- 覆盖相邻变量的值,导致程序逻辑错误;
- 覆盖函数返回地址,导致程序崩溃或跳转到恶意代码;
- 覆盖操作系统数据,导致整个系统崩溃;
- 被恶意利用,执行任意代码(缓冲区溢出攻击)。
在现实中,越界访问是许多安全漏洞的根源。著名的“缓冲区溢出”攻击,就是利用程序没有检查边界,写入超出缓冲区的内容,覆盖关键数据,从而控制程序执行流。
操作系统如何防止这些问题?答案就是——内存保护(Memory Protection)。
内存保护是操作系统的核心安全机制之一。它确保每个进程只能访问自己的内存空间,不能读写其他进程或操作系统的内存。没有内存保护,多进程系统就无法安全运行,一个程序的错误可能拖垮整个系统。
这篇文章,我们就来把“内存保护”这件事讲清楚。
说明:本文涉及虚拟内存、页表、段表、特权级、MMU 等概念。初学者如果遇到暂时看不懂的术语,可以先跳过细节,重点理解“操作系统通过硬件和软件机制,确保每个进程只能访问自己的内存”这一核心思想。
一、内存保护到底是什么?
1.1 从“门禁”说起
我们可以用“小区门禁”来类比内存保护。
| 小区场景 | 操作系统 |
|---|---|
| 小区 | 计算机系统 |
| 住户 | 进程 |
| 住户的房间 | 进程的内存空间 |
| 门禁卡 | 进程的页表/段表 |
| 保安 | MMU(内存管理单元) |
| 禁止进入他人房间 | 禁止访问其他进程内存 |
| 禁止进入设备间 | 禁止访问内核内存 |
| 监控录像 | 审计日志 |
小区里,每个住户只能进自己的房间,不能进别人的房间,更不能进设备间。保安(MMU)通过门禁卡(页表)验证住户身份,决定是否放行。如果有人试图闯入他人房间,保安会阻止并报警(触发异常)。
1.2 内存保护的定义
给出一个正式的定义:
内存保护(Memory Protection)是操作系统通过硬件和软件机制,限制进程只能访问其合法内存区域,防止进程读写其他进程或操作系统的内存,从而保证系统的安全性、稳定性和隔离性。
这个定义里有五个关键点:
- 硬件和软件机制:需要 MMU、页表、特权级等硬件支持,也需要操作系统管理;
- 限制访问:进程只能访问自己的内存;
- 防止越界:不能读写其他进程或内核的内存;
- 安全性:防止恶意程序窃取或破坏数据;
- 稳定性:防止一个程序的错误影响其他程序。
1.3 内存保护在系统中的位置
图 1:内存保护在系统中的位置。 MMU 负责地址转换和权限检查,越界访问触发异常。
从图 1 可以看出,内存保护的核心是权限检查。每次内存访问,MMU 都会检查该访问是否合法。如果合法,正常执行;如果不合法,触发异常,由操作系统处理。
二、为什么需要内存保护?
2.1 多进程环境的需求
现代操作系统同时运行多个进程。每个进程有独立的虚拟地址空间,但物理内存是共享的。如果没有内存保护:
- 进程 A 可以读取进程 B 的密码、密钥等敏感数据;
- 进程 A 可以修改进程 B 的代码或数据,导致进程 B 崩溃;
- 进程 A 可以修改内核数据,控制系统;
- 一个程序的 bug 可能拖垮整个系统。
内存保护确保进程之间互不干扰,每个进程只能访问自己的内存。
2.2 安全性的需求
内存保护是操作系统安全的基础。没有它,以下安全机制都无法实现:
| 安全机制 | 依赖内存保护 |
|---|---|
| 进程隔离 | 是 |
| 用户态/内核态隔离 | 是 |
| 地址空间布局随机化(ASLR) | 是 |
| 栈保护 | 是 |
| 数据执行保护(DEP) | 是 |
2.3 稳定性的需求
内存保护不仅防止恶意攻击,也防止程序错误:
- 空指针解引用不会破坏系统;
- 数组越界不会覆盖其他进程数据;
- 栈溢出不会覆盖内核数据。
一个程序的崩溃通常只影响自己,不会拖垮整个系统。
图 2:内存保护的三大需求。 隔离、安全、稳定。
2.4 一个真实的案例
2014 年,著名的“心脏出血”(Heartbleed)漏洞就是内存保护失效的典型案例。OpenSSL 的一个 bug 允许攻击者读取服务器内存中的任意数据,包括私钥、密码等敏感信息。
如果内存保护机制更完善,这种漏洞的影响可以被限制。但根本原因是程序没有检查边界,而内存保护只能防止跨进程访问,不能防止进程内部越界。
这说明,内存保护是多层防御的一部分,需要和程序自身的边界检查配合。
三、内存保护的实现机制
内存保护通过多种机制实现,主要包括:
| 机制 | 说明 |
|---|---|
| 地址空间隔离 | 每个进程有独立的虚拟地址空间 |
| 页表权限位 | 页表项中的读、写、执行权限 |
| 段表权限位 | 段表项中的读、写、执行权限 |
| 特权级 | 用户态和内核态分离 |
| 越界检查 | 段长检查、页号检查 |
| 地址对齐检查 | 对齐访问要求 |
| 只读保护 | 代码段只读 |
| 不可执行保护 | 数据段不可执行 |
图 3:内存保护的主要机制。
四、地址空间隔离
4.1 什么是地址空间隔离?
地址空间隔离(Address Space Isolation)是内存保护的基础。每个进程有独立的虚拟地址空间,进程 A 的虚拟地址和进程 B 的虚拟地址互不相关。
进程 A 的虚拟地址 0x1000 可能映射到物理地址 0x5000,进程 B 的虚拟地址 0x1000 可能映射到物理地址 0x9000。两个进程访问同一个虚拟地址,实际访问的是不同的物理内存。
4.2 地址空间隔离的实现
地址空间隔离通过页表实现。每个进程有自己的页表,页表定义了该进程的虚拟地址到物理地址的映射关系。
当操作系统切换进程时,同时切换页表。MMU 使用当前进程的页表进行地址转换,因此不同进程的相同虚拟地址会映射到不同的物理地址。

图 4:地址空间隔离。 不同进程的相同虚拟地址映射到不同物理地址。
4.3 地址空间隔离的效果
地址空间隔离确保:
- 进程 A 无法通过虚拟地址访问进程 B 的物理内存;
- 进程 A 不知道进程 B 的数据在哪里;
- 即使进程 A 的虚拟地址和进程 B 相同,也不会冲突。
4.4 地址空间隔离的局限
地址空间隔离本身不能阻止进程访问内核内存。如果内核内存映射在进程的地址空间中(通常如此),进程可能通过虚拟地址访问内核。这就需要特权级和页表权限位来进一步保护。
五、页表权限位
5.1 页表项的权限位
页表项中通常包含以下权限位:
| 权限位 | 说明 |
|---|---|
| 读(R) | 允许读取该页 |
| 写(W) | 允许写入该页 |
| 执行(X) | 允许执行该页的代码 |
| 用户/内核(U/S) | 用户态可访问 / 仅内核态可访问 |
| 有效(V) | 该页是否在内存中 |
| 访问(A) | 该页是否被访问过 |
| 修改(D) | 该页是否被修改过 |
图 5:页表项的权限位。 用户/内核位决定该页是否允许用户态访问。
5.2 权限检查的过程
每次内存访问,MMU 都会检查:
- 有效位:该页是否在内存中?如果不在,触发缺页中断;
- 用户/内核位:当前是用户态还是内核态?如果是用户态,且该页标记为“仅内核”,触发保护异常;
- 读/写/执行位:当前操作是什么?如果操作与权限不匹配,触发保护异常。
图 6:页表权限检查的完整流程。
5.3 一个具体的例子
假设一个进程试图写入代码段(只读):
- CPU 发出虚拟地址,操作类型为“写”;
- MMU 查页表,发现该页的写权限位为 0;
- MMU 触发保护异常(Protection Fault);
- 操作系统捕获异常,向进程发送 SIGSEGV 信号;
- 进程被终止,或执行信号处理函数。
// 代码段通常标记为只读
const char *str = "Hello";
str[0] = 'h'; // 触发段错误
5.4 用户/内核位的重要性
用户/内核位是内存保护的关键。它确保:
- 用户态程序不能访问内核内存;
- 即使用户态程序知道内核的虚拟地址,也无法访问;
- 内核内存只能在内核态访问。
内核内存通常映射在进程地址空间的高地址部分,但标记为“仅内核态可访问”。用户态程序访问这些地址时,MMU 触发保护异常。

图 7:用户空间与内核空间的权限隔离。
六、段表权限位
6.1 段表项的权限位
在分段机制中,段表项也包含权限位:
| 权限位 | 说明 |
|---|---|
| 读(R) | 允许读取该段 |
| 写(W) | 允许写入该段 |
| 执行(X) | 允许执行该段 |
| 特权级(DPL) | 该段的特权级要求 |
| 存在位(P) | 该段是否在内存中 |
6.2 段长检查
分段机制还有一个独特的保护机制:段长检查。
每个段有一个段长。当进程访问段内地址时,MMU 检查偏移是否小于段长。如果偏移 ≥ 段长,触发越界异常。
图 8:分段机制中的段长检查。
6.3 段页式中的双重保护
段页式结合了分段和分页,提供双重保护:
- 段级检查:偏移是否小于段长?权限是否匹配?
- 页级检查:页的有效位、权限位是否匹配?
双重保护更加安全,但也增加了地址转换的复杂度。
七、特权级
7.1 特权级的概念
特权级(Privilege Level)是 CPU 提供的硬件机制,把运行状态分为多个等级:
| 特权级 | 名称 | 说明 |
|---|---|---|
| Ring 0 | 内核态 | 最高权限,可执行所有指令 |
| Ring 1 | 保留 | 很少使用 |
| Ring 2 | 保留 | 很少使用 |
| Ring 3 | 用户态 | 最低权限,不能执行特权指令 |
7.2 特权级与内存保护
特权级确保:
- 用户态程序不能执行特权指令(如修改页表基址);
- 用户态程序不能访问内核内存;
- 只有内核态代码可以修改页表、切换进程等。

图 9:特权级与内存保护。
7.3 特权级切换
当用户态程序需要访问硬件或内核服务时,通过系统调用切换到内核态。内核态代码执行完毕后,通过返回指令切换回用户态。
在切换过程中,CPU 会:
- 保存用户态上下文;
- 切换到内核栈;
- 提升特权级到 Ring 0;
- 执行内核代码;
- 恢复用户态上下文;
- 降低特权级到 Ring 3。
八、越界访问的检测
8.1 什么是越界访问?
越界访问(Out-of-Bounds Access)是指程序访问了超出其合法内存范围的地址。
越界访问包括:
| 类型 | 说明 |
|---|---|
| 数组越界 | 访问数组索引超出范围 |
| 缓冲区溢出 | 写入超出缓冲区大小的数据 |
| 空指针解引用 | 访问地址 0 |
| 野指针 | 访问已释放的内存 |
| 栈溢出 | 递归太深,栈空间耗尽 |
8.2 越界访问的检测机制
操作系统和硬件通过多种机制检测越界访问:
| 机制 | 说明 |
|---|---|
| 页表权限检查 | 访问权限不匹配时触发异常 |
| 段长检查 | 偏移超出段长时触发异常 |
| 地址空间检查 | 访问未映射地址时触发缺页异常 |
| 栈保护 | 检测栈溢出 |
| 地址对齐检查 | 未对齐访问触发异常 |
图 10:越界访问的检测机制。
8.3 一个具体的例子
假设进程访问虚拟地址 0x00000000(空指针):
- CPU 发出虚拟地址
0x00000000; - MMU 查页表,发现该页无效(有效位为 0);
- MMU 触发缺页中断;
- 操作系统检查地址合法性,发现该地址未映射;
- 操作系统向进程发送 SIGSEGV 信号;
- 进程被终止。
int *p = NULL;
*p = 42; // 触发段错误
8.4 栈溢出的检测
栈溢出是常见的越界访问类型。操作系统通常使用保护页(Guard Page)检测栈溢出:
- 在栈的边界处设置一个不可访问的页;
- 如果栈增长超过边界,访问保护页,触发异常;
- 操作系统捕获异常,终止进程或扩展栈。

图 11:栈保护页检测栈溢出。
8.5 缓冲区溢出的检测
缓冲区溢出是另一种常见的越界访问。操作系统和编译器通过多种机制检测:
| 机制 | 说明 |
|---|---|
| 栈金丝雀 | 在栈帧中插入特殊值,函数返回前检查 |
| 地址空间布局随机化(ASLR) | 随机化内存布局,增加攻击难度 |
| 数据执行保护(DEP) | 数据段不可执行 |
| 边界检查 | 编译器插入边界检查代码 |
九、内存保护的硬件支持
9.1 MMU
内存管理单元(MMU)是内存保护的核心硬件。它负责:
- 地址转换:虚拟地址 → 物理地址;
- 权限检查:检查访问是否合法;
- 触发异常:非法访问时触发异常。
9.2 TLB
快表(TLB)缓存最近的地址转换结果。TLB 中也会缓存权限位,加速权限检查。
当页表修改时,操作系统需要刷新 TLB,确保权限检查使用最新的页表。
9.3 特权级寄存器
CPU 提供特权级寄存器,记录当前特权级。MMU 根据特权级和页表权限位决定是否允许访问。
9.4 地址对齐检查
某些架构要求内存访问必须对齐(如 4 字节访问必须 4 字节对齐)。未对齐访问可能触发异常。

图 12:内存保护的硬件支持。
十、内存保护的软件支持
10.1 操作系统的职责
操作系统负责:
- 建立页表/段表,设置权限位;
- 处理保护异常,终止违规进程或发送信号;
- 切换进程时切换页表;
- 管理内核内存和用户内存的隔离;
- 实现 ASLR、DEP 等安全机制。
10.2 信号处理
当保护异常发生时,操作系统向进程发送信号:
| 信号 | 说明 |
|---|---|
| SIGSEGV | 段错误,非法内存访问 |
| SIGBUS | 总线错误,地址对齐错误 |
| SIGILL | 非法指令 |
进程可以注册信号处理函数,但通常这些信号会导致进程终止。
10.3 内存保护与 ASLR
地址空间布局随机化(ASLR)是操作系统提供的一种安全机制,随机化进程内存布局,使攻击者难以预测关键数据的地址。
ASLR 依赖内存保护机制,确保攻击者不能通过固定地址访问目标数据。
10.4 内存保护与 DEP
数据执行保护(DEP)把数据段标记为不可执行,防止攻击者在数据段注入代码并执行。
DEP 通过页表项的“执行”权限位实现。
图 13:内存保护的软件支持。
十一、实际应用
11.1 Linux 中的内存保护
Linux 使用分页机制实现内存保护:
- 每个进程有独立的页表;
- 页表项包含权限位;
- 用户态和内核态分离;
- 支持 ASLR、DEP、栈保护。
# 查看进程的内存映射
cat /proc/PID/maps
输出中可以看到每个内存区域的权限(rwx):
00400000-00401000 r-xp # 代码段,可读可执行
00600000-00601000 rw-p # 数据段,可读可写
7fff0000-7fff1000 rw-p # 栈,可读可写
11.2 Windows 中的内存保护
Windows 使用类似机制:
- 页表权限位;
- 用户态/内核态分离;
- DEP(数据执行保护);
- ASLR(地址空间布局随机化)。
11.3 国产操作系统中的内存保护
国产操作系统(如银河麒麟、统信 UOS)基于 Linux 内核,内存保护机制与 Linux 一致。此外,它们还支持:
- 强制访问控制(MAC);
- 可信计算;
- 安全审计。
图 14:内存保护的实际应用。
十二、常见误区澄清
| 误区 | 真相 |
|---|---|
| “内存保护是软件实现的” | 核心是硬件(MMU)实现,软件负责管理 |
| “内存保护能防止所有越界访问” | 只能检测跨页/跨段越界,页内越界无法检测 |
| “内存保护会降低性能” | 现代硬件加速,性能影响很小 |
| “内存保护只用于安全” | 也用于稳定性和错误隔离 |
| “64 位系统不需要内存保护” | 任何系统都需要内存保护 |
| “ASLR 是内存保护的全部” | ASLR 是补充机制,不是全部 |
| “内存保护能防止缓冲区溢出” | 只能限制影响,不能防止程序内部溢出 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)