当前位置: 首页 > java >正文

【Note】《深入理解Linux内核》Chapter 9 :深入理解 Linux 内核中的进程地址空间管理机制

《深入理解Linux内核》Chapter 9 :深入理解 Linux 内核中的进程地址空间管理机制

摘要:虚拟内存、内存映射、VMA、mm_struct、进程隔离、brk、mmap、栈、堆、页表、多线程共享、地址空间布局


一、地址空间的重要性

地址空间(Address Space)是操作系统为每个进程构建的内存抽象。Linux 使用虚拟内存技术,使得:

  • 每个进程拥有独立的虚拟地址空间;
  • 用户空间与内核空间相互隔离;
  • 支持按需分页、写时复制、共享内存等机制;
  • 实现系统安全性与进程稳定性。

二、虚拟地址空间结构概览

在 Linux 中,每个进程拥有完整的虚拟地址空间,通常结构如下(以 32 位系统为例):

0x00000000|| 用户空间|| text段(代码段)| data段(已初始化静态数据)| bss段(未初始化全局数据)| heap(堆)—— 从低地址向上增长(brk)| mmap区域—— 映射文件/共享内存/动态库| 栈(stack)—— 从高地址向下增长|
0xC0000000|| 内核空间(共享)|
0xFFFFFFFF

64 位架构使用更大的地址空间,并对地址段进行了更灵活的划分。


三、核心数据结构:mm_struct 与 VMA

3.1 mm_struct

每个进程地址空间由一个 mm_struct 表示,结构定义如下:

struct mm_struct {pgd_t *pgd;                          // 页目录基地址struct vm_area_struct *mmap;        // VMA 链表struct rb_root mm_rb;               // VMA 红黑树索引unsigned long start_code, end_code;unsigned long start_brk, brk;unsigned long start_stack;...
};

其中 pgd 指向页目录表,mmap/mm_rb 负责管理所有虚拟内存区域。

3.2 vm_area_struct(VMA)

每个内存区域由一个 vm_area_struct 表示:

struct vm_area_struct {unsigned long vm_start;unsigned long vm_end;unsigned long vm_flags;struct file *vm_file;struct vm_area_struct *vm_next;struct mm_struct *vm_mm;...
};
  • vm_start/vm_end:区域范围;
  • vm_flags:属性(只读/可写/可执行等);
  • vm_file:若该区域为文件映射,则指向文件结构;
  • 所有 VMA 通过链表与红黑树组织,便于快速查找。

四、地址空间的组成与分配方式

4.1 代码段(.text)

  • 存储可执行指令;
  • 映射自 ELF 可执行文件;
  • 通常为只读 + 可执行(VM_READ | VM_EXEC);
  • VMA 来源:load_elf_binary() 中的 do_mmap()

4.2 数据段(.data / .bss)

  • 包含初始化与未初始化的全局变量;
  • 映射于执行文件的特定段,或通过 brk() 分配;
  • BSS 初始化为 0,占用匿名内存。

4.3 堆区(heap)

  • 通过 brk()sbrk() 系统调用增长;
  • 默认由 glibc 封装为 malloc() 等接口;
  • 从低地址向高地址扩展;
  • start_brkbrk 限定。
unsigned long brk = current->mm->brk;

4.4 栈区(stack)

  • 每个线程分配一个私有用户栈;
  • 从高地址向低地址增长;
  • start_stack 标示栈底地址;
  • 受限于 ulimit -s 设置;
  • 页表保护栈底防止溢出。

4.5 mmap 区域

  • 映射文件、动态链接库、匿名内存等;
  • 使用 mmap() 系统调用映射;
  • 起始地址通常从某个随机高地址向下分配;
  • 是现代内存分配(如 malloc())的主力方式。

五、地址空间创建流程

5.1 execve():创建新地址空间

当调用 execve() 执行新程序时:

  1. 内核释放当前 mm_struct;
  2. 调用 load_elf_binary()
  3. 分配新 mm_struct
  4. 使用 do_mmap() 映射 text/data/bss 段;
  5. 初始化堆、栈区域;
  6. 设置页表,切换进程上下文。

5.2 fork():共享地址空间

  • fork() 创建子进程时,会复制父进程地址空间;
  • 页表不复制,而是使用写时复制(COW);
  • 子进程拥有新 mm_struct 与 VMA 链表;
  • 页框共享,设置只读位标志;
  • 当子进程尝试写时,触发缺页异常并复制页。

六、多线程共享与 mm_struct

6.1 多线程如何共享地址空间?

  • 所有线程共享同一个 mm_struct
  • CLONE_VM 标志确保地址空间共享;
  • 栈空间由每个线程单独分配。

6.2 线程栈分配

  • 用户空间栈由 glibc 分配;
  • 内核通过 do_fork() 设置新线程栈地址;
  • 默认栈大小可通过 pthread_attr 设置。

七、内存映射(mmap)机制详解

7.1 mmap 系统调用

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
  • 映射文件或匿名页;
  • 创建新的 VMA;
  • mmap_region() 完成实际映射,更新页表;

7.2 使用场景

  • 动态库加载;
  • 映射大型文件;
  • 创建共享内存;
  • malloc() 大块分配。

7.3 匿名映射(anonymous mmap)

mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
  • 分配未关联文件的内存;
  • 相当于手动实现 malloc
  • 常用于线程栈、堆、大数据结构。

八、地址空间保护与随机化

8.1 访问控制标志(vm_flags)

标志含义
VM_READ可读
VM_WRITE可写
VM_EXEC可执行
VM_MAYSHARE可共享映射
VM_GROWSDOWN栈可向下增长

8.2 ASLR(地址空间布局随机化)

  • 提高安全性,防止缓冲区溢出攻击;
  • text、heap、stack、mmap 等区域随机偏移;
  • 内核通过 /proc/sys/kernel/randomize_va_space 控制;

九、页表与地址空间的连接

9.1 每个进程的页表基地址

current->mm->pgd
  • 指向该进程页目录起始地址;
  • vm_area_struct 共同组织地址空间;
  • 每次上下文切换时更新 CR3(x86)寄存器;

9.2 页表更新与映射

  • mmap() 映射区域仅创建 VMA;
  • 首次访问触发缺页异常,由 handle_mm_fault() 创建页表项;
  • do_page_fault() 处理实际映射并分配页框。

十、地址空间的释放与销毁

10.1 进程退出时

  • 通过 exit_mm() 释放 mm_struct
  • 清除页表项,释放页框;
  • 销毁所有 VMA 结构;
  • 若有共享页,减少引用计数。

10.2 映射销毁

munmap(addr, length);
  • 调用 do_munmap() 删除对应 VMA;
  • 卸载页表项;
  • 若映射文件,写回脏页。

十一、调试工具与接口

接口/工具用途
/proc/<pid>/maps显示进程地址空间各段信息
/proc/<pid>/smaps内存占用细节(RSS、PSS)
/proc/<pid>/pagemap虚拟地址与物理地址映射
gdb + info proc mappings调试进程内存映射
strace ./a.out捕捉 mmap/brk/munmap 系统调用

十二、源码文件与核心函数

源码路径说明
mm/mmap.c实现 mmap、munmap、brk 逻辑
mm/memory.c缺页异常与页面映射
fs/exec.c加载 ELF,创建初始地址空间
kernel/fork.cfork、clone、线程创建
include/linux/mm_types.hmm_structvm_area_struct 定义

十三、实验与实践建议

  1. 写程序使用 mmap() 分配内存,并查看 /proc/<pid>/maps
  2. 写一个 fork() 后修改堆内容的程序,观察 COW 效果;
  3. 使用 strace 观察程序使用 brk()mmap()
  4. 利用 gdb 调试栈地址,验证其向下增长;
  5. 多线程程序中手动设定每个线程栈地址,研究其映射变化;
  6. 阅读 ELF 执行文件头部结构,理解各段加载关系。

十四、总结对比表

区域分配方式分配函数成长方向描述
text段ELF 映射load_elf_binary固定可执行指令,只读可执行
data段ELF 映射load_elf_binary固定已初始化全局变量
bss段brk/mmapbrk固定未初始化全局变量
heapbrk/mmapmalloc/brk向上动态分配内存区
stack线程创建时设定clone/pthread_create向下每线程独立栈
mmap区域映射文件/匿名内存mmap动态灵活共享/文件映射
http://www.xdnf.cn/news/14791.html

相关文章:

  • FASTAPI+VUE3平价商贸管理系统
  • MySQL数据库----DML语句
  • 论文阅读笔记——Autoregressive Image Generation without Vector Quantization
  • uniapp打包微信小程序主包过大问题_uniapp 微信小程序时主包太大和vendor.js过大
  • 深度学习-逻辑回归
  • 深入理解 Redis Cluster:分片、主从与脑裂
  • Gemini CLI初体验
  • MySQL 8.0 OCP 1Z0-908 题目解析(17)
  • SciPy 安装使用教程
  • 数据结构:数组在编译器中的表示(Array Representation by Compiler)
  • NumPy-核心函数transpose()深度解析
  • MediaCrawler:强大的自媒体平台爬虫工具
  • 【python】OOP:Object-Oriented Programming
  • DHCP中继及动态分配
  • 全双工和半双工在以太网报文收发过程中的核心区别
  • 读书笔记:《DevOps实践指南》
  • GitHub 解码指南:用 AI 赋能,五步快速掌握任意开源项目
  • IOC容器讲解以及Spring依赖注入最佳实践全解析
  • LeetCode--40.组合总和II
  • Android App冷启动流程详解
  • 基于 Elasticsearch 实现地图点聚合
  • R语言初学者爬虫简单模板
  • 多种方法实现golang中实现对http的响应内容生成图片
  • Ubuntu20.04运DS-5
  • Lua 安装使用教程
  • docker-compose快速搭建redis集群
  • 容器基础5-Helm 与 K8s 的关系
  • 配置tcp的https协议证书
  • (第三篇)HMTL+CSS+JS-新手小白循序渐进案例入门
  • 【字节跳动】数据挖掘面试题0003:有一个文件,每一行是一个数字,如何用 MapReduce 进行排序和求每个用户每个页面停留时间