2. 进程地址空间概念:进程地址空间布局、用户空间与内核空间划分、地址空间随机化(ASLR)
进程地址空间是操作系统为每个进程提供的虚拟内存视图。它屏蔽了物理内存的细节,为每个进程提供了一个独立、连续且安全的虚拟内存范围。理解进程地址空间的布局、用户态与内核态的划分以及地址空间随机化(ASLR)机制,是掌握 Linux 内存管理的基石。
2.1 进程地址空间布局
在 64 位 Linux 系统(如 x86_64)上,尽管虚拟地址空间理论上可达 2^64 字节,但实际实现中通常只使用低 47 位(或 48 位),并遵循经典的布局结构。一个典型的进程地址空间从低地址到高地址包含以下几个主要区域:
核心要点: 地址空间布局是“自底向上”增长的,但栈是“自顶向下”增长的。堆和内存映射区域之间存在一个巨大的“空洞”,用于动态分配。
2.1.1 各段详细说明
| 段 (Segment) | 存储内容 | 增长方向 | 权限 |
|---|---|---|---|
| 文本段 (Text) | 可执行指令、只读常量 | 固定 | 读 + 执行 |
| 数据段 (Data) | 已初始化的全局变量和静态变量 | 固定 | 读 + 写 |
| BSS 段 | 未初始化的全局变量和静态变量(运行时清零) | 固定 | 读 + 写 |
| 堆 (Heap) | 动态分配的内存(malloc、new) |
向上(高地址) | 读 + 写 |
| 内存映射区域 | 共享库、文件映射、匿名映射 | 可变 | 取决于映射 |
| 栈 (Stack) | 局部变量、函数参数、返回地址 | 向下(低地址) | 读 + 写 |
实践观察: 使用
cat /proc/[pid]/maps 可以查看任意进程的详细地址空间布局。例如,cat /proc/self/maps 可以查看当前 cat 命令自身的地址空间。
2.2 用户空间与内核空间划分
为了保障系统安全与稳定,Linux 将虚拟地址空间划分为两个部分:用户空间 和 内核空间。这种划分是通过硬件(MMU)和操作系统协同实现的。
- 用户空间 (User Space):进程在用户态(Ring 3)下运行时可以访问的地址范围。每个进程拥有独立的用户空间,进程间默认无法互相访问。
- 内核空间 (Kernel Space):操作系统内核在核心态(Ring 0)下运行时使用的地址范围。内核空间是全局的,所有进程共享同一个内核空间映射。
在 x86_64 架构上,典型的划分比例为:
| 空间 | 虚拟地址范围 | 大小 | 特权级 |
|---|---|---|---|
| 用户空间 | 0x00000000 00000000 ~ 0x00007FFF FFFFFFFF |
128 TB | Ring 3 (用户态) |
| 内核空间 | 0xFFFF8000 00000000 ~ 0xFFFFFFFF FFFFFFFF |
128 TB | Ring 0 (核心态) |
安全边界: 用户态程序无法直接访问内核空间地址。任何尝试访问内核空间的行为都会触发“段错误”(Segmentation Fault),从而防止用户程序破坏内核数据结构。
2.3 地址空间随机化 (ASLR)
地址空间随机化(Address Space Layout Randomization, ASLR)是一种安全技术,用于防止缓冲区溢出等内存破坏攻击。它通过随机化进程地址空间中关键区域(如栈、堆、共享库、mmap 区域)的基地址,使得攻击者难以预测目标内存地址。
2.3.1 ASLR 随机化的区域
- 栈基址:每次进程启动时,栈的起始地址都会随机偏移。
- 堆基址:堆的起始地址也会随机化。
- 共享库基址:动态链接库(如
libc.so)的加载地址随机化。 - mmap 区域基址:通过
mmap系统调用映射的区域。 - 可执行程序基址:PIE(Position Independent Executable)程序本身的加载地址也会随机化。
2.3.2 ASLR 的配置与查看
ASLR 的随机化程度由内核参数 /proc/sys/kernel/randomize_va_space 控制:
| 值 | 含义 | 说明 |
|---|---|---|
| 0 | 关闭 ASLR | 所有地址固定,不进行随机化。 |
| 1 | 部分随机化 | 随机化栈、mmap 区域、共享库。堆不随机化。 |
| 2 | 完全随机化 | 随机化栈、mmap 区域、共享库、堆。这是大多数 Linux 发行版的默认值。 |
查看当前 ASLR 状态:
cat /proc/sys/kernel/randomize_va_space
临时关闭 ASLR(需要 root 权限):
echo 0 > /proc/sys/kernel/randomize_va_space
2.3.3 ASLR 效果演示
以下代码片段展示了 ASLR 如何影响栈地址:
#include <stdio.h>
#include <stdlib.h>
int main() {
int stack_var;
printf("栈变量地址: %p\n", &stack_var);
return 0;
}
多次运行该程序,你会发现栈变量的地址每次都不同(在 ASLR 开启的情况下)。
安全提示: ASLR 是纵深防御体系的一部分。它不能单独防御所有攻击,但与 NX(No-Execute)位、栈保护(Stack Canary)等技术结合使用时,能显著提高系统的安全性。