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


从程序局部性原理的描述中我们可以得出这样一个结论:进程在运行之后,对于内存的访问不会一下子就要访问全部的内存,相反进程对于内存的访问会表现出明显的倾向性,更加倾向于访问最近访问过的数据以及热点数据附近的数据 。
根据这个结论我们就清楚了 , 无论一个进程实际可以占用的内存资源有多大,根据程序局部性原理,在某一段时间内,进程真正需要的物理内存其实是很少的一部分,我们只需要为每个进程分配很少的物理内存就可以保证进程的正常执行运转 。
而虚拟内存的引入正是要解决上述的问题,虚拟内存引入之后,进程的视角就会变得非常开阔,每个进程都拥有自己独立的虚拟地址空间,进程与进程之间的虚拟内存地址空间是相互隔离 , 互不干扰的 。每个进程都认为自己独占所有内存空间,自己想干什么就干什么 。

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

文章插图
系统上还运行了哪些进程和我没有任何关系 。这样一来我们就可以将多进程之间协同的相关复杂细节统统交给内核中的内存管理模块来处理,极大地解放了程序员的心智负担 。这一切都是因为虚拟内存能够提供内存地址空间的隔离 , 极大地扩展了可用空间 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
这样进程就以为自己独占了整个内存空间资源,给进程产生了所有内存资源都属于它自己的幻觉,这其实是 CPU 和操作系统使用的一个障眼法罢了,任何一个虚拟内存里所存储的数据,本质上还是保存在真实的物理内存里的 。只不过内核帮我们做了虚拟内存到物理内存的这一层映射,将不同进程的虚拟地址和不同内存的物理地址映射起来 。
当 CPU 访问进程的虚拟地址时,经过地址翻译硬件将虚拟地址转换成不同的物理地址,这样不同的进程运行的时候,虽然操作的是同一虚拟地址,但其实背后写入的是不同的物理地址 , 这样就不会冲突了 。
3. 进程虚拟内存空间上小节中,我们介绍了为了防止多进程运行时造成的内存地址冲突 , 内核引入了虚拟内存地址,为每个进程提供了一个独立的虚拟内存空间,使得进程以为自己独占全部内存资源 。
那么这个进程独占的虚拟内存空间到底是什么样子呢?在本小节中,笔者就为大家揭开这层神秘的面纱~~~
在本小节内容开始之前 , 我们先想象一下,如果我们是内核的设计人员,我们该从哪些方面来规划进程的虚拟内存空间呢?
本小节我们只讨论进程用户态虚拟内存空间的布局 , 我们先把内核态的虚拟内存空间当做一个黑盒来看待,在后面的小节中笔者再来详细介绍内核态相关内容 。
首先我们会想到的是一个进程运行起来是为了执行我们交代给进程的工作 , 执行这些工作的步骤我们通过程序代码事先编写好,然后编译成二进制文件存放在磁盘中,CPU 会执行二进制文件中的机器码来驱动进程的运行 。所以在进程运行之前,这些存放在二进制文件中的机器码需要被加载进内存中,而用于存放这些机器码的虚拟内存空间叫做代码段 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
在程序运行起来之后,总要操作变量吧,在程序代码中我们通常会定义大量的全局变量和静态变量,这些全局变量在程序编译之后也会存储在二进制文件中,在程序运行之前 , 这些全局变量也需要被加载进内存中供程序访问 。所以在虚拟内存空间中也需要一段区域来存储这些全局变量 。
  • 那些在代码中被我们指定了初始值的全局变量和静态变量在虚拟内存空间中的存储区域我们叫做数据段 。
  • 那些没有指定初始值的全局变量和静态变量在虚拟内存空间中的存储区域我们叫做 BSS 段 。这些未初始化的全局变量被加载进内存之后会被初始化为 0 值 。

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

文章插图
上面介绍的这些全局变量和静态变量都是在编译期间就确定的,但是我们程序在运行期间往往需要动态的申请内存,所以在虚拟内存空间中也需要一块区域来存放这些动态申请的内存,这块区域就叫做堆 。注意这里的堆指的是 OS 堆并不是 JVM 中的堆 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图

推荐阅读