x86汇编语言——开启分页机制

分页机制

分页从功能上来说,就是用长度固定的页来代替长度不一定的段

简单分页模型

在单纯的分段模式下,线性地址就是物理地址
在这里插入图片描述
采用页式内存管理,就应当把4GB内存分成大小相同的页。页的最小单位是4KB,也就是4096字节,用十六进制数表示就是0x1000。因此,第1个页的物理地址是0x0000000,第2个页的物理地址是0x00001000,第3个页的物理地址是0x00002000,…,最后一个页的物理地址是0xFFFFF000.这样,可以将4GB内存划分为1048576(0x100000个页)
在这里插入图片描述

段管理机制对于 Intel处理器来说是最基本的,任何时候都无法关闭。也就是说,即使启用页管理功能,分段机制依然是起作用的,段部件也依然工作。
在这里插入图片描述
在分页模式下,操作系统可以创建一个为所有任务共用的4GB虚拟内存空间,也可以为每个任务创建独立的4GB虚拟内存空间,这都是可行的。当一个程序加载时,操作系统既在要左边的虚拟内存中分配段空间,又要在右边的物理内存中分配相应的页面。

页的最小尺寸是4KB,也就是4096字节。因此,8200字节的段,需要占用3个页面,其中最后一个页面只用了8个字节,其余都浪费着,但这无关紧要,如果允许页共享,多个段或多个程序可以用同一个页来存放各自的数据。

在分段之后,操作系统的任务是把段拆开,并分别映射到物理页。注意,段必须是连续的但不要求所分配的页都是连续的、挨在一起的。分配页面时,操作系统会搜索那些空闲的页,并分配给程序使用,所分配页面的总长度要大于等于段长度

4GB虚拟内存空间不可能用来保存任何数据,因为它是虚拟的,它只是用来指示内存的使用情况。当操作系统加载一个程序并创建为任务时,操作系统在虚拟内存空间寻找空闲的段,并映射到空闲的页。然后,到真正开始加载程序时,再把原本属于段的数据按页的尺寸拆开,分开写入对应的页中。

从段部件输出的是线性地址,或者叫虚拟地址。为了根据线性地址找到页的物理地址,操作系统必须维护一张表,把线性地址转换成物理地址,这是一个反过程(上一章中的段选择子索引的线性地址,成为了页的索引)

在页式内存管理中,页面的管理和分配是独立的,和分段以及段地址没有关系。线性地址,包括线性地址空间,和页面的分配机制没有关系。(程序和任务的加载仍旧按照段进行组织,但是实际的物理地址是将物理内存按页组织并分配给段使用,处理器完成了段到页的映射)

页存储使得应用程序的内存空间自动隔离了,段地址从原先的物理地址变为虚拟地址

在这里插入图片描述
我们知道,为了完成从虚拟地址(线性地址)到物理地址的转换,操作系统应当为每个任务准备一张页映射表。因为任务的虚拟地址空间为4GB,可以分出1048576个页,所以,映射表需要1048576个表项,用于存放页的物理地址。又因为每个表项占4字节,所以,映射表的总大小为4MB。

分页结构层次化的主要手段是不采用单一的映射表,取而代之的是页目录表和页表

在这里插入图片描述
这样的层次化分页结构是每个任务都拥有的,或者说,每个任务都有自己的页目录和页表
在处理器内部,有一个控制寄存器CR3,存放着当前任务页目录的物理地址,故又叫做页目录基址寄存器( Page Directory Base Register,PDBR)。

每个任务都有自己的任务状态段(TSS),它是任务的标志性结构,存放了和任务相关的各种数据,其中就包括了CR3寄存器域,存放了任务自己的页目录物理地址。当任务切换时,处理器切换到新任务开始执行,而CR3寄存器的内容也被更新,以指向新任务的页目录位置。相应地,页目录又指向一个个的页表,这就使得每个任务都只在自己的地址空间内运行。
在这里插入图片描述

地址变换

CR3寄存器给出了页目录的物理基地址;页目录给出了所有页表的物理地址,而每个页表给出了它所包含的页的物理地址。

举例:
假如某个任务加载后,操作系统根据它的实际情况,在其4GB虚拟地址空间里创建了一个段,段的起始地址为0x0080000,段界限值为0x5000,字节粒度。当该任务执行时,段寄存器DS指向该段。又假设执行了下面一条指令:
mov edx,[0x1050]
此时,段部件会输出线性地址0x00801050,在没有开启分页机制时,这就是要访问的物理内存地址,但现在开启了分页机制,所以,这是一个虚拟地址,要经过页部件的转换,才能得到物理地址如图16-8所示,处理器的页部件专门负责线性地址到物理地址的转换工作。它首先将段部件送来的32位线性地址截成3段,分别是高10位、中间的10位和低12位。高10位是页目录的索引,中间10位是页表的索引,低12位则作为页内偏移来用
在这里插入图片描述
当前任务页目录的物理地址在处理器的CR3寄存器中,假设它的内容为0x00005000,段管理部件输出的线性地址是0x00801050,其二进制的形式为0000 0000 1000 0000 0001 0000 0101 0000,高10位为0000000010也就是十六进制的0x002,它是页目录表内的索引,处理器将它乘以4(因为每个目录项为4字节),作为偏移量访问页目录。最终,处理器从物理地址00005008处取得页表的物理地址0x08001000。

线性地址的中间10位为二进制的0000000001即0x001,处理器要用它作为页表内的索引来取得页的物理地址。处理器将该索引值乘以4,作为偏移量访问页表。最终,处理器又从物理地址08001004处取得页的物理地址,这就是我们一直努力寻找的那个页

页的物理地址是0x0000C000,而线性地址的低12位是数据所在的页内偏移量。故处理器将它们相加,得到物理地址0x0000C050,这就是线性地址0x00801050所对应的物理地址,要访问的数据就在这里。

注意,这种变换不是无缘无故的,而是事先安排好的。当任务加载时,操作系统先创建虚拟的段,并根据段地址的高20位决定它要用到哪些页目录项和页表项。然后,寻找空闲的页,将原本应该写入段中的数据写到一个或者多个页中,并将页的物理地址填写到相应的页表项中。只有这样做了,当程序运行的时候,才能以相反的顺序进行地址变换,并找到正确的数据

开启分页机制

首先必须创建页目录和页表。每个任务都有自己的页目录和页表,内核也不例外,尽管它是为所有任务所共有的,但也包括作为任务而独立存在的部分,以执行必要的系统管理工作。因此,要想内核正常运行,必须创建它自己的页目录和页表。

麻烦在于,内核已经加载完毕,它的所有部分都已经位于内存中。

在一个理想的分页系统中,要加载程序,必须先搜索可用的页,并将它们与段对应起来。在这种情况下,段部件输出的线性地址和页部件输出的物理地址不同,是很自然的事,因为一切都发生在程序加载完毕、段和页已经有了确定的映射关系之后。在这种情况下,页功能开启之后,各方都能很好地适应。

然而,由于内核是在开启页功能之前加载的,段在内存中的位置已经固定。即使开启了页功能,线性地址也必须和物理地址相同才行。比如,在开启页功能之前,GDT在内存中的基地址是0x00007F00,它就是全局描述符表的物理地址,段部件输出的线性地址就是物理地址。在开启页功能之后,它还在那个内存位置,这就要求页部件输出的物理地址和段部件输出的线性地址相同。一句话,要求线性地址等于物理地址才行(此处的线性地址是段指明的地址,即将变为虚拟地址)。

注意,进入分页模式之后,所有东西的地址都成了线性地址(虚拟地址),包括GDT、LDT和TSS的地址。

由于本例中内核程序小,处于低端的1M,只需要将1MB内存特殊处理,将该部分内存的线性地址和经过页部件转换后的物理地址相同即可

页目录和页表(装着4KB页的物理地址)在内存中的位置没有限制,但是必须占用一个自然页,即低12位为0。内核只需要1个页表即4MB的大小就足够了。

在GDT和内核加载的区域之间,是一片空白。因此,我们可以将内核的页目录表放在物理地址0x00020000处;而把内核的第一个页表放在物理地址0x00021000处。此时,新的低端1MB内存布局就如图16-10所示了。
在这里插入图片描述
页目录和页表的格式

在这里插入图片描述由于页目录和页表都必须处于自然页中(4KB)故低12位不用,

在这里插入图片描述
页目录和页表都已创建,它们的表项也都安排妥当,第961、962行,将页目录表的物理基地址传送到控制寄存器CR3,也就是页目录表基地址寄存器PDBR,该寄存器的格式如图16-13所示

由于页目录表必须位于一个自然页内,故其物理基地址的低12位是全零,处理器的设计者认为既然如此,只登记它的高20位即可。低12位,除了PCD和PWT位外,都没有使用。
在这里插入图片描述现在就开启页功能。如图16-14所示,控制寄存器CR0的最高位,也就是位31,是PG(Page)位,用于开启或者关闭页功能。当该位清零时,页功能被关闭,从段部件来的线性地址就是物理地址;当它置位时,页功能开启。只能在保护模式下才能开启页功能,当PE位清零时(实模式),设置PG位将导致处理器产生一个异常中断。

在这里插入图片描述
(页目录和页表同样要占据一页)

每个任务都有自己的页目录表和页表,当任务创建时,它们一同被创建。当任务执行时,页部件使用它们访问任务自己的私有内存空间(页面)。但是,任务的页目录表和页表不能只包含任务的私有页面。如果不是这样,当任务调用内核服务时,或者换句话说,进入0特权级的全局地址空间执行时,地址转换将无法进行,因为任务的页目录表和页表里没有登记内核所占用的那些物理页面。

公平起见,全局地址空间占据着任务4GB地址空间的高2GB,对应的线性地址范围是0x8000000-0XFFFFFFFF;而局部地址空间则使用低2GB,对应的线性地址范围是0x00000000-0x7 FFFFFFF。

地址空间的分配必须在每个任务的页目录中体现,页目录的前半部分指向任务自己的页表;后半部分则指向内核的页表。否则的话,当转到内核中执行时,是无法完成地址转换的,因为找不到对应的目录项和页表项。在任何任务内,在任何时候,如果段部件发出的线性地址高于等于0x8000000指向和访问的就是全局地址空间,或者说内核。

为了修改页目录表PDT,需要访问它,知道它的物理地址。但是,当前已经开启了分页功能,在分页机制下,程序只能使用线性地址,访问内存必须先访问页目录和页表,通过它们转换之后的地址才是能够发送到内存芯片的物理地址,你自己知道页目录表的物理地址,这没有用。

或者,说得更清楚一点,你访问的是页目录表,但却还要通过页目录表进行地址转换之后才能访问内存中的页目录表。这有点自相矛盾,除非页目录表中有一个目录项能指向页目录表自己。否则,访问一个并未在页目录表和页表内登记的页,会引发处理器异常中断。

如图16-17所示,当前程序或者任务的页目录表,其物理基地址是由控制寄存器CR3指示的,仅高20位有效,是多少并不重要,可以假定为0x???000,段部件产生的线性地址是0xFFFFF200,其高10位的值是0x3FF,这个值乘以4,结果为0xFFC。这个值同CR3寄存器提供的页目录表物理地址相加,结果是0x???FFC,它就是页目录表内最后一个目录项的物理地址从此处取出一个双字,就是线性地址0xFFFFF200所对应页表的物理地址。有趣的是,在前面第936行,我们已经在该目录项内填写了页目录表的物理基地址。因此,该目录项所指向的页表正是当前的页目录表自己,这实际上是把页目录表当成页表来用。

在这里插入图片描述
访问页目录的过程,CR3中存储页目录的20位物理基地地址(低12位不用,占据整个页4KB),并且实在进入分页模式前指定的。通过CR3寻找到页目录。

进一步指定线性地址,取前10位,乘以4获取页目录的偏移地址,和CR3中的20位地址相加获得页目录项的内容(提前写好为页目录本身,即0X???000),取中间10位继续同上操作获取页表项地址(将页目录当成页表使用),仍然获得页目录自身,加上最后12位乘4获得偏移地址,并将此页表项改写为0x00021003(内核)

在这里插入图片描述
最终,页目录表内有两个目录项都指向同一个页表。不过,尽管指向的是同一个页表,这两个目录项所映射的线性地址是不一样的,旧表项依然对应着线性地址0x0000000-0x000FFFFF;新表项则对应着一个高端的地址范围0x80000000~x800FFFFF
(物理地址在进入分页模式前就已经指定为低端的1MB,但是通过改变页目录的索引方式,即将0X80000000仍指向目录项0x00021003,同时指向从0开始的页表项)
在这里插入图片描述
下次寻址时,0x80000000地址会得到低端1MB的首个页,就好像低端1MB被挪到了0x80000000地址处一样。原先的0x00000000寻址也能指向低端1MB内存空间。

可见进入分页模式后,物理地址的寻址是通过页目录和页表跳转实现的,和页目录页表自身所在的物理地址无关,由CR3指定(在进入分页模式前)

内核的页映射手动完成,此后的线性地址和页映射应当交由硬件完成,之所以如此是为了将任务的公共部分和局部部分分离
在这里插入图片描述
仅仅修改页目录表是没有用的,如果段部件给出的线性地址并不在0x800000以上,是没有用的。因此,必须修改与内核有关的段描述符,包括全局描述符表(GDT)自己的线性地址。开启页功能,除页目录表和页表的地址外,其他所有地址都是线性地址,只需要将其中的基地址部分加上0x80000000即可。比如GDT,原先的地址是0x00007500,现在则要改为0x80007E00

指令LGDT和SGDT分别用于加载和保存GDTR寄存器的内容

如果你的计算机上真的有4GB物理内存,那么,它可以划分为1048576(20)个页。如果每个表项占一字节,则需要1MB内存来创建该表。显然,这有些不划算。为了简单,可以使用位串来指示页的分配情况。如图16-21所示,可以用一个长的比特串,叫做页映射位串,来指示每个页的位置及分配情况。取决于你所拥有的实际内存数量(页数),该串最多可以有1048576比特,由于每字节包含8个比特,所以,共需要131072字节,也就是128KB。
在这里插入图片描述
比特在位串中的位置,决定了它所映射的页在哪里。如图16-21所示,位0对应的是物理地址为0x000000的页,位1对应的是物理地址为0x00001000的页,位2对应的是物理地址为0x00002000的页,……,最后一个比特对应的是最后一个页,即物理地址为0 xFFFFFO000的页。

除了用比特所在的位置决定页的位置外,比特的值决定了页的分配情况。当某比特为“0”时,表示它所对应的页未分配,是可以分配的空闲页;否则,就表明那个页已经被占用了,不能再分配给任何程序。

物理地址、逻辑地址、线性地址和虚拟地址

物理地址就是物理内存真正的地址

在实模式下,“段基址+段内偏移地址”经过段部件的处理,直接输出的就是物理地址, CPU 可以直接用此地址访问内存 。

在保护模式下,"段基址+段内偏移地址”称为线性地址(选择子),本质是个索引,通过这个索引便能在 GDT 中找到相应的段描述符若没有开启地址分页功能,此线性地址就被当作物理地址来用,可直接访问内存。

若开启了分页功能,线性地址实际上就是虚拟地址,虚拟地址要经过 CPU 页部件转换成具体的物理地址,这样 CPU 才能将其送上地址总线去访问内存。

无论在实模式或是保护模式下,段内偏移地址又称为有效地址,也称为逻辑地址,这是程序员可见的地址 。
在这里插入图片描述

参考
操作系统真象还原
x86汇编语言从实模式到保护模式

posted @ 2021-09-29 11:01  zhaojie10  阅读(13)  评论(0)    收藏  举报  来源