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


0x0000 0000 到 0x0804 8000 这段虚拟内存地址是一段不可访问的保留区 , 因为在大多数操作系统中 , 数值比较小的地址通常被认为不是一个合法的地址 , 这块小地址是不允许访问的 。比如在 C 语言中我们通常会将一些无效的指针设置为 NULL , 指向这块不允许访问的地址 。
保留区的上边就是代码段和数据段 , 它们是从程序的二进制文件中直接加载进内存中的 , BSS 段中的数据也存在于二进制文件中 , 因为内核知道这些数据是没有初值的 , 所以在二进制文件中只会记录 BSS 段的大小 , 在加载进内存时会生成一段 0 填充的内存空间 。
紧挨着 BSS 段的上边就是我们经常使用到的堆空间 , 从图中的红色箭头我们可以知道在堆空间中地址的增长方向是从低地址到高地址增长 。
内核中使用 start_brk 标识堆的起始位置 , brk 标识堆当前的结束位置 。当堆申请新的内存空间时 , 只需要将 brk 指针增加对应的大小 , 回收地址时减少对应的大小即可 。比如当我们通过 malloc 向内核申请很小的一块内存时(128K 之内) , 就是通过改变 brk 位置实现的 。
堆空间的上边是一段待分配区域 , 用于扩展堆空间的使用 。接下来就来到了文件映射与匿名映射区域 。进程运行时所依赖的动态链接库中的代码段 , 数据段 , BSS 段就加载在这里 。还有我们调用 mmap 映射出来的一段虚拟内存空间也保存在这个区域 。注意:在文件映射与匿名映射区的地址增长方向是从高地址向低地址增长 。
接下来用户态虚拟内存空间的最后一块区域就是栈空间了 , 在这里会保存函数运行过程所需要的局部变量以及函数参数等函数调用信息 。栈空间中的地址增长方向是从高地址向低地址增长 。每次进程申请新的栈地址时 , 其地址值是在减少的 。
在内核中使用 start_stack 标识栈的起始位置 , RSP 寄存器中保存栈顶指针 stack pointer , RBP 寄存器中保存的是栈基地址 。
在栈空间的下边也有一段待分配区域用于扩展栈空间 , 在栈空间的上边就是内核空间了 , 进程虽然可以看到这段内核空间地址 , 但是就是不能访问 。这就好比我们在饭店里虽然可以看到厨房在哪里 , 但是厨房门上写着 “厨房重地 , 闲人免进”  , 我们就是进不去 。

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

文章插图
4.2 64 位机器上进程虚拟内存空间分布上小节中介绍的 32 位虚拟内存空间布局和本小节即将要介绍的 64 位虚拟内存空间布局都可以通过 cat /proc/pid/maps 或者 pmap pid 来查看某个进程的实际虚拟内存布局 。
我们知道在 32 位机器上 , 指针的寻址范围为 2^32 , 所能表达的虚拟内存空间为 4 GB 。
那么我们理所应当的会认为在 64 位机器上 , 指针的寻址范围为 2^64 , 所能表达的虚拟内存空间为 16 EB。虚拟内存地址范围为:0x0000 0000 0000 0000 0000 - 0xFFFF FFFF FFFF FFFF。
好家伙 !!! 16 EB 的内存空间 , 笔者都没见过这么大的磁盘 , 在现实情况中根本不会用到这么大范围的内存空间 , 
事实上在目前的 64 位系统下只使用了 48 位来描述虚拟内存空间 , 寻址范围为2^48  , 所能表达的虚拟内存空间为 256TB 。
其中低 128 T 表示用户态虚拟内存空间 , 虚拟内存地址范围为:0x0000 0000 0000 0000- 0x0000 7FFF FFFF F000。

经验总结扩展阅读