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


说了这么多,那么到底虚拟内存地址长什么样子呢?
我们还是以日常生活中的收货地址为例做出类比,我们都很熟悉收货地址的格式:xx省xx市xx区xx街道xx小区xx室,它是按照地区层次递进的 。同样,在计算机世界中的虚拟内存地址也有这样的递进关系 。
这里我们以 Intel Core i7 处理器为例,64 位虚拟地址的格式为:全局页目录项(9位)+ 上层页目录项(9位)+ 中间页目录项(9位)+ 页内偏移(12位) 。共 48 位组成的虚拟内存地址 。

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

文章插图
虚拟内存地址中的全局页目录项就类比我们日常生活中收获地址里的?。喜阋衬柯枷罹屠啾仁?,中间层页目录项类比区县 , 页表项类比街道小区,页内偏移类比我们所在的楼栋和几层几号 。
这里大家只需要大体明白虚拟内存地址到底长什么样子,它的格式是什么,能够和日常生活中的收货地址对比理解起来就可以了,至于页目录项 , 页表项以及页内偏移这些计算机世界中的概念,大家暂时先不用管,后续文章中笔者会慢慢给大家解释清楚 。
32 位虚拟地址的格式为:页目录项(10位)+ 页表项(10位) + 页内偏移(12位) 。共 32 位组成的虚拟内存地址 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
进程虚拟内存空间中的每一个字节都有与其对应的虚拟内存地址,一个虚拟内存地址表示进程虚拟内存空间中的一个特定的字节 。
2. 为什么要使用虚拟地址访问内存经过第一小节的介绍,我们现在明白了计算机世界中的虚拟内存地址的含义及其展现形式 。那么大家可能会问了,既然物理内存地址可以直接定位到数据在内存中的存储位置,那为什么我们不直接使用物理内存地址去访问内存而是选择用虚拟内存地址去访问内存呢?
在回答大家的这个疑问之前 , 让我们先来看下,如果在程序中直接使用物理内存地址会发生什么情况?
假设现在没有虚拟内存地址,我们在程序中对内存的操作全都都是使用物理内存地址,在这种情况下,程序员就需要精确的知道每一个变量在内存中的具体位置,我们需要手动对物理内存进行布局,明确哪些数据存储在内存的哪些位置,除此之外我们还需要考虑为每个进程究竟要分配多少内存?内存紧张的时候该怎么办?如何避免进程与进程之间的地址冲突?等等一系列复杂且琐碎的细节 。
如果我们在单进程系统中比如嵌入式设备上开发应用程序,系统中只有一个进程,这单个进程独享所有的物理资源包括内存资源 。在这种情况下 , 上述提到的这些直接使用物理内存的问题可能还好处理一些,但是仍然具有很高的开发门槛 。
然而在现代操作系统中往往支持多个进程,需要处理多进程之间的协同问题,在多进程系统中直接使用物理内存地址操作内存所带来的上述问题就变得非常复杂了 。
这里笔者为大家举一个简单的例子来说明在多进程系统中直接使用物理内存地址的复杂性 。
比如我们现在有这样一个简单的 Java 程序 。
public static void main(String[] args) throws Exception {string i = args[0];..........}在程序代码相同的情况下,我们用这份代码同时启动三个 JVM 进程,我们暂时将进程依次命名为 a , b , c。
这三个进程用到的代码是一样的,都是我们提前写好的 , 可以被多次运行 。由于我们是直接操作物理内存地址,假设变量 i 保存在 0x354 这个物理地址上 。这三个进程运行起来之后,同时操作这个 0x354 物理地址,这样这个变量 i 的值不就混乱了吗? 三个进程就会出现变量的地址冲突 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
所以在直接操作物理内存的情况下 , 我们需要知道每一个变量的位置都被安排在了哪里,而且还要注意和多个进程同时运行的时候,不能共用同一个地址 , 否则就会造成地址冲突 。
现实中一个程序会有很多的变量和函数,这样一来我们给它们都需要计算一个合理的位置,还不能与其他进程冲突 , 这就很复杂了 。
那么我们该如何解决这个问题呢?程序的局部性原理再一次救了我们~~
程序局部性原理表现为:时间局部性和空间局部性 。时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某块数据被访问,则不久之后该数据可能再次被访问 。空间局部性是指一旦程序访问了某个存储单元 , 则不久之后,其附近的存储单元也将被访问 。

推荐阅读