参考文章:https://www.cnblogs.com/yaoxiaowen/p/7805964.html
问题:
1, 进程是,一个程序在运行时,会有这样一种假象,该程序(也就是这个进程)好像是独占使用 CPU 和内存,CPU 是没有间断地一条接一条的执行该程序的指令,所有的内存空间都是供该进程的代码和数据分配使用的。这是如何做到的。
不同进程之间,如果都同时使用一个内存地址,那么为什么没有引起冲突和错误
在程序运行时,库代码也要被加到内存中,如果许多程序都应用了这个库,那么内存中需要对库代码加载好多份吗?如果不是的,那么库代码又如何被所有进程所共享?
一 物理和虚拟寻址
1.1 物理寻址
在访问者看来,内存就是一个有M个字节大小的单元组成的数组,每个字节都有唯一的物理地址(Physical Address PA)。它的访问地址和数组一样,第一个地址都是0,后面的地址依次为 1,2,3 .... M-1, 这叫做线性地址空间。这种自然的寻址方式我们称之为物理寻址(physical addressing)。
注意:在访问内存时,对于任意一个地址,不管是(第0个还是第M-1个),访问该地址的时间总是相同的。
在各种数据结构中,我们都说 hash 表是最快的,比红黑树都要快,为啥 hash 最快?hash 表内部本质是永乐数组,所以本质上还是数组最快。为什么数组最快?这是因为知道数组的起始地址以及某个元素的序号,就可以得到该元素在内存中的地址,而对内存,访问任意一个地址,访问时间总是相同的。而类似链表、数等结构,却只能靠遍历了。
上图是一个物理寻址的例子,这是一条加载指令,它读取从物理地址5开始的3个字节,CPU通过内存总线将指令和地址传给内存,内存读取从物理地址5开始的3个字节,返回给CPU。
1.2 虚拟寻址
早期计算机使用物理寻址方式,但是到了现在的多任务计算机时代,普遍使用的是虚拟寻址(virtual Address)
CPU 通过一个虚拟地址(virtual address, VA)来访问内存,这个虚拟地址在被送到内存之前会先转换成一个物理地址,将虚拟地址转化成物理地址的任务叫做地址翻译(address translation)
有少数现代计算机系统依旧采用物理寻址方式,比如嵌入式、超级计算机。这些系统主要是执行单一任务,不像通用计算机那样使用多任务。可以想象到,物理寻址方式更快。这个道理,和Java比C++ 慢是一个道理。(虚拟机 / C++)
之所以不会发生文章开头的问题,正式因为虚拟内存的存在。
进程地址空间
上图是一个 64位 的进程地址空间,编译器在编译程序时,将结果编译 32/64 位的地址空间。虚拟寻址方式简化了编译器,链接器的工作。
同时也因为虚拟内存,每个进程才能有很大的,一致的,私有的地址空间。这方便了内存管理,保护了每个进程的地址空间不被其他进程破坏,同时也方便了共享库。
三、虚拟内存也是一种缓存思想
虚拟内存将内存看成是一个磁盘的高速缓存,内存中只保存活动区域,并根据需要在磁盘和内存之间来回传递数据。
从概念上说,虚拟内存被组织成一个由存放在磁盘上的 N 个连续的字节大小的单元组成的数组,也就是字节数组。
每个字节都有一个唯一的虚拟地址作为数组的索引。虚拟内存的地址和磁盘的地址之间建立映射关系。磁盘上活动的数组内容被缓存在主存中,在存储器层次中,磁盘(较低层L5)的数据被分割为块(block),这些块作为和内存(较高层,L4)之间的传输单元。内存作为虚拟内存(或者说磁盘)的缓存。
虚拟内存(VM)系统将虚拟内存分割为大小固定的虚拟页(Virtual Page),每个虚拟页的大小为固定字节。同样的,物理内存被分割为物理页(Physical Page PP),大小也为固定字节(物理也成称为页帧,Page frame)。
在任意时刻,虚拟页面都分为三个不相交的部分:
- 未分配的(Unallocated), VM 系统还未分配(或者创建)的页,未分配的页没有任何数据和它们关联,因此不占用任何内存/磁盘空间。
- 缓存的(Cache):当前已缓存在物理内存中的已分配页
-
未缓存的(UnCache):该页已经映射到磁盘上,但是还未缓存在屋里内存中。
virtual.png
这张图展示了在一个有 8 个页面的虚拟内存中,虚拟 0 和 3 还没有被分配,所以
其中未分配的VP不占用任何的实际物理空间。32位程序地址空间就有4G,至于64G的程序的地址空间也是非常的(2的64次),而目前我们电脑的高配也就2T,16G内存。如果64位程序每个VP都映射着实际的PP。无论如何也对不上。并且也完全没必要。
页表(page table)
系统必须得有办法判断某个虚拟页是否缓存在内存的某个地方。这具体分为两种情况:
- 已经在内存中,就需要判断该虚拟页存在于哪个物理页中
- 不在内存中,那么系统必须判定虚拟页存放在磁盘的哪个位置,并且在物理内存中选择一个牺牲页,并将该虚拟页从磁盘复制到内存,替换这个牺牲页。
这些功能由软硬件联合起来,包括操作系统,CPU 中的内存管理单元(Memory Management Unit,MMU)和一个存放在物理内存中叫页表(page table)的数据结构,页表将虚拟页映射到物理页。
上图展示了一个页面的基本结构,页表是一个页表条目(Page Table Entry, PTE)的数组。虚拟地址的每个页都在页表中有一个对应的 PTE。在这里我们假设每个 PTE 是由一个有效位(Valid bit)和一个 n 位地址字段组成的。有效位表明了该虚拟页是否被缓存在内存中:
- 有效位为1,则内存缓存了该虚拟页,地址字段表示内存中相应的物理页的起始位置。
- 有效位为0,则地址字段的null表示这个虚拟页还未被分配,否则该地址就指向该虚拟页在磁盘上的起始位置。
页命中与缺页
在存储器层次结构中说过缓存命中与不命中的问题,都是缓存思想。磁盘与内存之间的缓存不命中代价肯定大的多。因为 L0 - L4 之间,每级缓存的速度大约相差了 10 倍左右,但是 L4 主存与 L5 磁盘之间,它们的速度相差十万倍。所以主存与磁盘之间交换的页容量是最大的,尽可能的增加命中率。
在存储器层次,每次替换的区域,是以块(block)为单位,在这里却是 页(page)。其实两者都是同一个意思,因历史原因叫法不同。
当 CPU 想要读取包含在某个虚拟页的内容时,如果该页已经缓存在主存中,也就是页命中。但是如果该页美欧缓存在内存中,则称之为缺页(page fault).
Page fault: 指向虚拟内存,它不在物理内存中。
如上图所示, CPU 引用了 VP3 中的内容,VP3 并未缓存在主存中。系统从内存中读取 PTE3 时, 得知 VP3 未被缓存,这会触发一个缺页异常。缺页异常会调用 kernel 的缺页异常处理程序,该程序会选择一个牺牲页。如下图所示,牺牲页选择了存放在 PP3 中的 VP4.
此时如果 VP4 的内容被修改了,Kernel 会将它复制回磁盘的。接下来,Kernel 从磁盘赋值 VP3 到内存中的 PP3 并更新 PTE3。随后返回用户进程。当异常处理程序返回时,它会重启执行导致缺页的指令,当重新执行这条指令时,因为此时VP3已经在主存中,此时就是页命中。
这张图显示了 vp3 被缓存到 pp3.
根据习惯性的叫法,我们在磁盘和内存之间传递页的活动叫做交换(swapping)或者页面调度(paging)。这种交换活动,只有当不命中发生时才会发生(也就是当系统没有把磁盘内存预存到内存中)。这种策略叫做按需页面调度(demand)。
虚拟内存作为内存管理和内存保护的工具
理所当然,每个进程都有一个独立的页表和一个独立的虚拟地址空间.
比如每个 C 程序都要调用 stdio 这个库,不可能为每个进程都添加一份库,内存中只有一份 stdio 库的内容,供每个使用该库的进程共享。
如上图所示,第一个进程的的页表将 VP2 映射到某个物理页面,而第二个进程的同样将它的 VP2 映射到该物理页面。所以该物理页面被这两个进程所共享。
在 c 语言中存在指针,可以直接进行内存操作。因为有了虚拟内存,所以我们的指针操作也不会访问到其他进程的区域,但是哪怕对于自己的地址空间,很多内存区域也应该是禁止访问的,这不仅包括 kernel 的区域,也包括自己的只读代码段。那么虚拟内存就提供了这样的一种内存保护工具。
地址翻译机制可以使用一种自然的方式来提供内存的访问控制。PTE上添加一些额外的控制位来添加权限。每次 CPU 生成一个地址时,地址翻译硬件都会读一个 PTE。
虚拟内存提供内存保护,在上图中,每个 PTE 额外添加了三个控制位,SUP 表示进程是否必须运行内核模式,READ 和 WRITE 分别控制页面的读写权限。如果有指令违反了这些控制权限,那么 CPU 会触发一个故障,并将控制传递给内核中的异常处理程序。这种异常被称为段错误(segmentation fault)
段和页
页是操作系统为了管理主存方便而划分的,对用户不可见。但是思考这个情况,一个页的大小是 1 M,但是某个程序数据加起来也就 0.5 M,所以在内存和磁盘进行页交换明显浪费了内存。所以还有一种划分方式是分段。
swap 分区的作用
linux 有一个 swap 分区,它的作用是当系统的物理内存不够用的时候,就要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自于一些很长时间也没有什么操作的程序,这些被释放的空间中的信息被临时保存到 Swap 区域,等到那些程序要运行时,再从 Swap 中唤醒,恢复保存的数据到内存中。系统总是在物理内存不够时,才进行 Swap交换。
特别注意,按照字面意思,swap 交换区也可以称为虚拟内存。硬盘上的 swap 交换区,其实就相当于承担了内存的作用。swap 交换区起到了扩大内存的作用。所以从某种意义上讲,swap 区也可以叫做虚拟内存,但是这个虚拟内存是字面意思,和我们本文中站在计算机系统角度来解释的虚拟内存不是一个概念。