2. 进程地址空间概念:进程地址空间布局、用户空间与内核空间划分、地址空间随机化(ASLR)

进程地址空间是操作系统为每个进程提供的虚拟内存视图。它屏蔽了物理内存的细节,为每个进程提供了一个独立、连续且安全的虚拟内存范围。理解进程地址空间的布局、用户态与内核态的划分以及地址空间随机化(ASLR)机制,是掌握 Linux 内存管理的基石。

2.1 进程地址空间布局

在 64 位 Linux 系统(如 x86_64)上,尽管虚拟地址空间理论上可达 2^64 字节,但实际实现中通常只使用低 47 位(或 48 位),并遵循经典的布局结构。一个典型的进程地址空间从低地址到高地址包含以下几个主要区域:

核心要点: 地址空间布局是“自底向上”增长的,但栈是“自顶向下”增长的。堆和内存映射区域之间存在一个巨大的“空洞”,用于动态分配。
Linux 进程地址空间典型布局 (x86_64) Linux 进程地址空间典型布局 (x86_64) 内核空间 (Kernel Space) 0xFFFF8000 00000000 - 0xFFFFFFFF FFFFFFFF 用户/内核边界 栈 (Stack) 局部变量、函数调用帧 (向下增长) 内存映射区域 (Memory Mapping Segment) 文件映射、动态库、mmap 匿名映射 堆 (Heap) 动态内存分配 (malloc/new) (向上增长) BSS 段 (未初始化全局/静态变量) 数据段 (已初始化全局/静态变量) 文本段 (Text / Code Segment)

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 效果对比:固定地址 vs 随机化地址 ASLR 效果对比:固定地址 vs 随机化地址 ASLR 关闭 (randomize_va_space = 0) 栈基址: 0x7ffffffde000 堆基址: 0x601000 libc 基址: 0x7ffff7a0d000 ASLR 开启 (randomize_va_space = 2) 栈基址: 0x7ffc8b4f2000 堆基址: 0x55a3f6c09000 libc 基址: 0x7f1a2c3d4000
安全提示: ASLR 是纵深防御体系的一部分。它不能单独防御所有攻击,但与 NX(No-Execute)位、栈保护(Stack Canary)等技术结合使用时,能显著提高系统的安全性。