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


这里我们可以看出,64 位虚拟内存空间的布局是和物理内存页 page 的大小有关的,物理内存页 page 默认大小 PAGE_SIZE 为 4K 。
PAGE_SIZE 定义在 /arch/x86/include/asm/page_types.h文件中:
/* PAGE_SHIFT determines the page size */#define PAGE_SHIFT12#define PAGE_SIZE(_AC(1,UL) << PAGE_SHIFT)而内核空间的起始地址是 0xFFFF 8000 0000 0000。在 0x00007FFFFFFFF000 - 0xFFFF 8000 0000 0000 之间的内存区域就是我们在 《4.2 64 位机器上进程虚拟内存空间分布》小节中介绍的 canonical address 空洞 。
5.2 内核如何布局进程虚拟内存空间在我们理解了内核是如何划分进程虚拟内存空间和内核虚拟内存空间之后,那么在 《3. 进程虚拟内存空间》小节中介绍的那些虚拟内存区域在内核中又是如何划分的呢?
接下来笔者就为大家介绍下内核是如何划分进程虚拟内存空间中的这些内存区域的,本小节的示例图中 , 笔者只保留了进程虚拟内存空间中的核心区域,方便大家理解 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
前边我们提到,内核中采用了一个叫做内存描述符的 mm_struct 结构体来表示进程虚拟内存空间的全部信息 。在本小节中笔者就带大家到 mm_struct 结构体内部去寻找下相关的线索 。
struct mm_struct {unsigned long task_size;/* size of task vm space */unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;unsigned long mmap_base;/* base of mmap area */unsigned long total_vm;/* Total pages mapped */unsigned long locked_vm;/* Pages that have PG_mlocked set */unsigned long pinned_vm;/* Refcount permanently increased */unsigned long data_vm;/* VM_WRITE & ~VM_SHARED & ~VM_STACK */unsigned long exec_vm;/* VM_EXEC & ~VM_WRITE & ~VM_STACK */unsigned long stack_vm;/* VM_STACK */...... 省略 ........}内核中用 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 是栈中所映射的内存页数目,这些变量均是表示进程虚拟内存空间中的虚拟内存使用情况 。

推荐阅读