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


除此之外 , 我们的程序在运行过程中还需要依赖动态链接库 , 这些动态链接库以 .so 文件的形式存放在磁盘中 , 比如 C 程序中的 glibc,里边对系统调用进行了封装 。glibc 库里提供的用于动态申请堆内存的 malloc 函数就是对系统调用 sbrk 和 mmap 的封装 。这些动态链接库也有自己的对应的代码段,数据段,BSS 段,也需要一起被加载进内存中 。
还有用于内存文件映射的系统调用 mmap,会将文件与内存进行映射,那么映射的这块内存(虚拟内存)也需要在虚拟地址空间中有一块区域存储 。
这些动态链接库中的代码段,数据段,BSS 段,以及通过 mmap 系统调用映射的共享内存区,在虚拟内存空间的存储区域叫做文件映射与匿名映射区 。

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

文章插图
最后我们在程序运行的时候总该要调用各种函数吧,那么调用函数过程中使用到的局部变量和函数参数也需要一块内存区域来保存 。这一块区域在虚拟内存空间中叫做栈 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
现在进程的虚拟内存空间所包含的主要区域 , 笔者就为大家介绍完了,我们看到内核根据进程运行的过程中所需要不同种类的数据而为其开辟了对应的地址空间 。分别为:
  • 用于存放进程程序二进制文件中的机器指令的代码段
  • 用于存放程序二进制文件中定义的全局变量和静态变量的数据段和 BSS 段 。
  • 用于在程序运行过程中动态申请内存的堆 。
  • 用于存放动态链接库以及内存映射区域的文件映射与匿名映射区 。
  • 用于存放函数调用过程中的局部变量和函数参数的栈 。
以上就是我们通过一个程序在运行过程中所需要的数据所规划出的虚拟内存空间的分布 , 这些只是一个大概的规划,那么在真实的 Linux 系统中,进程的虚拟内存空间的具体规划又是如何的呢?我们接着往下看~~
4. Linux 进程虚拟内存空间在上小节中我们介绍了进程虚拟内存空间中各个内存区域的一个大概分布,在此基础之上,本小节笔者就带大家分别从 32 位 和 64 位机器上看下在 Linux 系统中进程虚拟内存空间的真实分布情况 。
4.1 32 位机器上进程虚拟内存空间分布在 32 位机器上,指针的寻址范围为 2^32,所能表达的虚拟内存空间为 4 GB 。所以在 32 位机器上进程的虚拟内存地址范围为:0x0000 0000 - 0xFFFF FFFF 。
其中用户态虚拟内存空间为 3 GB,虚拟内存地址范围为:0x0000 0000 - 0xC000 000。
内核态虚拟内存空间为 1 GB,虚拟内存地址范围为:0xC000 000 - 0xFFFF FFFF 。
一步一图带你深入理解 Linux 虚拟内存管理

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

推荐阅读