虚拟内存分页(virtual memory paging)
参考:http://hi.baidu.com/zeyu203/item/c58cd9009d820b98a3df439f
虚拟内存 -- 分页机制
〇、概述
尽管基址寄存器和界限寄存器可以用于创建地址空间的抽象,还有另一个问题需要解决:管理软件的膨胀,虽然存储器容量增长快速,但是软件大小增长更快,需要运行的程序往往大到内存无法容纳,由于磁盘传输速度太慢,所以交换技术并不是一个很好的解决方案
最早解决这个问题的方案被称为“覆盖”,将软件分为多个模块,由覆盖管理模块进行装载,应用程序员需要将软件设计成多个模块化的片段,这对于应用程序员来说是非常费事的设计,且很容易出错
1961 年,有人提出了虚拟内存的解决方案:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称为一个页或页面,每一页有连续的地址范围,这些页被映 射到物理内存,但是并不是所有的页都必须在内存中才能运行程序,当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射,当程序引用到不 在物理内存中的地址空间时,有操作系统负责将相应的部分载入内存并重新执行指令,在这段等待载入的时间内,CPU可以被交给另一个进程使用
一、分页
大部分虚拟内存系统都使用一种称为“分页”的技术,在任何一台计算机上,程序都可以执行:
MOV REG,1000
这 里,1000代表的地址是通过索引、基址寄存器、段寄存器或其他方式产生的,因此被称为“虚拟地址”,他们构成了一个“虚拟地址空间”,在没有虚拟内存的 计算机上,系统直接将虚拟地址送到内存总线操作与之相同的物理地址,而在使用虚拟内存的情况下,虚拟地址被送到内存管理单元(MMU),MMU把虚拟地址 映射为物理地址,如图所示:
下面的例子说明了这种映射是如何工作的,在这个例子中,进程地址空间有64KB,但是实际的物理内存只有32KB,因此程序不能被完全调入内存执行:
虚拟地址空间按照固定的大小划分为若干个单元,每个单元被称为一个“页面”,在物理内存中对应的单元成为页框(page frame),页面和页框的大小通常是一样的,如上例中是4KB,RAM和磁盘之间的交换总是以整个页面为单元进行的
MMU在程序请求内存时将虚拟地址映射成物理地址,并加载相应的页面到页框,对于应用程序来说,他并不关心实际的物理地址,而对于内存,他收到的指令所包含的地址都是经过MMU转换后的物理地址,因此他并不知道MMU的存在
当程序所需要的内存超过物理内存时,MMU会注意到该页面没有被映射,于是使CPU陷入到操作系统,称为“缺页中断”,操作系统找到一个页框加载这一页面然后修改映射关系,即可执行程序的相应指令
操作系统需要维护一个虚拟页表,这个表中标记了一个页面是否被映射到页框中,如果一个页面在页框中长期没有得到执行,操作系统可以选择将该页面的虚拟页表项标记为未映射然后将他保存到磁盘中,以便让这个页框被其他页面利用
另 外,有一个小故事,虽然Intel 8086处理器不支持虚拟内存,然而一些公司依然设计出了包含未作任何改动的8086 CPU 的分页系统,他们制作 了MMU,并连接在CPU与地址总线之间,这样从处理器进入MMU的地址全部被视为虚拟地址,并被转换为物理地址,然后被送到地址总线,映射到内存中
二、页表
作为一种最简单的实现,虚拟地址到物理地址的映射可以概括如下:
虚拟地址被分成虚拟页号(高位)和偏移量(低位),如对于16位地址和4K的页面大小,高4位可以指定16个虚拟页面中的一页,而低12位接着确定了所选页面中的字节偏移量,当然,3或者5或者其他位数拆分虚拟地址都是可行的,不同的划分对应不同的页面大小
虚拟页号可以用作页表的索引,以找到该虚拟页面对应的页表项,由页表项可以找到页框号,然后把页框号拼接到偏移量的高位端,就可以形成物理地址
-
页表项结构
页表项的结构是与机器密切相关的,但是不同机器的页表项存储的信息都大致相同,不同机器的页表项可能不同,但一般使用32位
-
页表项中最重要的域是页框号,毕竟映射的目的是找到这个域
-
在不在位如果为0则说明该表项对应的虚拟页面不在内存中,此时如果访问他就会陷入缺页中断,否则则说明该表项可以使用
-
保护位指出一个页允许什么类型的访问,如只读、读/写、执行等
-
修 改和访问位用于记录页面的使用情况,在写入一页时由硬件自动设置修改位,该位在操作系统重新分配页框时是非常有用的,如果一个页面已经被修改过则必须把他 写回到磁盘上,如果一个页面没有被修改过,只要把他丢弃到就可以了,因为他在磁盘的副本仍然有效,这一位也被称为脏位(dirty bit),如果没有被 修改过他就被称为“干净”的,否则成为“脏”的,而这一位反映了该页面的状态
-
无论是读还是写,计算机都会设置访问位,他的值用来帮助操作系统在发生缺页中断时选择要被淘汰的页面,不再使用的页面要比正在使用的页面更加适合淘汰
-
最 后一位“高速缓存禁止位”用于禁止该页面被高速缓存,对那些映射到设备寄存器而不是常规内存的页面而言,这个特性是非常重要的,假如操作系统正在紧张地循 环等待某个IO设备对他刚发出的命令作出响应,保证硬件是不断从设备中读取数据而不是访问一个旧的被高速缓存的副本是非常重要的,通过这一位可以禁止高速 缓存,但是具有独立的IO空间而不是使用内存映射IO的机器不需要这一位
页表只保存把虚拟地址转换为物理地址时硬件所需要的信息,操作系统在处理缺页中断时需要把该页面磁盘地址等信息保存在操作系统内部的软件表格中
三、快速分页过程
在任何分页系统中,都需要考虑下面的两个问题:
-
虚拟地址到物理地址的映射必须非常快
-
如果虚拟地址空间很大,页表也会很大
第一个问题是由于每次访问内存,都需要进行虚拟地址到物理地址的映射,所有的指令最终都必须来自内存,并且很多指令也会访问内存中的操作数,因此,每条指令常常都要进行一两次甚至更多的页表访问,因此页表查询速度成为了指令执行速度的一个瓶颈
第二个问题来自现代计算机至少使用32位的虚拟地址,如果每个页长4KB,那么就需要100万页,而64位地址空间简直多到不可想象,而且每个进程都需要自己的页表(因为他有自己的虚拟地址空间)
总之,对大而快速的页映射的需求成为了构建计算机的重要约束
解决这个问题的最简单的设计是使用一组寄存器组成单一页表,这样不必再为页表而访问内存,但是在页表很大时,代价会很高昂,而且每一次上下文切换都必须装在整个页表,会降低性能
另一种方案是将整个页表都放到内存中,所需的硬件仅仅是一个指向内存中页表起始地址的寄存器,这个方案的优势在于上下文切换进行映射时,只需要重新装入一个寄存器,但是这种做法在每次执行指令时,都需要访问内存,以完成页表项的读入,性能会很差
1、转换检测缓冲区
对于一个简单的指令:从一个寄存器将数据复制到另一个寄存器,在实地址模式下,只需要访问一次内存以便中转数据即可,但是在分页的情况下,因为需要访问页表而产生更多次的内存访问,所以对性能的降低会很明显
由于大多数应用程序总是对少量页面进行多次访问,所以只有很少的页表项会被反复读取,而其他页表项则很少被访问,因此提出了“转换检测缓冲区”的方案
这 种解决方案是为计算机设置一个小型的硬件设备,将虚拟地址直接映射到物理地址,而不必再访问页表,这种设备称为“转换检测缓冲区”(TLB),有时也称为 “相连存储器”,他通常在MMU中,包含少量的表项,在实际中很少会超过64个,每个表项记录了一个页面的相关信息,这些域与也表中的域是一一对应的,如 下图所示:
当一个虚拟地址需要在MMU中进行转换时,硬件首先通过将该虚拟页号与TLB中的所有表项进行匹配,如果没有匹配成功则进行正常的页表查询,并将该项插入TLB
TLB失效分为“硬失效”和“软失效”,软失效指的是页面访问在内存中而不在LTB中,此时只要更新一下LTB即可,硬失效指的是页面本身也不在内存中,这时就需要一次磁盘存取以装入该页面,硬失效一般是软失效处理时间的百万倍
2、软件TLB管理
此前,对TLB的管理和TLB的失效处理都完全由MMU硬件来实现,只有在内存中没有找到某个页面时,才会陷入操作系统的“缺页中断”
但是,许多现代RISC极其将几乎所有的页面管理都在软件中实现,TLB表项被操作系统显式地装载,出现问题由操作系统来解决
软件管理实现可以获得一个非常简单的MMU,这样CPU就可以有更大的空间进行其他方面的改进
四、针对大内存的页表
在原有内存页表的方案上,使用TLB可以用来加快虚拟地址到物理地址的转换,但是还有另一个问题,那就是怎样处理巨大的虚拟地址空间,下面提供两种解决方案
1、多级页表
第一种方法是采用多级页表,如图所示是一个简单的例子
多级列表的一个优点在于可以避免把全部页表一直保存在内存中,比如一个实际只是用12MB的内存的进程的大量的地址空间都是没有使用的空闲区
通过索引顶级页表得到的表项中得到二级页表的地址或页框号,然后从二级页表中查找到的页框号与顶级页表中的偏移量结合形成物理地址
多级列表的级别越多,灵活性就越大,但是当页表超过三级以后会带来更大的复杂性,往往是设计者不愿意看到的
2、倒排页表
随着64为计算机的普及,多级也表也不再能够满足需要,因为对于2^64的内存需要2^52个表项,仅仅存储页表就需要3000万GB的空间
新的解决方案之一就是倒排页表,在这种设计中,每一个页框有一个表项,而不是每一个虚拟页面,使用倒排索引,即根据 物理地址来找逻辑地址,而不是根据逻辑地址来找物理地址,如下图所示
虽然倒排页表节省了大量的空间,但是他也有严重的不足:从虚拟地址到物理地址的转换会变得很困难,当进程N访问虚拟页面P时,硬件不再能够通过把P当作指向页表的一个索引来查找物理页框,取而代之的是,他必须搜索整个倒排页表来查找某一个表项,这是非常费时的操作
解决这个问题的方法还是使用TLB,如果TLB能够记录所有频繁使用的页面,地址转换就可能很快,但是当发生TLB失效时,还是需要用软件搜索整个倒排页表
一个可行的实现方案是使用如上图的哈希表,这会大大提高映射速度