首页 理论教育 操作系统实现之路:分页机制应用

操作系统实现之路:分页机制应用

时间:2023-10-21 理论教育 版权反馈
【摘要】:分页机制是现代CPU的最基本特征。深入理解分页机制及其应用,是深入理解现代操作系统的基础,在本章中,我们对IA32 CPU的分页机制进行了描述,并列举了几个典型的应用。

操作系统实现之路:分页机制应用

分页机制是现代CPU的最基本特征。只有基于分页机制,一些现代操作系统的功能才能得以实现,比如虚拟内存、部分程序装入、按需内存分配、代码共享等。但分页机制也有一个缺陷,就是可能会导致效率下降,因为分页机制启用后,CPU访问内存需要经过一系列的查表操作,而这些查表操作需要进一步从内存中读取数据。因此,分页机制一般应用在通用操作系统(比如基于PC的操作系统)中,而在实时嵌入式操作系统中,一般很少使用。

IA32 CPU实现了完善的分页机制,在Hello China中也实现了基于IA32 CPU的分页功能,提供了简单的内存保护、高速缓存控制等功能。不过,当前版本Hello China实现的分页功能,是作为一个可选择模块实现的,在编译的时候,可以通过注释掉一个预定义选项来取消分页功能。

深入理解分页机制及其应用,是深入理解现代操作系统的基础,在本章中,我们对IA32 CPU的分页机制进行了描述,并列举了几个典型的应用。这些应用,都是现代通用操作系统中实现的最基本功能。

1.分页机制概述

IA32 CPU根据逻辑地址计算出线性地址,此时,若没有启用分页机制(通过CR0寄存器中的一个位判断),则直接把线性地址映射到物理地址,即直接把线性地址送到物理地址总线上完成一个内存访问动作。若启用了分页机制,CPU就不会直接根据线性地址访问物理地址了,而是根据线性地址查找页目录和页表,最终获得物理地址。图5-8示意了根据线性地址查找物理地址的过程(算法)。

978-7-111-41444-5-Chapter05-8.jpg

图5-8 地址转换算法

①32位对齐至4KB页边界

(1)CPU根据CR3寄存器的值获得页目录(第一级页表)所在的物理地址,从而获得页目录在内存中的位置。

(2)CPU根据线性地址的前10比特(22~31比特)形成一个索引值,根据这个索引值查找页目录,得到页表(二级页表)的位置(物理地址)。

(3)CPU再根据线性地址的中间10比特(12~21比特),形成页表内索引,根据这个索引查找步骤(2)获得的页表,从而获得页框的物理内存。

(4)CPU以线性地址的最后12比特(0~11比特)为偏移,以步骤(3)获取的物理地址为基地址,相加得到实际的物理地址。

可见,上述过程是较为复杂的,在最终获得正确的内存位置前,需要经过两次内存访问和两次查表操作,这显然是需要消耗时间的。为了提高效率,现代CPU都实现了一种TLB(后备转换存储器)的机制,即把部分页表和页目录缓存到CPU的片上缓存中,查找时,首先从TLB中查找,若查找失败,再启动内存查找,并更新TLB。

图5-9是IA32 CPU的页目录、页表和页框的结构。

978-7-111-41444-5-Chapter05-9.jpg

图5-9 页目录、页表和页框的关系

其中,页目录是由页目录项组成的,每个页目录项是一个32比特的结构,如图5-10所示。

978-7-111-41444-5-Chapter05-10.jpg

图5-10 页目录项的组成

其中,页表基地址部分指出了与该页目录项对应的页表的基地址,其他比特的含义见表5-1。详细的含义可参考Intel CPU的用户编程手册。

表5-1 页目录项各比特的含义

978-7-111-41444-5-Chapter05-11.jpg

线性地址的前10比特是页目录的索引,因此最大可以确定1024个页目录项,每个页目录项的大小是4B,整个页目录的大小是4KB。

同样,页表也是由页表项组成的,页表项的结构如图5-11所示。

978-7-111-41444-5-Chapter05-12.jpg

图5-11 页表项的组成

其中,页基地址给出了页框的物理地址(基地址),该字段是20bit,因此,每个页框是4KB对齐的,且其大小是4KB,其他标志的含义见表5-2。

表5-2 页表项各比特的含义

978-7-111-41444-5-Chapter05-13.jpg

由于线性地址的中间10比特是页表项索引,因此,一个页表项的大小也是4KB(1024个页表项,每个页表项4B)。32比特线性地址空间采用这种页目录和页表结构完整表示,需要内存的数量为

4KB(页目录)+1024×4KB=4100KB

其中,第一个4KB是页目录所占用的空间,1024×4KB则是所有页表占用的空间(一个页目录项对应一个页表)。但一般情况下,不需要把整个线性地址空间表示完,表示其中的一部分就可以满足应用需要了,因此,页目录和页表所占用的空间大大减少。

采用分页机制可以完成线性空间内任意地址与物理地址空间内任意地址的映射,而且页表项(或页目录项)提供了页面的访问属性,通过这种映射关系以及访问属性控制,可以很灵活地实现操作系统特性。在页表项和页目录项中存在一个P标志,该标志指出了对应的页框或页表框是否存在(位于内存中)。当试图访问一个P标志为0的页表或页框时,将引发一个异常。除了访问P标志为0的页面会引发异常以外,还有一些其他的组合情况也会引发异常。表5-3列举了会引发异常的情况以及访问方式。(www.xing528.com)

表5-3 特权模式和访问模式之间的组合

978-7-111-41444-5-Chapter05-14.jpg

上述情况中,没有考虑页表和页目录的对应标志不同的情况(在这种情况下会更加复杂,详细信息请参考Intel CPU的用户手册)。另外,按照Intel的软件编程手册中的描述,不同类型的CPU,其页面级保护方式也不一样,比如有的CPU,若当前模式是特权模式,也可以直接写入访问属性是Read-Only、页面特权模式是用户的页面。但在本书的描述中,这些特殊情况不会造成影响。

通过分页机制以及基于页面的保护机制,操作系统可以实现许多应用价值非常高的功能特性,比如内核保护、虚拟内存、按需内存分配、代码共享等,下面简单介绍几个比较典型的机制。

2.操作系统核心的保护

采用IA32 CPU提供的页面级保护机制,可以实现操作系统核心代码的保护功能,即防止应用程序破坏操作系统核心代码或数据,导致整个系统故障。假设一个操作系统采用保护平展段模式使用IA32 CPU的分段机制,即整个系统设置四个段:

(1)操作系统核心代码段,特权级为0(最高特权级),基地址为0x00000000,界限为4GB,即覆盖整个CPU的线性地址空间。

(2)操作系统核心数据段,特权级为0,覆盖整个CPU的线性地址空间。

(3)用户程序代码段,特权级为3(最低特权级),覆盖整个CPU的线性地址空间。

(4)用户程序数据段,特权级为3,覆盖整个CPU的线性地址空间。

上述四个段彼此重合,但访问的特权级不同。操作系统在实现的时候,把自己的代码和数据映射到线性地址的高端(比如E0000000H~FFFFFFFFH),低端的3GB线性地址空间供应用程序使用。这样,操作系统需要为这1GB空间建立页表(只为实际装入代码和数据的内存建立部分页表项即可),建立页表时,把页表的访问特权级设置为Supervisor。每创建一个进程(加载一个应用程序),操作系统就会根据应用程序的要求,把应用程序的代码、数据和堆栈映射到低端的3GB空间中,并为实际使用的线性地址建立页表项,这时候,把页表项的特权级设置为User,最后,把操作系统对应的页表项追加到应用程序页表上(不改变其特权级别),这样应用程序就可以“看到”操作系统的相关代码和数据了(但不能直接访问),如图5-12所示。

978-7-111-41444-5-Chapter05-15.jpg

图5-12 应用程序和操作系统地址空间

图中灰色的页表项是操作系统空间所对应的页表项。在创建应用程序的时候,把这部分页表项追加到应用程序的页表项当中。这样应用程序的页表项即包含操作系统的页表项,但应用程序用户空间的页表项的访问权限是用户级,而操作系统内核地址空间所对应的页表项的访问权限是系统级。

正常情况下,应用程序的运行只限制在其用户地址空间内,以特权级3(用户级)来运行。由于操作系统页表访问特权级是0(系统级),而目前的特权级是3,因此,一旦应用程序试图访问操作系统占用的内核空间,就会引发越权访问异常。

应用程序要访问操作系统提供的服务,只能通过IA32 CPU提供的门机制来提升当前的运行特权级(提升为0),进而可以顺利访问操作系统的代码或数据。

在这个模型中,每个进程都有独立的地址空间(每创建一个进程,都需要创建对应的页目录和页表),但操作系统所占用的线性地址空间却映射到所有应用程序地址空间中的相同位置。进程切换时,只需把CR3寄存器(页目录基地址寄存器)切换为新的进程,就实现了进程地址空间的切换。目前许多流行的操作系统,比如Linux、Windows都是按照这种方式实现的,或者与此方式类似。

3.虚拟内存的实现

现代计算机操作系统一般都实现了虚拟内存功能,即把永久性存储介质(比如硬盘)中的一部分空间开辟出来,虚拟成物理内存来使用,这样对应用程序来说,物理内存大大增加了,使得计算机系统能够运行实际大小比物理内存大得多的应用程序。

虚拟内存的实现也是建立在CPU的分页机制上的。在页表项中,有一个重要比特—P比特,该比特指明了页表项对应的页框是否存在于物理内存中。若P比特为0,则说明该页表项对应的页框不在内存中。这种情况下,若访问的线性地址刚好落到了这个不在物理内存中的页框内,就会引发一个缺页异常(Page-Fault)。在缺页异常处理程序中,操作系统会把该页表项对应的页框,从永久性存储介质中重新装入内存,并更改P标志为1,然后从异常处理程序中返回。返回后,原先引起内存访问异常的指令会被再次执行。这时候由于对应的页框已经被切换回内存中,且P标志被修改成了1,因此不会再次引发缺页异常。

图5-13简单地说明了这种情况。

978-7-111-41444-5-Chapter05-16.jpg

图5-13 虚拟内存的实现机制

应用程序的页表项中有的P比特为1,该页表项与唯一的物理内存页框对应;而有的P比特为0,表明与该页表项对应的物理内存框不在内存中,在虚拟内存的情况下,该页表项对应的页框位于块状存储介质中。在P比特为0的情况下,页表项的前31比特可以用来指明该页框在物理介质上的具体位置(或者可以理解为一个文件以页长度为单位的偏移),若应用程序(代码或数据)访问了该页框,就会引发缺页异常。在缺页异常处理程序中,操作系统会重新分配一块物理内存页框,并把所缺的页从磁盘中读回到这个物理页框,然后更改对应的页表项(若需要,还需更改页目录项),所有这些操作完成之后,操作系统从异常处理程序中返回,引起异常的程序得以继续执行。由于操作系统更改了页目录项,将不会再次引起异常。

需要补充的是,在页表项中,若P标志为0,则CPU对页表项的其他31比特是不作定义的。这样,剩余的31比特可以用来存储对应的页框在页面文件(虚拟内存在磁盘上对应的文件)中的位置。但是,P标志为0有时并不代表该页面被切换出了内存,还有一种情况是该页面尚未分配具体的物理内存(参考下面按需内存分配)。可以将剩余的31比特全部设置为0来表示这种情况。为了区分这两种情况(按需内存分配和虚拟内存),规定在虚拟内存的情况下,页表项的剩余31比特必须不能全为0,这样的一个后果就是页面文件的第一个页面大小的块将不被使用。

4.按需内存分配

按需内存分配是分页机制的另外一个应用,其作用是尽可能地把应用程序对内存的需求延迟到必须的时候才分配,以尽可能地提高内存利用率,尤其是应用程序申请内存的数量较大的时候。比如一个应用程序申请了1MB的内存,一般情况下,应用程序不可能一下子把1MB内存都使用完,为了提高内存使用效率,操作系统会给应用程序返回一个分配成功的信息,但实际上只为应用程序分配了有限数量的内存(比如几个页框),而应用程序请求的内存所对应的页表项,都已经创建,除了已经分配的几个页框,其他页表项的P比特都设置为0。

这样若应用程序访问P标志为0的页表项会导致一个缺页异常。在缺页异常处理程序中,操作系统会重新为应用程序分配内存,并更新页表项(需要的时候进一步更新页目录项)。这个过程与虚拟内存类似。为了区别这两种情况,采用了一个特殊的标识方法——页表项除了P标志之外的31比特全部为零时表示页表项对应一个未分配页框,否则表示页表项对应虚拟内存的情况。

5.代码共享机制

应用程序之间可以通过分页机制共享相同的代码,这样可使得共享代码在内存中仅仅保留一份副本,不用为每个应用程序进行单独加载,因此可减少内存空间的使用,提高内存使用效率。具体实现机制非常简单,即把共享的代码映射到共享它的应用程序地址空间的相同位置(通过页表机制可以实现这一点)。

6.部分装入机制

采用分页机制可以实现一种叫做“部分应用程序装入”的操作系统功能,用来运行实际大小比物理内存大得多的应用程序。为了实现这种功能,操作系统在装载应用程序时,会根据应用程序的代码段、数据段以及堆栈的需求建立全部的页表项以及页目录项,但只把应用程序代码和数据的前面很少部分(比如几个页面)装入物理内存,其他剩余部分仍然保留在硬盘上。装入内存的页面所对应的页表项和页目录项的P标志设置为1,其他的设置为0,这样应用程序就可以从开始位置运行了。运行过程中,若指令位置或数据位置超出了装入内存的部分,就会引发一个缺页异常,导致操作系统把程序的另外一部分代码和数据再装入内存,这样可使应用程序继续执行。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈