APUE.03b::进程的虚拟内存管理

@todo: OneNote 笔记 copy 过来(内核如何使用 task_struct -> mm_struct 管理进程内存布局)

进程的内存布局(32位)

在32位 Linux 系统中,每个进程都有4GB 的虚拟地址空间,其中0-3GB 是用户空间,3-4GB 是内核空间。每个进程都以为自己独占整个4GB 的地址空间,但实际上1GB 的内核空间是所有进程共享的,独占的3GB 用户空间也只是虚拟的。

  
+---+--------------------+---------------------- 0xFFFF FFFF 高地址
1 GB | Kernel | 内核空间
+------------------------+---------------------- 0xC000 0000
| | Random stack offset| 用户空间 ↓
| +--------------------+
| | Stack↓ | 进程中的所有的线程共享相同的地址空间
| | |
| +--------------------+
| | |
| | |
| +--------------------+
| | Mem Mapping ↓ | 文件映射,动态链接库(.so文件)
| | | mmap()
| +--------------------+
+ | |
3 GB | |
+ +--------------------+
| | Heap↑ | 用户通过 malloc()分配的内存
| | |
| +--------------------+
| | Random stack offset|
| +--------------------+
| | BSS Seg. |
| +--------------------+
| | Data Seg. (+rodata)|
| +--------------------+
| | Text Seg. | 用户空间 ↑
+---+--------------------+---------------------- 0x00000000 低地址

内存布局从高地址到低地址:

  • Kernel: 内核空间在页表中拥有较高的特权级(ring 2或以下), 因此只要用户态的程序试图访问这些页, 就会导致一个页错误(page fault), 用户程序不可访问内核页

  • Stack: 自高地址向低地址增长, 每个进程都有一个自己的栈, 当不断压栈直到超过了最大的栈空间, 将会引起Stack Overflow, 进程中的每一个线程都有属于自己的栈

  • Memory Mapping Segment: mmap()实现”文件-内存映射”, 它被用于加载动态库, 大多数实际的malloc实现会考虑通过mmap分配较大块的内存区域, 这个区域自高地址向低地址增长
  • Heap: malloc()分配的内存空间, 如果堆中有足够的空间来满足内存请求, 它就可以被语言运行时库处理而不需要内核参与. 否则堆会被扩大, 通过brk()系统调用来分配请求所需的内存块, 堆自低地址向高地址增长
  • BSS Segment: 未赋初始值的static变量, 包括全局的static变量和函数内定义的static变量(全局变量默认就是static)
  • Data Segment: 有初始值的static变量, 程序bin映像的一部分
    • 还包括一个叫 rodata 的区域, 存储”字面量字符串”(包括全局/局部定义的字面量字符串), 以及”const 常量”
  • Text Segment: 这里存放的是二进制代码

C 代码中的 const、static 在 Data Segment 的存储细节,解释参考 GNU的obj分析工具的使用 - nm/objdump | 扔掉笔记 ᐛ


其中高地址的 1GB Kernel Space 结构如下(左边):
../_images/32bit-linux-memory-model.png

进程的内存布局(64位)

64位架构下内存布局与32位类似, 可寻址64TB(Intel架构下是46个地址线, 2^46)

64bit_linux_memory

How to 查看某个进程的内存分布

命令行:cat /proc/xxx/maps