虚拟内存
前言
在8086/80186时还没有操作系统,把它称之为实地址模式。
那么怎么将地址表示出来呢?
比如:数据寄存器DS中的值是16位,要转换成20位的地址。怎么转换呢?
只要左移四位,则低四位变为0了,然后再加上IP寄存器中的偏移值,就是物理地址。
DS << 4 + IP (即偏移值)= 物理地址 没有操作系统则它的空间就称为物理空间(上图的空间)
DS(基地址) IP(偏移地址、偏移量、逻辑地址)
IP寄存器中最多存16位,则偏移量最多为2^16=64K.
因为起始位置必须是16的倍数,则整个段大小在16B-64K之间,则它的真实大小就不确定,就可能导致如果别人恶意修改这个IP寄存器中的值,则段的大小就会发生改变,进而会访问无法访问的空间。
因此为了解决这样的问题,所以我们就需要保存记录 基地址 段大小 以及 访问权限 。
所以在有了操作系统以后,就添加了 GDTR(全局段描述表寄存器) 和 LDTR(局部段描述符表寄存器)。(而因为要满足向上兼容原则,所以上述寄存器不改变)
我们这里先分析GDTR,是在内存存储的全局段描述表(相当于数组)
既然GDTR都已经存储了这些信息,那么DS、CS、SS(ES)这些寄存器做什么呢?
存储 段描述符表的下标 段描述符表的类别 以及权限位
当操作系统启动时,默认占据12个描述符表项,而用户进程最多只能用8192-12=8180个描述符表项。
现在继续说当有了GTDR和LDTR之后的保护模式,比如:
GDTR[DS>>3].BaseAddr + IP (IP<=SIZE) = 线性地址
我们先看等号左边:
DS先右移三位,表示GDTR的一个下标,来访问全局描述符表里面的表项中的基地址,再加上IP,而IP必须要小于等于段大小(防止越界访问)。
再看等号右边:
- 如果未开启分页机制,线性地址就直接到物理地址。
- 如果开启了分页机制,线性地址(即IP中表示的逻辑地址)就等于虚拟地址。
那么如何检查是否开启了分页机制呢?
寄存器CR0的最高位PG位为0则表示未开启分页机制,为1则表示开启分页机制
而如果是虚拟地址时,知道了CR3中记录的页目录的起始地址才能完成页面映射。
如果基地址等于0,则IP就等于物理地址(未开启时)或者 虚拟地址(开启时)
而对于当代的操作系统,基地址全部设置为0,那么为什么还保留呢?是因为要维持向上兼容的原则。
前面简单了解了分页、分段的概念并分析了各自的缺点,本篇开始讨论虚拟内存。
分页分段的两个特点:
- 进程中所有的存储器访问都是逻辑地址,这些逻辑地址在运行时动态地被转换成物理地址。这意味着一个进程可以被换入或换出主存,使得进程在执行过程中的不同时刻,占据主存中的不同区域。
- 一个进程可以被划分成许多块(页和段),在执行过程中,这些块不需要连续地位与主存之中。动态运行时地址转换和页表或段表的使用使这一点成为可能。
满足上述特点时,则在进程执行过程中,就不需要所有块都在主存中。
- 进程中任何时候都在主存中的部分被定义为常驻集。当进程执行时访问常驻集中的单元,就可以顺利执行;通过使用段表或页表,处理器总是可以确定是否如此。
- 如果处理器需要访问一个不在主存中的逻辑地址,就会产生一个中断,说明产生了内存访问故障。而操作系统就把被中断的进程置于阻塞状态,并取得控制。为了继续执行继承,操作系统要把包含引发故障的逻辑地址的进程快取进主存。(为此,操作系统产生一个磁盘I/O读请求。产生I/O请求后,在执行磁盘I/O期间操作系统可以分派另一个进程运行。一旦需要的块被取进主存,则产生一个I/O中断,控制被交回操作系统,而操作系统把由于缺少该块而被阻塞的进程置回就绪态。)
进程在执行中一旦存在没有装入主存中的块就不得不中断,这样就产生了效率的质疑。暂且不考虑保证效率的问题,先考虑新策略的实现问题。有两种实现方法可以提高系统的使用率:
- 在主存中保留多个进程。由于对任何特定的进程都装入它的某些块,因此就有足够的空间来放置更多的进程。这样,在任何时刻这些进程中都能至少有一个就绪状态,于是处理器得到了更高效的利用。
- 进程可以比主存的全部空间还大。程序占用的主存空间的大小时程序设计中的最大限制之一。如果没有这种方案,则程序员就必须清除有多少主存空间可用。而如果编写的程序太大,程序员就必须设计出能把程序分成块的方法,这些块可以按某种放置策略分别加载。通过基于分页或分段的虚拟内存,这项工作可以由操作系统和硬件完成。对于程序员而言,他所处理的是一个巨大的主存,大小与磁盘存储器有关。操作系统在需要时,自动把进程块装入主存。
由于一个进程只能在主存中执行,因此这个存储器称为实存储器,简称实存。但是程序员或用户感觉到的是一个更大的内存,通常它被分配在磁盘上,这称为虚拟内存,简称虚存。
1.局部性和虚拟内存
在任何时候,任何一个进程都只有一部分在主存中,只有需要时操作系统才产生中断进行换入换出。
但是操作系统必须能够很"聪明"的管理这个方案。而一旦这个块在使用前刚好却被换出了,当需要时操作系统又不得不很快再次将他取回来。太多这类操作就会导致一种系统抖动:操作系统一直用于换入换出块,而不是执行命令。
因此,避免系统抖动成为了重要的研究领域,就出现了许多复杂但有效的算法。从本质上看,这些算法都是操作系统试图根据最近的历史来猜测在不远的将来可能访问的块。
这类推理基于局部性原理。局部性原理描述了一个进程中程序和数据引用的集簇倾向。因此假设在很短的时间内仅需要访问进程的一部分块是合理的;同时,还得预测将来可能访问的块从而避免系统抖动。
局部性原理说明了虚拟内存的可行性。
- 首先,必须要有对所采用的分页分段方案的硬件支持。
- 操作系统必须有管理页或段在主存和辅助存储器(辅存)之间移动的软件。
2.分页
在简单分页时,每个进程都有自己的页表,当它的所有页都装入到主存,页表被创建并被装入主存。页表项包含有与主存中的页对应的帧号。当考虑基于分页的虚拟内存方案时也同样需要页表,并且通每个进程都有一个惟一的页表,但这时页表项变得更复杂,如图8.2(a)所示。
由于一个进程可能只有一部分页在主存中,因而每个页表项需要一位(P)来表示它所对应的页是否在主存中。如果这一位表示该页在主存中,则这个页表项还包括该页的帧号。
页表项中所需要的另一个控制位是修改位(M),表示相应页的内存从上一次装入主存到现在是否已经改变。如果没有改变,则当需要把该页换出时,不需要用帧中的内容更新该页。
页表结构
从存储器中读取一个字的基本机制包括使用页表从虚拟地址到物理地址的转换。虚拟地址又称为逻辑地址,由页号和偏移量组成,而物理地址由帧号和偏移量组成。由于页表的长度可以基于进程的长度而变化,因而不能期望在寄存器中保存它,它必须在主存中且可以访问到。图8.3给出了一种硬件实现。当一个特定进程正在运行时,一个寄存器保存该进程页表的起始地址。虚拟地址的页号用于检索页表、查找相应的帧号,并与虚拟地址的偏移量组合起来产生需要的实地址。
32位地址下,4GB(232)的虚拟地址空间,每页为4KB(212),则就需要220个页。而如果每个页用4个字节的页表项映射,则就需要4MB(222)的内存空间,从而形成了用户页表。这个由210个页表项组成的巨大用户页表可以保留在虚存中,由一个包含210个页表项的根页表映射,根页表占4KB(212)的主存。(页表也像页一样服从分页管理)
3.分段
分段允许程序员把内存看作是有多个地址空间或段组成的,段的大小是不等的,并且是动态的。
优点:
- 简化不断增长的数据结构的处理。如果程序员事先不知道一个特定的数据结构会变得多大,除非使用动态的段大小,否则必须对其大小进行猜测。而对于段式虚存,这个数据结构可以分配到它自己的段,需要时操作系统可以扩大或缩小这个段。如果需要被扩大的段在主存中。并且主存已经没有足够的空间,则操作系统可能把这个段移到主存中的一个更大区域(如果可以得到),或把它换出。对于后一种情况,被扩大的段将在下一次有机会时被换回。
- 允许程序独立地改变或重新编译,而不要求整个程序集合重新链接和重新加载。同样,这也是使用多个段实现的。
- 有助于进程间的共享。程序员可以在段中放置一个使用工具程序或一个有用的数据表,供其他进程访问。
- 有助于保护。由于一个段可以被构造成包含一个明确定义的程序或数据集,因而程序员或系统管理员可以更方便地指定访问权限。
组织
在简单分段时,每个进程都有自己的段表,当它的所有段都装入主存时,为该进程创建一个段表并装入主存。每个段表项包含相应段在主存中的起始地址和段的长度。基于分段的虚拟内存方案仍然需要段表,并且每个进程都有唯一的段表。如图8.2(b)所示。
- 由于一个进程可能只有一部分段在主存中,因而每个段表项中需要一位表明对应的段是否在主存中。如果这一位表明该段在主存中,则这个项还包括该段的起始地址和长度。
- 段表项中需要的另一个控制位是修改位,用于表明相应的段从上一次被装入到内存到目前为止其内容是否被改变。如果没有改变把该段换出时就不需要写回。还可能需要其他控制位,例如若要在段级来管理保护或共享,则需要具有这种目的的位。
从存储器中读出一个字的基本机制涉及到使用段表来将段号和偏移量组成的虚拟地址(或逻辑地址)转换成物理地址。根据进程的大小,段表长度可变,而无法在存储器中保存,因此访问段表时它必须在主存中。
图8.12表明了一种硬件实现。当一个特定的进程正在运行时,有一个寄存器为该进程保存段表的起始地址。虚拟地址中的段号用于检索这个表,并查找该段起点的相应主存地址。这个地址加上虚拟地址中的偏移量,产生了需要的实地址。
4.分段和分页的结合
在分页和分段相结合的系统中,用户的地址空间被程序员划分成许多段。每个段一次划分成许多固定大小的页,页的长度等于主存中的帧的大小。如果某一段的长度小于一页,则该段只占据一页。从程序员角度看,逻辑地址仍然由段号和段偏移量组成;从系统的角度看,段偏移量可看作是指定段中的一个页号和页偏移量。
图8.13给出了支持分页和分段相结合的结构。每个进程使用一个段表和一些页表,并且每个进程段使用一个页表。当一个特定的进程运行时,使用一个寄存器记录该进程段表的起始地址。对每一个虚拟地址,处理器使用段号部分来检索进程段表以寻找该段的页表。然后虚拟地址的页号部分用于检索页表并查找相应的帧号。这结合了虚拟地址的偏移部分来产生需要的实地址。
段表项包含段的长度,还包含一个基域,这个基域现在指向一个页表,这时不需要存在位和修改位,因为它们相关的问题将在页一级处理(页一级处理与分页相同)。此外可能需要用于基于共享和保护目的的其他控制位。