超全的深度剖析内存系列——2. 虚拟内存 (一)

本贴最后更新于 2356 天前,其中的信息可能已经事过境迁

上一节博客中,博主和同学们一起学习了一些内存相关的计算机基础知识,本节博客我们继续来学习物理内存管理相关的知识。

1. 计算机存储系统层次

计算机的存储器的最优设计目标应该是更大的容量,更快的速度和更低的价格。然而事实上容量,速度,价格这三个方面是互相矛盾的,人们没法设计出完全满足三个条件的存储器。一个折中的解决方案就是不要依赖单一的存储组件或技术,而是采取一种层次结构的存储系统设计。

4e603b19949249ffbdd09770fe30bf1b.png

  • 寄存器
    寄存器是 CPU 用来存储临时数据的地方,受限于集成程度和造价,寄存器的位数一般都不是很高,因此寄存器只能用来存储相当小量的数据,通常在几十到几百字节。
  • 高速缓存
    高速缓存是位于 CPU 与主内存间的一种容量较小但速度很高的存储器。由于 CPU 的速度远高于主内存,CPU 直接从内存 ` 中存取数据要等待一定时间周期,Cache 中保存着 CPU 刚用过或循环使用的一部分数据,当 CPU 再次使用该部分数据时可从 Cache 中直接调用,这样就减少了 CPU 的等待时间,提高了系统的效率。
  • 内存
    所有 CPU 处理的数据都是从内存中读取到的,它被设计成了一个由 M 个连续的字节大小的存储单元组成的数组,内存是数据存储的重要空间,但所有存在内存中的数据只有在计算机通电时才能存在,无法持久化。
  • I/O 设备
    主要就是我们的磁盘等外部存储,能存储大量的数据并实现持久化而且价格较上述介质来说最低,但是读写数据速度是几种存储介质中最慢的。

2.物理寻址和虚拟寻址

要想对内存中的数据进行读写,我们必须知道该数据所在的内存的地址。由于计算机的内存被设计为由 M 个连续的字节大小的存储单元组成的数组,在这个数组中每个字节都有一个唯一的物理地址(Physical Address, PA),由低至高,第一个字节地址为 0,下一个为 1,以此类推。

如果 CPU 直接以内存的物理地址来访问,这种方式就称为物理寻址(physical addressing)。这种方式很简单,在早期的计算机系统中使用的就是这种寻址方式。在这种寻址方式下,我们在程序中也必须使用内存的物理地址来进行读写数据。这种寻址方式对于现代计算机系统来看存在着相当大的弊端。首先直接使用物理地址就意味着一旦程序发生失误(比如写错了地址),就会修改到别的内存中的数据,更有甚者会直接使操作系统崩溃(比如误修改了操作系统所在内存的数据);其次,我们在程序时很少直接知道我们需要操作的内存的地址,即使我们真的使用了正确的物理地址写完了程序,这种程序也只能在一台 PC 上使用,因为程序一旦换到另一台 PC 上,物理地址也会相应的发生改变,因此程序移植性极差;最后在多进程同时运行的计算机系统中,一旦两个进程同时对同一物理地址进行操作,那么两个进程都会发生程序逻辑上无法检测出的错误。

因此这种寻址方式比较适合在只有单一进程运行的简单芯片上工作,CPU 能够以最直接的方式访问内存同时不用担心内存地址误用的情况

现代的操作系统中,大多都采用一种**虚拟寻址(virtual addressing)**的寻址形式。这种寻址方式,简单来说就是 CPU 通过虚拟地址(Virtual Address, VA)来访问内存,这个虚拟地址在被送到内存之前会被转换成合适的物理地址,这个工作通常由 CPU 上的内存管理单元(Memory Management Unit, MMU)来完成。

3. 地址空间

地址空间是一个非负整数地址的有序集合。

在一个带有虚拟内存的系统中,CPU 能够从一个拥有 N(N=2n)个地址的地址空间中生成虚拟地址,这个地址空间就被称为虚拟地址空间(virtual address space)。对于一个系统而言还有一个拥有 M 个地址的物理地址空间(physical address space),对应于系统中物理内存的 M 个字节。

举个例子来说,对于一个 32 位的操作系统来说,它拥有一个 232 = 4G 的虚拟地址空间。与之相对的是我们的物理地址空间,它是和系统中的物理内存的字节数对应的,因此如果我们计算机上内存为 1 个 G,那么物理地址空间就是 1 个 G,如果内存是 512M,那么物理地址空间也就是 512M 了。还记得在上一节博客中,我们提到了地址总线的宽度决定了计算机的最大寻址范围,因此物理地址空间的最大取值也是由地址总线的宽度决定的。

总体来看,虚拟地址空间的大小是由操作系统位数(也就是支持的指令集位数决定的),现代的操作系统通常都支持的是 32 位的虚拟地址空间(232=4G)或 64 位的虚拟地址空间(248=256T,注:64 位的操作系统通常只支持 48 位的虚拟地址空间)。而物理虚拟地址空间的大小是由计算机的实际物理内存决定的(最大不能超过 2n,其中 n 为地址总线宽度)。

有的同学可能要问,直接使用物理地址空间中的物理地址来操作内存不是更方便么,为什么操作系统如此大费周章的提供一个比物理地址空间更大的虚拟地址空间呢?

虚拟地址空间其实是一种操作系统对内存优雅的管理机制——虚拟内存技术为每个进程提供的一个更大的,一致的和私有的地址空间。这种技术主要提供了以下三个重要的能力:

  1. 它将内存看作是存储在磁盘上的地址空间的高速缓存,在内存中只保存活跃区域,并根据需要在内存与磁盘之间来回传送数据。比如一个进程在不活动的情况下,操作系统会将该进程物理内存上的数据移动到一个磁盘的文件中(在 Windows 系统上就是页面文件,在 Linux 系统上的 SWAP 交换分区),将内存留给正在活动的进程使用,这也是为什么我们如果唤醒一个长时间没有使用的程序时,磁盘会吱吱作响,这是操作系统再将磁盘上的数据重新返回到内存中的证据。
  2. 它为每个进程提供一个一致的地址空间,简化了程序开发人员对内存的使用。如果没有统一的虚拟地址空间,每个程序中需要使用内存时就不得不使用物理地址空间中的物理地址。但是正如我们在上文中所提到的,直接使用物理地址是一个相当困难且易因为误操作造成损失的行为。因此每个进程都面对相同的虚拟地址空间,使用内存时都使用虚拟地址,能够大大降低我们使用内存出错的概率,至于虚拟地址究竟指向哪个真实的物理地址,开发程序的人完全不需要操心,全部都由操作系统搞定。
  3. 它保护了每个进程地址空间不被其它进程所破坏。由于每个进程所面对的虚拟地址空间的都是独立的,即使两个进程使用的相同的虚拟地址,操作系统也会将它映射到不同的物理地址上去,因此一个进程无法直接访问到另一个进程的私有内存。即使是一个进程与其它进程共享的虚拟地址页,MMU 也会添加一些额外的许可位来控制访问的权限的。

下图来自美团技术团队,讲述了 PC 硬件、操作系统、进程三个层面的内存之间的概要关系:

aed75d94870a47f996f696fff0c952f5-03.png

  • 从 PC 硬件的角度来看,内存分为两部分:一部分是物理内存另一部分就是磁盘上的辅存(Windows 系统上的页面文件或 Linux 系统上的 SWAP)。物理内存是操作系统活动的主要内存区域,当物理内存不够时,操作系统会将一部分不常用的数据移动到到虚拟内存上去,从而获得更多可用的物理内存;当需要磁盘上的数据时在将它们返回到内存中去。

  • 从操作系统的角度来看,除了引导系统的 BIN 区,整个内存空间主要被分成两个部分:内核内存(Kernel space)、用户内存(User space)。内核空间主要是指操作系统运行时用于程序调度、内存分配、连接硬件资源等程序逻辑使用的内存空间;用户内存是提供给各个进程主要空间,也就是我们上面提到的操作系统提供给各个进程的一致并且独立的虚拟地址空间。

  • 对每个进程而言,上面的虚拟地址空间被更细致地划分为 5 个部分:代码区、数据区、堆区、栈区、未使用区。代码区中存放应用程序的机器代码,运行过程中代码不能被修改,具有只读和固定大小的特点。数据区中存放了应用程序中的全局数据,静态数据和一些常量字符串等,其大小也是固定的。堆是运行时程序动态申请的空间,属于程序运行时直接申请、释放的内存资源。栈区用来存放函数的传入参数、临时变量,以及返回地址等数据。未使用区是分配新内存空间的预备区域。

内核空间和用户空间的大小分配也是一个问题。当前 32 位的 Windows 系统中默认的内核内存和用户内存比例为 1:1(各占 2GB),而在 32 位的 Linux 系统中,默认的比例是 1:3(1GB 的内核内存和 3GB 的用户内存)

下节博客中我们会继续讲解相关的虚拟内存技术。

参考:

  1. http://www.ibm.com/developerworks/library/j-nativememory-linux/
  2. https://tech.meituan.com/linux-jvm-memory.html
  3. 《深入理解计算机系统》

总结不易,转载请注明:

转:http://www.wxueyuan.com/blog/articles/2017/11/04/1509794123120.html
文章来源:Jesmin 的个人博客

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...