这是“操作系统系列教程”的第三十八篇。前面几篇我们讲了虚拟内存、分页、分段、页表、缺页中断、页面置换、内存碎片、交换空间、内存映射和内存分配算法。这些机制解决的是“内存怎么用”的问题。但还有一个同样重要的问题:内存怎么保护?如果一个程序试图访问不属于它的内存,操作系统如何发现并阻止?这就是——内存保护要解决的问题。

一个“越界”的严重后果

先来看一段简单的 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)是操作系统通过硬件和软件机制,限制进程只能访问其合法内存区域,防止进程读写其他进程或操作系统的内存,从而保证系统的安全性、稳定性和隔离性。

这个定义里有五个关键点:

  1. 硬件和软件机制:需要 MMU、页表、特权级等硬件支持,也需要操作系统管理;
  2. 限制访问:进程只能访问自己的内存;
  3. 防止越界:不能读写其他进程或内核的内存;
  4. 安全性:防止恶意程序窃取或破坏数据;
  5. 稳定性:防止一个程序的错误影响其他程序。

1.3 内存保护在系统中的位置

MMU

内核

进程B

进程A

允许

拒绝

允许

仅内核态允许

触发异常

合法内存区域

越界访问

合法内存区域

内核内存

地址转换 + 权限检查

操作系统处理

图 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)该页是否被修改过

页表项

物理页框号

读 R

写 W

执行 X

用户/内核 U/S

有效 V

访问 A

修改 D

图 5:页表项的权限位。 用户/内核位决定该页是否允许用户态访问。

5.2 权限检查的过程

每次内存访问,MMU 都会检查:

  1. 有效位:该页是否在内存中?如果不在,触发缺页中断;
  2. 用户/内核位:当前是用户态还是内核态?如果是用户态,且该页标记为“仅内核”,触发保护异常;
  3. 读/写/执行位:当前操作是什么?如果操作与权限不匹配,触发保护异常。
页表MMUCPU页表MMUCPUalt[操作与权限不匹配][权限匹配]alt[用户态访问内核页][权限匹配]alt[有效位为 0][有效位为 1]发出虚拟地址 + 操作类型查询页表项返回权限位检查有效位触发缺页中断检查用户/内核位触发保护异常检查读/写/执行位触发保护异常返回物理地址

图 6:页表权限检查的完整流程。

5.3 一个具体的例子

假设一个进程试图写入代码段(只读):

  1. CPU 发出虚拟地址,操作类型为“写”;
  2. MMU 查页表,发现该页的写权限位为 0;
  3. MMU 触发保护异常(Protection Fault);
  4. 操作系统捕获异常,向进程发送 SIGSEGV 信号;
  5. 进程被终止,或执行信号处理函数。
// 代码段通常标记为只读
const char *str = "Hello";
str[0] = 'h';  // 触发段错误

5.4 用户/内核位的重要性

用户/内核位是内存保护的关键。它确保:

  • 用户态程序不能访问内核内存;
  • 即使用户态程序知道内核的虚拟地址,也无法访问;
  • 内核内存只能在内核态访问。

内核内存通常映射在进程地址空间的高地址部分,但标记为“仅内核态可访问”。用户态程序访问这些地址时,MMU 触发保护异常。

在这里插入图片描述

图 7:用户空间与内核空间的权限隔离。

六、段表权限位

6.1 段表项的权限位

在分段机制中,段表项也包含权限位:

权限位说明
读(R)允许读取该段
写(W)允许写入该段
执行(X)允许执行该段
特权级(DPL)该段的特权级要求
存在位(P)该段是否在内存中

6.2 段长检查

分段机制还有一个独特的保护机制:段长检查。

每个段有一个段长。当进程访问段内地址时,MMU 检查偏移是否小于段长。如果偏移 ≥ 段长,触发越界异常。

段表MMUCPU段表MMUCPUalt[偏移合法][偏移越界]发出虚拟地址(段号+偏移)查询段表项返回段基址和段长检查偏移 < 段长返回物理地址触发越界异常

图 8:分段机制中的段长检查。

6.3 段页式中的双重保护

段页式结合了分段和分页,提供双重保护:

  1. 段级检查:偏移是否小于段长?权限是否匹配?
  2. 页级检查:页的有效位、权限位是否匹配?

双重保护更加安全,但也增加了地址转换的复杂度。

七、特权级

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(空指针):

  1. CPU 发出虚拟地址 0x00000000;
  2. MMU 查页表,发现该页无效(有效位为 0);
  3. MMU 触发缺页中断;
  4. 操作系统检查地址合法性,发现该地址未映射;
  5. 操作系统向进程发送 SIGSEGV 信号;
  6. 进程被终止。
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 通过页表项的“执行”权限位实现。

内存保护软件支持

操作系统管理页表

处理保护异常

信号处理

ASLR

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);
  • 可信计算;
  • 安全审计。

内存保护实际应用

Linux: 分页 + 权限位

Windows: DEP + ASLR

国产 OS: MAC + 可信计算

图 14:内存保护的实际应用。

十二、常见误区澄清

误区真相
“内存保护是软件实现的”核心是硬件(MMU)实现,软件负责管理
“内存保护能防止所有越界访问”只能检测跨页/跨段越界,页内越界无法检测
“内存保护会降低性能”现代硬件加速,性能影响很小
“内存保护只用于安全”也用于稳定性和错误隔离
“64 位系统不需要内存保护”任何系统都需要内存保护
“ASLR 是内存保护的全部”ASLR 是补充机制,不是全部
“内存保护能防止缓冲区溢出”只能限制影响,不能防止程序内部溢出
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐