一步一图带你深入理解 Linux 虚拟内存管理(11)

内核中用 mm_struct 结构体中的上述属性来定义上图中虚拟内存空间里的不同内存区域 。
start_code 和 end_code 定义代码段的起始和结束位置 , 程序编译后的二进制文件中的机器码被加载进内存之后就存放在这里 。
start_data 和 end_data 定义数据段的起始和结束位置 , 二进制文件中存放的全局变量和静态变量被加载进内存中就存放在这里 。
后面紧挨着的是 BSS 段 , 用于存放未被初始化的全局变量和静态变量 , 这些变量在加载进内存时会生成一段 0 填充的内存区域 (BSS 段) ,  BSS 段的大小是固定的 , 
下面就是 OS 堆了 , 在堆中内存地址的增长方向是由低地址向高地址增长 ,  start_brk 定义堆的起始位置 , brk 定义堆当前的结束位置 。

我们使用 malloc 申请小块内存时(低于 128K) , 就是通过改变 brk 位置调整堆大小实现的 。
接下来就是内存映射区 , 在内存映射区内存地址的增长方向是由高地址向低地址增长 , mmap_base 定义内存映射区的起始地址 。进程运行时所依赖的动态链接库中的代码段 , 数据段 , BSS 段以及我们调用 mmap 映射出来的一段虚拟内存空间就保存在这个区域 。
start_stack 是栈的起始位置在 RBP 寄存器中存储 , 栈的结束位置也就是栈顶指针 stack pointer 在 RSP 寄存器中存储 。在栈中内存地址的增长方向也是由高地址向低地址增长 。
arg_start 和 arg_end 是参数列表的位置 ,  env_start 和 env_end 是环境变量的位置 。它们都位于栈中的最高地址处 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
在 mm_struct 结构体中除了上述用于划分虚拟内存区域的变量之外 , 还定义了一些虚拟内存与物理内存映射内容相关的统计变量 , 操作系统会把物理内存划分成一页一页的区域来进行管理 , 所以物理内存到虚拟内存之间的映射也是按照页为单位进行的 。这部分内容笔者会在后续的文章中详细介绍 , 大家这里只需要有个概念就行 。
mm_struct 结构体中的 total_vm 表示在进程虚拟内存空间中总共与物理内存映射的页的总数 。
注意映射这个概念 , 它表示只是将虚拟内存与物理内存建立关联关系 , 并不代表真正的分配物理内存 。
当内存吃紧的时候 , 有些页可以换出到硬盘上 , 而有些页因为比较重要 , 不能换出 。locked_vm 就是被锁定不能换出的内存页总数 , pinned_vm表示既不能换出 , 也不能移动的内存页总数 。
data_vm 表示数据段中映射的内存页数目 , exec_vm 是代码段中存放可执行文件的内存页数目 , stack_vm 是栈中所映射的内存页数目 , 这些变量均是表示进程虚拟内存空间中的虚拟内存使用情况 。
现在关于内核如何对进程虚拟内存空间进行布局的内容我们已经清楚了 , 那么布局之后划分出的这些虚拟内存区域在内核中又是如何被管理的呢?我们接着往下看~~~
5.3 内核如何管理虚拟内存区域在上小节的介绍中 , 我们知道内核是通过一个 mm_struct 结构的内存描述符来表示进程的虚拟内存空间的 , 并通过 task_size 域来划分用户态虚拟内存空间和内核态虚拟内存空间 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
而在划分出的这些虚拟内存空间中如上图所示 , 里边又包含了许多特定的虚拟内存区域 , 比如:代码段 , 数据段 , 堆 , 内存映射区 , 栈 。那么这些虚拟内存区域在内核中又是如何表示的呢?

经验总结扩展阅读