Linux程序内存布局分析

发布于:2025-08-18 ⋅ 阅读:(12) ⋅ 点赞:(0)

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 | ← 管理虚拟内存区域
    +-------------------+
    
  • 分配流程
    1. malloc(128) 检查线程本地缓存 (tcache)
    2. 未命中 → 检查fast bins/free lists
    3. 仍未命中 → 通过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)        |   +-----------+   +---------------+
缺页中断处理流程
  1. CPU访问未映射的虚拟地址
  2. 触发缺页中断(Page Fault)
  3. 内核检查访问合法性
  4. 分配物理页/加载文件内容
  5. 更新页表项
  6. 返回用户态重新执行指令

七、诊断工具

  1. 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
    
  2. Windows 工具

    • VMMap (SysInternals)
    • WinDbg !address 命令

网站公告

今日签到

点亮在社区的每一天
去签到