Linux程序内存布局分析
Linux程序内存布局分析
一、完整程序内存布局详解(Linux x86-64)
高地址 0x7FFFFFFFFFFFFF
+----------------------+ <-- 内核空间边界
| **内核空间** | (操作系统内核代码/数据)
+----------------------+ <-- 0x7FFF80000000
| **栈 (Stack)** | ↓ 向下增长
| - 函数栈帧 |
| - 局部变量 |
| - 返回地址 | <-- RSP (栈指针寄存器)
+----------------------+
| **共享库映射区** | (动态链接库加载位置)
| - libc.so |
| - ld-linux-x86-64 |
+----------------------+
| **内存映射段** | (mmap分配区域)
| - 文件映射 |
| - 匿名映射 |
+----------------------+
| ↓ |
| **堆 (Heap)** | ↑ 向上增长
| - malloc/new分配区 | <-- brk/sbrk边界
+----------------------+ <-- 程序断点 (brk)
| **BSS 段** | (未初始化全局数据)
| - 未初始化全局变量 |
| - 初始化为0的变量 |
+----------------------+
| **DATA 段** | (已初始化全局数据)
| - 初始化全局变量 |
| - 静态变量 |
| - 常量字符串 |
+----------------------+
| **RODATA 段** | (只读数据)
| - 字符串常量 |
| - const全局常量 |
+----------------------+
| **TEXT 段** | (代码段)
| - 机器指令 |
| - 函数代码 | <-- RIP (指令指针)
+----------------------+
| **保留区** | (0x00000000-0x400000)
低地址 0x00000000000000
二、关键内存区域深度解析
1. 代码段 (TEXT)
- 内容:编译后的机器指令
- 权限:
r-x
(可读/执行,不可写) - 特点:
- 所有进程共享同一物理副本(写时复制)
- 包含函数入口点(
_start
,main
)
- 实例:
$ readelf -S a.out | grep .text [13] .text PROGBITS 0000000000401040 001040...
2. 数据段 (DATA/BSS)
段名 | 存储内容 | ELF节名 | 初始化方式 |
---|---|---|---|
DATA | 显式初始化的全局/静态变量 | .data | 程序加载时从文件读取 |
BSS | 未初始化的全局/静态变量 | .bss | 内核初始化为零 |
RODATA | 只读常量 | .rodata | 程序加载时初始化 |
- 实例代码:
int data_var = 42; // DATA段 const char *rodata = "ABC"; // RODATA段 (.rodata) static int bss_var; // BSS段
3. 堆 (Heap)
- 管理机制:
+-------------------+ | glibc的ptmalloc2 | ← 用户态内存分配器 +-------------------+ | brk()系统调用 | ← 调整program break位置 +-------------------+ | 内核的vm_area_struct | ← 管理虚拟内存区域 +-------------------+
- 分配流程:
malloc(128)
检查线程本地缓存 (tcache)- 未命中 → 检查fast bins/free lists
- 仍未命中 → 通过
brk()
或mmap()
扩展堆
4. 栈 (Stack)
- 栈帧结构详解:
高地址 +-------------------+ | 参数n | | ... | | 参数1 | +-------------------+ | 返回地址 | ← 调用结束后返回位置 +-------------------+ | 保存的EBP | ← 当前栈帧基址 +-------------------+ | 局部变量1 | | 局部变量2 | | ... | +-------------------+ | 对齐填充 | ← 栈对齐要求(16字节) +-------------------+ ← 当前栈顶 (RSP) 低地址
- 寄存器:
- RSP:栈顶指针寄存器
- RBP:栈基址寄存器(可选使用)
- RIP:下条指令地址
三、高级内存区域
1. 内存映射段 (Memory Mapping Segment)
- 分配方式:
mmap()
系统调用 - 使用场景:
// 文件映射 void *file_map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);// 匿名映射 (大内存分配) void *big_mem = mmap(NULL, 1<<30, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
- 优势:独立于堆,直接由内核管理,适合大块内存
2. 线程栈
- 特点:
- 每个线程有独立栈(默认2-10MB)
- 通过
pthread_create()
创建 - 位于内存映射段
+---------------------+ | 主线程栈 (8MB) | +---------------------+ | 线程1栈 (2MB) | +---------------------+ | 线程2栈 (2MB) | +---------------------+ | ... |
3. 内核空间
- 组成:
+---------------------+ | 直接映射区 | ← 物理内存1:1映射 +---------------------+ | vmalloc区 | ← 不连续物理页映射 +---------------------+ | 持久内核映射区 | ← 高端内存映射 +---------------------+ | 固定映射区 | +---------------------+
四、实例分析:完整内存快照
C程序示例
#include <stdlib.h>
#include <stdio.h>int global_init = 10; // DATA段
char *str = "Hello"; // RODATA段 (指针在DATA)
int global_uninit; // BSS段void func(int param) { // TEXT段int local = 20; // 栈static int static_local = 30; // DATA段int *heap_var = malloc(sizeof(int)); // 堆printf("%s\n", str); // 调用库函数 (共享库映射区)
}int main() {func(42);return 0;
}
运行时内存布局图示
0x7ffff7ffb000 +----------------------+| 主线程栈 || - main栈帧 || - func栈帧 || param=42 || local=20 |
0x7ffff7de0000 +----------------------+ <-- 共享库映射区| libc.so代码/数据 || printf实现代码 |
0x7ffff7a00000 +----------------------+ <-- 堆顶 (brk)| malloc分配的heap_var |+----------------------+| ... (堆空间) |
0x55555555a000 +----------------------+ <-- BSS段| global_uninit (初始0) |
0x555555559000 +----------------------+ <-- DATA段| global_init=10 || str (指向RODATA地址) || static_local=30 |
0x555555558000 +----------------------+ <-- RODATA段| "Hello" 字符串常量 |
0x555555554000 +----------------------+ <-- TEXT段| func机器码 || main机器码 |
0x400000 +----------------------+
五、内存管理关键数字
参数 | 典型值 | 查看命令 |
---|---|---|
栈默认大小 | 8MB (Linux) | ulimit -s |
页大小 | 4KB | getconf PAGE_SIZE |
堆分配阈值 | 128KB (glibc) | mallopt(M_MMAP_THRESHOLD) |
TEXT/DATA最大偏移 | 2GB (32位系统) | 编译链接参数控制 |
ASLR随机化范围 | ±28位 (Linux) | /proc/sys/kernel/randomize_va_space |
六、高级话题:内存映射底层原理
虚拟内存到物理内存映射
虚拟地址空间 页表 物理内存
+---------------+ +-----------+ +---------------+
| 0x400000 | → | PTE | → | 代码页1 |
| (TEXT) | +-----------+ +---------------+
+---------------+ +-----------+ +---------------+
| 0x600000 | → | PTE | → | 数据页 |
| (DATA) | +-----------+ +---------------+
+---------------+ +-----------+ +---------------+
| 0x7fff0000 | → | PTE | → | 栈页 |
| (Stack) | +-----------+ +---------------+
+---------------+ +-----------+ +---------------+
| 0x100000000 | → | PTE | → | 堆页 |
| (Heap) | +-----------+ +---------------+
缺页中断处理流程
- CPU访问未映射的虚拟地址
- 触发缺页中断(Page Fault)
- 内核检查访问合法性
- 分配物理页/加载文件内容
- 更新页表项
- 返回用户态重新执行指令
七、诊断工具
-
Linux 进程映射:
cat /proc/$PID/maps
输出示例:
00400000-00401000 r-xp 00000000 08:01 /app # TEXT 00600000-00601000 r--p 00000000 08:01 /app # DATA 00601000-00602000 rw-p 00001000 08:01 /app # BSS 7ffff7a00000-7ffff7bc0000 rw-p 00000000 00:00 0 # Heap 7ffff7bd0000-7ffff7bd1000 rw-p 00000000 00:00 0 # 匿名映射 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 # Stack
-
Windows 工具:
- VMMap (SysInternals)
- WinDbg
!address
命令