【Linux】虚拟内存——页表与分页

发布于:2025-04-22 ⋅ 阅读:(43) ⋅ 点赞:(0)

目录

一. 分页

二. 页表

2.1.页表项的结构

三. 与分页有关的工作

四. 缺页中断处理


把物理地址暴露给进程会带来下面几个严重问题。

        第一,如果用户程序可以寻址内存的每个字节,它们就可以很容易地(故意地或偶然地)破坏操作系统,从而使系统慢慢地停止运行(除非使用特殊的硬件进行保护,如IBM 360的锁键模式)。即使在只有一个用户进程运行的情况下,这个问题也是存在的。

        第二,使用这种模型,想要同时运行(如果只有一个CPU就轮流执行)多个程序是很困难的。在个人计算机上,同时打开几个程序是很常见的(一个文字处理器,一个邮件程序,一个网络浏览器),其中一个当前正在工作,其余的在按下鼠标的时候才会被激活。在系统中没有对物理内存的抽象的情况下,很难实现上述情景,因此,我们需要其他办法。

采用的方法称为虚拟内存(virtualmemory)。

        虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页或页面(page)。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。

        虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。

一. 分页

大部分虚拟内存系统中都使用一种称为分页(paging)的技术,我们现在就介绍这一技术。在任何一台计算机上,程序引用了一组内存地址。当程序执行指令

MOV REG, 1000

时,它把地址为1000的内存单元的内容复制到REG中(或者相反,这取决于计算机的型号)。地址可以通过索引、基址寄存器、段寄存器或其他方式产生。

        由程序产生的这些地址称为虚拟地址(virtual address),它们构成了一个虚拟地址空间(virtual addressspace)。

在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字;

而在使用虚拟内存的情况下,虚拟地址不是被直接送到内存总线上,而是被送到内存管理单元(Memory Management Unit,MMU),MMU把虚拟地址映射为物理内存地址,如图所示。

下图所示的简单例子说明了这种映射是如何工作的。

在这个例子中,有一台可以产生16位地址的计算机,地址范围从0到64K-1,且这些地址是虚拟地址。然而,这台计算机只有32KB的物理内存,因此,虽然可以编写64KB的程序,但它们却不能被完全调入内存运行。在磁盘上必须有一个最多64KB的程序核心映像的完整副本,以保证程序片段在需要时能被调入内存。

  • 虚拟地址空间按照固定大小划分成被称为页面(page)的若干单元。
  • 在物理内存中对应的单元称为页框(page frame)

页面和页框的大小通常是一样的,在本例中是4KB,但实际系统中的页面大小从512字节到1GB。对应于64KB的虚拟地址空间和32KB的物理内存,可得到16个虚拟页面和8个页框。

RAM和磁盘之间的交换总是以整个页面为单元进行的。

很多处理器根据操作系统认为适合的方式,支持对不同大小页面的混合使用和匹配。例如,x86-64架构的处理器支持4KB、2MB和1GB大小的页面,因此,可以将一组4KB大小的页面用于用户程序,将一个1GB大小的页面用于内核程序。稍后将介绍为什么有时候用一个较大的页面好于用一堆较小的页面。

        上图中的标记符号如下:标记0K~4K的范围表示该页的虚拟地址或物理地址是0~4095,4K~8K的范围表示地址4096~8191,等等。每一页包含了4096个地址,起始于4096的整数倍位置,结束于4096倍数缺1的位置。

当程序试图访问地址0时,例如执行下面这条指令

MOV REG,0

将虚拟地址0送到MMU。

MMU看到虚拟地址落在页面0(0~4095),根据其映射结果,这一页面对应的是页2(8192~12287),因此MMU把地址变换为8192,并把地址8192送到总线上。内存对MMU一无所知,它只看到一个读或写地址8192的请求并执行它。MMU从而有效地把所有从0~4095的虚拟地址映射到了8192~12 287的物理地址。

同样地,指令

MOV REG.8192

被有效地转换为:

MOV REG,24576

因为虚拟地址8192(在虚拟页面2中)被映射到物理地址24576(在物理页框6中)上。

第三个例子,虚拟地址20500在距虚拟页面5(拟地址20480~24575)起始地址20字节处,并且被映射到物理地址12288+20=12 308。通过恰当地设置MMU,可以把16个虚拟页面映射到8个页框中的任何一个。但是这并没有解决虚拟地址空间比物理内存大的问题。在图3-9中只有8个物理页框,于是只有8个虚拟页面被映射到了物理内存中,在图3-9中用叉号表示的其他页面并没有被映射。

在实际的硬件中,用一个“在/不在”位(presentabsentbit)记录页面在内存中的实际存在情况当程序访问了一个未映射的页面,例如执行指令

MOV REG,32780

将会发生什么情况呢?

虚拟页面8(从32768开始)的第12个字节所对应的物理地址是什么呢?

MMU注意到该页面没有被映射(在图中用号表示),于是使CPU陷入到操作系统,这个陷阱称为缺页中断或缺页错误(page fault)。

操作系统找到一个很少使用的页框且把它的内容写入磁盘(如果它不在磁盘上)。随后把需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷阱的指令。

例如,如果操作系统决定放弃页框1,那么它将把虚拟页面8装入物理地址4096,并对MMU映射做两处修改。首先,它要将虚拟页面1的表项标记为未映射,使以后任何对虚拟地址4096~8191的访问都导致陷阱。随后把虚拟页面8的表项的叉号改为1,因此在引起陷阱的指令重新启动时,它将把虚拟地址32780映射为物理地址4108(4096+12)。

        下面查看一下MMU的内部结构以便了解它是怎么工作的,以及了解为什么我们选用的页面大小都是2的整数次幂。

在图3-10中可以看到一个虚拟地址的例子,虚拟地址8196(二进制是0010000000000100)用图3-9所示的MMU映射机制进行映射,输入的16位虚拟地址被分为4位的页号和12位的偏移量。4位的页号可以表示16个页面,12位的偏移可以为一页内的全部4096个字节编址。
        可用页号作为页表(pagetable)的索引,以得出对应于该虚拟页面的页框号。如果“在/不在位是0,则将引起一个操作系统陷阱。如果该位是1,则将在页表中查到的页框号复制到输出寄存器的高3位中,再加上输入虚拟地址中的低12位偏移量。

如此就构成了15位的物理地址。输出寄存器的内容随即被作为物理地址送到内存总线。

二. 页表

        作为一种最简单的实现,虚拟地址到物理地址的映射可以概括如下:虚拟地址被分成虚拟页号(高位部分)和偏移量(低位部分)两部分。

        例如,对于16位地址和4KB的页面大小,高4位可以指定16个虚拟页面中的一页,而低12位接着确定了所选页面中的字节偏移量(0~4095)。但是使用3或者5或者其他位数拆分虚拟地址也是可行的。不同的划分对应不同的页面大小。

        虚拟页号可用作页表的索引,以找到该虚拟页面对应的页表项。由页表项可以找到页框号(如果有的话)。然后把页框号拼接到偏移量的高位端,以替换掉虚拟页号,形成送往内存的物理地址。

页表的目的是把虚拟页面映射为页框。从数学角度说,页表是一个函数,它的参数是虚拟页号,结果是物理页框号。通过这个的数可以把虎拟地址中的虚拟页面域替换成页框域,从而形成物理地址。

2.1.页表项的结构


下面将讨论单个页表项的细节。

页表项的结构是与机器密切相关的,但不同机器的页表项存储的信息都大致相同。图3-11中给出了页表项的一个例子。不同计算机的页表项大小可能不一样,但32位是一个常用的大小。

最重要的域是页框号。毕竟页映射的目的是找到这个值。

其次是“在/不在”位。这一位是1时表示该表项是有效的,可以使用;如果是0,则表示该表项对应的虚拟页面现在不在内存中,访问该页面会引起一个缺页中断。

保护(protection)位指出一个页允许什么类型的访问。最简单的形式是这个域只有一位,0表示读写,1表示只读。一个更先进的方法是使用三位,各位分别对应是否启用读、写、执行该页面。

为了记录页面的使用状况,引入了修改(modified)位和访问(referenced)位。在写人一页时由硬件自动设置修改位。该位在操作系统重新分配页框时是非常有用的。如果一个页面已经被修改过(即它是“脏”的),则必须把它写回磁盘。如果一个页面没有被修改过(即它是“干净”的),则只简单地把它丢弃就可以了,因为它在磁盘上的副本仍然是有效的。这一位有时也被称为脏位(dirtybit),因为它反映了该页面的状态。

不论是读还是写,系统都会在该页面被访问时设置访问位。它的值被用来帮助操作系统在发生缺页中断时选择要被淘汰的页面。不再使用的页面要比正在使用的页面更适合淘汰。这一位在即将讨论的很多页面置换算法中都会起到重要的作用。

最后一位用于禁止该页面被高速缓存。对那些映射到设备寄存器而不是常规内存的页面而言,这个特性是非常重要的。假如操作系统正在紧张地循环等待某个I/O设备对它刚发出的命令作出响应,保证硬件是不断地从设备中读取数据而不是访问一个旧的被高速缓存的副本是非常重要的。通过这一位可以禁止高速缓存。具有独立的IO空间而不使用内存映射O的机器不需要这一位。

应该注意的是,若某个页面不在内存中,用于保存该页面的磁盘地址不是页表的组成部分。原因很简单,页表只保存把虚拟地址转换为物理地址时硬件所需要的信息。操作系统在处理缺页中断时需要把该页面的磁盘地址等信息保存在操作系统内部的软件表格中。硬件不需要它。

在深入到更多应用实现问题之前,值得再次强调的是:虚拟内存本质上是用来创造一个新的抽象概-地址空间,这个概念是对物理内存的抽象,类似于进程是对物理处理器(CPU)的抽象。虚拟内存的实现,是将虚拟地址空间分解成页,并将每一页映射到物理内存的某个页框或者(暂时)解除映射。

三. 与分页有关的工作

         操作系统要在下面的四段时间里做与分页相关的工作:进程创建时,进程执行时,缺页中断时和进程终止时。下面将分别对这四个时期进行简短的分析。

        当在分页系统中创建一个新进程时,操作系统要确定程序和数据在初始时有多大,并为它们创建一个页表。操作系统还要在内存中为页表分配空间并对其进行初始化。当进程被换出时,页表不需要驻留在内存中,但当进程运行时,它必须在内存中。另外,操作系统要在磁盘交换区中分配空间,以便在一个进程换出时在磁盘上有放置此进程的空间。操作系统还要用程序正文和数据对交换区进行初始化,这样当新进程发生缺页中断时,可以调入需要的页面。某些系统直接从磁盘上的可执行文件对程序正文进行分页,以节省磁盘空间和初始化时间。最后,操作系统必须把有关页表和磁盘交换区的信息存储在进程表中。

        当调度一个进程执行时,必须为新进程重置MMU,刷新TLB,以清除以前的进程遗留的痕迹。新进程的页表必须成为当前页表,通常可以通过复制该页表或者把一个指向它的指针放进某个硬件寄存器来完成。有时,在进程初始化时可以把进程的部分或者全部页面装人内存中以减少缺页中断的发生,例如,PC(程序计数器)所指的页面肯定是需要的。

        当缺页中断发生时,操作系统必须通过读硬件寄存器来确定是哪个虚拟地址造成了缺页中断。通过该信息,它要计算需要哪个页面,并在磁盘上对该页面进行定位。它必须找到合适的页框来存放新页面必要时还要置换老的页面,然后把所需的页面读入页框。最后,还要回退程序计数器,使程序计数器指向引起缺页中断的指令,并重新执行该指令。

        当进程退出的时候,操作系统必须释放进程的页表、页面和页面在硬盘上所占用的空间。如果某些页面是与其他进程共享的,当最后一个使用它们的进程终止的时候,才可以释放内存和磁盘上的页面。

四. 缺页中断处理

现在终于可以讨论缺页中断发生的细节了。缺页中断发生时的事件顺序如下:

  • 1)硬件陷入内核,在堆栈中保存程序计数器。大多数机器将当前指令的各种状态信息保存在特殊的CPU寄存器中。
  • 2)启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被操作系统破坏。这个例程将操
  • 作系统作为一个函数来调用。3)当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器包含了这一信息,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析这条指令,看看它在缺页中断时正在做什么。
  • 4)一旦知道了发生缺页中断的虚拟地址,操作系统检查这个地址是否有效,并检查存取与保护是否一致。如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框。如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。
  • 5)如果选择的页框“脏”了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
  • 6)一旦页框“干净”后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入。该页面正在被装入时,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一个用户进程运行。
  • 7)当磁盘中断发生时,表明该页已经被装入,页表已经更新可以反映它的位置,页框也被标记为正常状态。
  • 8)恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令,
  • 9)调度引发缺页中断的进程,操作系统返回调用它的汇编语言例程。
  • 10)该例程恢复寄存器和其他状态信息,返回到用户空间继续执行,就好像缺页中断没有发生过一样。


网站公告

今日签到

点亮在社区的每一天
去签到