概述
文章目录
- 虚拟内存
- 分页
- 页表
- 页表项的结构
- 加速分页过程
- 转换检测缓冲区
- 软件TLB管理
- 针对大内存的页表
- 多级页表
- 倒排页表
- 分段
虚拟内存
虚拟内存的基本思想:每个程序拥有自己的地址空间,这个空间被分割成多个块,每个块称作一页或页面。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。
分页
当程序引用了一组内存地址,当程序执行指令。
mov REG,1000
时,它把地址为1000的内存单元的内容复制到REG中。地址可以通过索引,基址寄存器,段寄存器或其他方式产生。
由程序产生的这些地址称为虚拟地址,它们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字;而在使用虚拟内存的情况下,虚拟内存不是被直接送到内存总线上,而是被送到内存管理单元(Memory Management Unit,MMU),MMU把虚拟地址映射为物理内存地址。如图3-8所示。
图3-9所示的例子说明了这种映射是如何巩固走的。在这个例子中,有一台可以 产生16位地址的计算机,地址范围从 0 0 0到 64 K − 1 64K-1 64K−1,且这些地址是虚拟地址。然而,这台计算机只有32KB的物理内存,因此,虽然可以编写64KB的程序,但它们却不能被完全调入内存运行。在磁盘上必须有一个最多64KB的程序核心映像的完整副本,以保证程序片段在需要时能被调入内存。
虚拟地址空间按照固定大小划分成被称为页面的若干单元。在物理内存中对应的单元称为页框。页面和页框的大小通常是一样。在本例中是4KB,但实际系统中的页面大小从512字节到1GB。对应于64KB的虚拟地址空间和32KB的物理内存,可得到16个虚拟页面和8个页框。RAM和磁盘那之间的交换总是以整个页面为单元进行的。
图3-9中的标记符号如下:标记 0 − 4 K 0-4K 0−4K的范围表示该页的虚拟地址或物理地址是 0 − 4095 0-4095 0−4095, 4 K − 8 K 4K-8K 4K−8K的范围表示地址 4096 − 8191 4096-8191 4096−8191,等等。每一项包含了4096个地址,起始于4096的整数倍地址,结束于4096倍数缺1的位置。
当程序试图访问地址0时,例如执行下面这条指令。
mov REG, 0
将虚拟地址0送到MMU。MMU看到虚拟地址落在页面0(04095),根据其映射结果,这一页面对应的是页框2(819212287),因此MMU把地址变换为8192,并把地址8192送到总线上。内存对MMU一无所知,它只看到一个读或写地址8192的请求并执行它。MMU从而有效地把所有从04095的虚拟地址映射到了819212287的物理地址。
当程序访问了一个未映射的页面,例如执行指令
mov reg,32780
将发生什么情况?虚拟页面8(从32768开始)的第12个字节所对应的物理地址什么呢?MMU注意到该页面没有被映射,于是使CPU陷入到操作系统中,这个陷阱称为缺页中断。操作系统找到一个很少使用的页框并把它的内容写入磁盘(如果它不再磁盘上),随后把需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷阱的指令。
例如,如果操作系统决定放弃页框1,那么它将把虚拟页面8装入物理地址4096,并对MMU映射做两处修改。首先,它要将虚拟页面1的表项标记为未映射,使以后任何对虚拟地址4096-8191的访问都导致陷阱。随后把虚拟页面8的表项的叉号改为1,因此在引起陷阱的指令重新启动时,它将把虚拟地址32780映射为物理地址4108(4096+12)。
可用页号作为页表的索引,以得出对应于该虚拟页面的页框号。如果”在/不在“位是0,则将引起一个操作系统陷阱。如果该位是1,则将在页表中查找的页框号复制到输出寄存器的高三位中,再加上输入虚拟地址中的低12位偏移量。如此就构成了15位的物理地址。输出寄存器的内容随机被作为物理地址送到内存总线。
页表
作为一种最简单的实现,虚拟地址到物理地址的映射可以概括如下:虚拟地址被分成虚拟页号(高位部分)和偏移量(低位部分)两部分。例如,对于16位地址和4KB的页面大小,高4位可以指定16个虚拟页面中的一个,而低12位接着确定了所选页面中的字节偏移量(0~4095)。但是使用3或者5或者其他位数拆分虚拟地址也是可行的。不同的划分对应不同的页面大小。
虚拟页号可作为页表的索引,以找到该虚拟页面对应的页表项。由页表项可以找到页框号(如果有的话),然后把页框号拼接到偏移量的高位端,以替换掉虚拟页号,形成送往内存的物理地址。
页表的目的是把虚拟页面映射位页框。从数学的角度来说,页表是一个函数,它的参数是虚拟页号,结果是物理页框号。通过这个函数可以把虚拟地址中的虚拟页面域替换成页框域,从而形成物理地址。
页表项的结构
图3-11给出了页表项的一个例子。不同计算机的页表项大小可能不一样,但32位是一个常用的大小。最重要的域是页框号。毕竟页映射的目的是找到这个值,其次是”在/不在“位。这一位是1时表示该表项是有效的,可以使用;如果是0,则表示该表项对应的虚拟页面现在不在内存中,访问该页面会引起一个缺页中断。
保护位指出一个页允许什么类型的访问。最简单的形式是这个域只有一位,0表示读写,1表示只读。一个更先进的方法是使用三位,各位跟别对应是否启用读,写,执行该页面。
为了记录页面的使用状况,引入了修改位和访问位。在写入一页时由硬件自动设置修改位。该位在操作系统重新分配页框时非常有用。如果一个页面已经被修改过,则必须把它写回磁盘。如果一个页面没有被修改过,则只是简单把它丢弃即可,因为它在磁盘上的副本仍然是有效的,这意味有时也被称为脏位,因为它反应了该页面的状态。
不论是都还是写,系统都会在该页面被访问时设置访问位。它的值被用来帮助操作系统在发生缺页中断时要选择被淘汰的页面。不再使用的页面要比正在使用的页面更适合淘汰。
最后一位用于禁止该页面被高速缓存。对于那些映射到设备寄存器而不是常规的内存的页面而言,这个特性是非常重要的。假如操作系统正在紧张地循环等待某个I/O设备对它刚发出命令做出相应,保证硬件是不断地从设备中读取数据而不是访问一个旧的被高速缓存的副本是非常重要的。通过这一位可以禁止高速缓存。具有独立的I/O空间而不使用内存映射I/O的机器不需要这一位。
应当注意的是,若某个页面不在内存中,用于保存该页面的磁盘地址不是页表的组成部分。原因很简单,页表只保存把虚拟地址转换为物理地址时硬件所需要的信息。操作系统在处理缺页中断时需要把该页面的磁盘地址等信息保存在操作系统内部的软件表格中。硬件不需要它。
加速分页过程
在任何分页系统中,都需要考虑两个主要问题。
- 虚拟地址到物理地址的映射必须非常快
- 如果虚拟地址空间很大,页表也会很大
第一个问题是由于每次访问内存都需要继续宁虚拟地址到物理地址的映射,所有的指令最终都必须来自内存,并且很多指令也会访问内存中的操作数。因此,每条指令进行一两次或者更多页表访问是必要的。如果执行一条指令需要1ns,页表查询必须在0.2ns内完成,以避免映射成为一个主要瓶颈。
第二个问题来自现代计算机使用至少32位的虚拟地址,而且64位变得越来越普遍。假设页面大小为4KB,32位的地址空间将有100万页,而64位地址空间太大了。如果虚拟地址空间由100万页,那么页表必然有100万条表项。另外请记住,每个进程都需要自己的页表(因为它有自己的虚拟地址空间)。
转换检测缓冲区
大多数程序总是对少量的页面进行多次的访问,因此,只有很少的页表项会被反复读取,而其他的页表项很少被访问。
上面提到的解决方案是为计算机设置一个小型的硬件设备,将虚拟地址直接映射到物理地址,而不必再访问页表。这种设备称为转换检测缓冲区(Translation Lookaside Buffer,TLB),有时又称为相联存储器或快表。如图3-12所示。它通常在MMU中,包含少量的表项,在此例中有8个,实际中很少会超过256个。每个表项记录了一个页面的相关信息,包括虚拟页号,页面的修改位,保护码(读/写/执行权限)和该页所对应的物理页框。除了虚拟页号(不是必须放在页表中),这些域与页表中的域是一样的。另外还有一位用来记录这个表项是否有效(即是否在使用)。
现在看一下TLB是如何工作的。将一个虚拟地址放入MMU中进行转换时,硬件首先通过将该虚拟页号与TLB中所有表项同时(即并行)进行匹配,判断虚拟页面是否在其中。如果发现了一个有效的匹配并且要进行的访问操作并不违反保护位,则将页框号直接从TLB中取出而不必再访问页表。如果虚拟页号确实在TLB中,但指令试图在一个只读页面上进行写操作,则会产生一个保护错误,就像对页表进行非法访问一样。
当虚拟页号不在TLB中时会怎么样呢?如果MMU检测到没有有效的匹配项,就会进行正常的页表查询。接着从TLB中淘汰一个表项,然后用新找到的页表项代替它。这样,如果这一页面很快被再次访问,第二次访问TLB时自然将会命中而不是未命中。当一个表项被清除出TLB时,将修改位复制到内存中的页表项,而除了访问位,其他的值不变。当页表项中从页表中装入TLB中时,所有的值都来自内存。
软件TLB管理
到目前为止,我们已经假设每一台具有虚拟内存的机器都具有由硬件识别的页表,以及一个TLB。在这种设计中,对TLB的管理和TLB的失效处理都完全由MMU硬件来实现。只有在内存中没有找到某个页面时,才会陷入到操作系统中。
让人感到惊奇的是,如果TLB达到(如64个表项)可以减少失效率时,TLB的软件管理就会变得足够有效。这种方法的最主要的好处是获得了一个非常简单的MMU,这就在CPU芯片上为高速缓存以及其他改善性能的设计腾出了相当大的空间。
无论是用硬件还是用软件来处理TLB失效,常见方法都是找到页表并执行索引操作以定位将要访问的页面。用软件做这样的搜索的问题是,页表可能不在TLB中,这就会导致处理过程中的额外的TLB失效。可以通过在内存中的固定位置维护一个大的(如4KB)TLB表项的软件高速缓存(该高速缓存的页面一直保存在TLB中)来减少TLB失效。通过首先检查软件高速缓存,操作系统能够实质性的减少TLB失效。
当使用软件TLB管理时,一个基本要求是要理解两种不同的TLB失效的区别在哪里。当一个页面访问在内存中而不在TLB中时,将产生软失效。那么此时所要做的就是更新一下TLB,不需要产生磁盘I/O。相反,当页面本身不再内存中(当然也不在TLB中)时,将产生硬失效。此时需要一次磁盘存取以装入该页面。这个过程大概需要几毫秒。硬失效的处理时间往往时软失效的百万倍。在页表结构中查找相应的映射被称为页表遍历。
针对大内存的页表
怎么样处理巨大的虚拟地址空间,下面讨论两种解决方法。
多级页表
第一种方法是采用多级页表。一个简单的例子如图3-13所示。在图3-13a中,32位的虚拟地址被划分为10位的PT1域,10位的PT2域和12位的Offset(偏移量)域。因为偏移量是12位,所以页面大小是4KB,共有 2 20 2^{20} 220个页面。
引入多级页表的原因是避免把全部页表一直保存在内存中。特别是那些从不需要的页表就不应该保留。
考察图3-13b中的二级页表是如何工作的。在左边是顶级页表,它有1024个个表项,对应于10位的PT1域。当一个虚拟地址被送到MMU时,MMU首先提取PT1域并把该值作为访问顶级页表的索引。因为整个4GB(即32位)虚拟地址空间已经按4KB大小分块,所以顶级页表中这1024个表项的每一个都表示4M的块的地址范围。
由索引顶级页表得到的表项中含有二级页表的地址或页框号。顶级页表的表项0指向程序正文的页表,表项1指向数据的页表,表项1023指向堆栈的页表,其他的表项(用阴影表示的)未用。现在把PT2域作为访问选定的二级页表的索引,以便找到该虚拟页面的对应页框号。
下面看一个示例,考虑32位虚拟地址0x00403004
位于数据部分12292字节处。它的虚拟地址对应PT1=1,PT2=3,Offset=4。MMU首先用PT1作为索引访问顶级页表得到表项1,它对应的地址范围是4M到8M-1。然后,它用PT2作为索引访问刚刚找到的二级页表并得到表项3,它对应的虚拟地址范围是在它的4M块内的12288~16383。这个表项含有虚拟地址0x00403004
所在页面的页框号。如果该页面不在内存中,页表项中的”在/不在“位将是0,引发一次缺页中断。如果该页面在内存中,从二级页表中得到的页框号将与偏移量4结合形成物理地址。该地址被放到总线上并送到内存中。
值得注意的是,虽然在图3-13中虚拟地址空间超过100万个页面,实际上只需要四个页表:顶级页表以及04M(正文段),4M8M(数据段)和顶端4M(堆栈段)的二级页表。顶级页表中的1021个表项的”在/不在“位都被设置为0,当访问它们时出现一个缺页中断。
图3-13所示的二级页表可扩充为三级,四级或者更多级。
倒排页表
针对页式调度层级不断增长的另一种解决方案是倒排页表。在这种设计中,实际内存中的每个页框对应一个表项,而不是每个虚拟页面对应一个表项。例如,对于64位虚拟地址,4KB的页,4GB的RAM,一个倒排页表仅需要1048576个表项。表项记录了哪一个(进程,虚拟页面)对定位于该页框。
虽然倒排页表节省了大量的空间(至少当虚拟地址空间比物理内存大得多的时候是这样的),但它也有严重的不足:从虚拟地址到物理地址的转换会变得很困难。当进程 n n n访问虚拟页面时 p p p时,硬件不再能通过把 p p p当作指向页表的一个索引来查找物理页框。取而代之的是,它必须搜索整个倒排页表来查找某一个表项( n n n, p p p)。此外,该搜索必须对每一个内存访问操作都要执行一次,而不仅仅是发生缺页中断时执行。每次内存访问操作都要查找一个256KB的表不是一种使机器快速运行的方法。
如果TLB能够记录所有频繁使用的页面,地址转换就可能变得像通常的页表一样快。但是,当发生TLB失效时,需要用软件搜索整个倒排页表。实现该搜索的一个可行的方法是建立一张散列表,用虚拟地址来散列。当前所有在内存中的具有相同散列值的虚拟页面被链接在一起,如图3-14所示。如果散列表中的槽数与机器中的物理页面数一样多,那么散列表的冲突链的平均长度将会是1个表项的长度,这将会大大提高映射速度。一旦页框号被找到,新的(虚拟页号,物理页框号)就会被装载到TLB中。
分段
到目前为止讨论的虚拟内存都是一维的,虚拟地址从0到最大地址,一个地址接着另一个地址。对许多问题来说,有两个或多个独立的地址空间可能比只有一个要好得多。比如,一个编译器在编译过程中会建立许多表,其中可能包括:
- 被保存起来供打印清单使用得源程序正文
- 符号表,包含变量的名字和属性
- 包含用到的所有整型量和浮点常量的表
- 语法分析树,包含程序语法分析的结果
- 编译器内部过程调用使用的堆栈
前4个表随着编译的进行不断地增长,最后一个表在编译过程中以一种不可预计的方式增长和缩小。在一维存储器中,这5个表只能被分配到虚拟地址空间中连续的块中,如图3-31所示。
考虑一下如果一个程序中变量的数量要远比其他部分的数量多时的情况。地址空间中分给符号表的块可能会被装满,但这是其他表中还有大量的空间。
所需要的是一种能令程序员不用管理表扩张和收缩的方法,这与虚拟内存解决程序段覆盖问题所用的方法相同。
一个直观并且通用的方法是在机器上提供多个互相独立的称为段的地址空间。每个段由一个从0到最大的线性地址序列构成。各个段的长度可以是0到某个允许的最大值之间的任何一个值。不同的段的长度可以不同,并且通常情况下也都不相同。段的长度在运行期间可以动态改变,比如,堆栈段的长度在数据被压入时会增长,在数据被弹出时又会减小。
因为每个段都构成一个独立的地址空间,所以它们可以独立地增长和减小而不会影响其他的段。如果一个在某个段中的堆栈需要更多的空间,他就可以立刻得到所需要的空间,因为它的地址空间中没有其他东西阻挡它增长。段当然有可能会被装满,但通常情况下段都很大,因此这种情况发生的可能性很小。要在这种分段或二维的存储器中指示一个地址,程序必须提供两部分地址,一个段号和一个段内地址。
需要强调的是,段是一个逻辑实体,一个段可能包括一个过程,一个数组,一个堆栈,一组数值变量,但一般不会同时包含多种不同类型的内容。
如果每个过程都位于一个独立的段中并且起始地址是0,那么把单独编译好的过程链接起来的操作就可以得到很大的简化。
分段有助于在几个进程之间共享过程和数据。这方面一个常见的例子是共享库。
因为每个段是一个为程序员所知道的逻辑实体,比如一个过程或一个数组,故不同的段可以有不同种类的保护。一个过程段可以被指明为只允许执行,从而禁止对它的读出和写入;一个浮点数组可以被指明为允许读写但不允许执行,任何试图向这个段内的挑断都将被截获。
考察点 | 分页 | 分段 |
---|---|---|
存在多少线性地址? | 1 | 许多 |
整个地址空间可以超出物理存储器的大小么? | 是 | 是 |
过程和数据可以被区分并分别被保护么? | 否 | 是 |
其大小浮动的表可以很容易提供么? | 否 | 是 |
用户间过程的共享方便么? | 否 | 是 |
为什么发明这种技术? | 为了得到大的线性地址而不必购买更大的物理存储器 | 为了使程序和数据可以被划分成逻辑上独立的地址空间并且有助于共享和保护 |
最后
以上就是欢呼百合为你收集整理的虚拟内存(现代操作系统),分段虚拟内存分页页表加速分页过程针对大内存的页表分段的全部内容,希望文章能够帮你解决虚拟内存(现代操作系统),分段虚拟内存分页页表加速分页过程针对大内存的页表分段所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复