<一>存储器分页管理机制
80386 开始支持存储器分页管理机制。分页机制是存储器管理机制的第3二部分。段管理机制实现虚拟地址(由段和偏移构成的逻辑地址)到线性地址的转换,分页管理机制实现线性地址到物理地址的转换。如果不启用分页管理机制,那么线性地址就是物理地址。本文将介绍80386的存储器分页管理机制和线性地址如何转换为物理地址。
在保护模式下,控制寄存器 CR0中的最高位 PG位控制分页管理机制是否生效。如果 PG=1,分页机制生效,把线性地址转换为物理地址。如果PG=0,分页机制无效,线性地址就直接作为物理地址。必须注意,只有在保护方式下分页机制才可能生效。只有在保证使PE 位为 1的前提下,才能够使 PG 位为1,否则将引起通用保护故障。
分页机制把线性地址空间和物理地址空间分别划分为大小相同的块。这样的块称之为页。通过在线性地址空间的页与物理地址空间的页之间建立的映射,分页机制实现线性地址到物理地址的转换。线性地址空间的页与物理地址空间的页之间的映射可根据需要而确定,可根据需要而改变。线性地址空间的任何一页,可以映射为物理地址空间中的任何一页。
采用分页管理机制实现线性地址到物理地址转换映射的主要目的是便于实现虚拟存储器。不象段的大小可变,页的大小是相等并固定的。根据程序的逻辑划分段,而根据实现虚拟存储器的方便划分页。
在 80386中,页的大小固定为 4K 字节,每一页的边界地址必须是4K 的倍数。因此,4G大小的地址空间被划分为 1M 个页,页的开始地址具有“XXXXX000H”的形式。为此,我们把页开始地址的高20 位 XXXXXH称为页码。线性地址空间页的页码也就是页开始边界线性地址的高20 位;物理地址空间页的页码也就是页开始边界物理地址的高20 位。可见,页码左移 12位就是页的开始地址,所以页码规定了页。
由于页的大小固定为 4K字节,且页的边界是 4K 的倍数,所以在把32 位线性地址转换成 32位物理地址的过程中,低 12 位地址保持不变。也就是说,线性地址的低12 位就是物理地址的低 12位。
<二>线性地址到物理地址的转换
1.映射表结构
线性地址空间的页到物理地址空间的页之间的映射用表来描述。由于4G 的地址空间划分为 1M个页,因此,如果用一张表来描述这种映射,那么该映射表就要有1M 个表项,若每个表项占用 4个字节,那么该映射表就要占用 4M 字节。为避免映射表占用如此巨大的存储器资源,所以80386 把页映射表分为两级。
页映射表的第一级称为页目录表,存储在一个 4K 字节的物理页中。页目录表共有1K 个表项,其中,每个表项为 4字节长,包含对应第二级表所在物理地址空间页的页码。页映射表的第二级称为页表,每张页表也安排在一个4K 字节的页中。每张页表都有 1K个表
下图显示了由页目录表和页表构成的页映射表结构。从图中可见,控制寄存器CR3 指定页目录表;页目录表可以指定 1K个页表,这些页表可以分散存放在任意的物理页中,而不需要连续存放;每张页表可以指定1K 个物理地址空间的页,这些物理地址空间的页可以任意地分散在物理地址空间中。需要注意的是,存储页目录表和页表的基地址是对齐在4K 字节边界上的。
2.表项格式
页目录表和页表中的表项都采用如下图所示的格式。从图中可见,最高20 位(位12—位31)包含物理地址空间页的页码,也就是物理地址的高20 位。低 12位包含页的属性。下图所示的属性中内容为0 的位是 Intel公司为 80486等处理器所保留的位,在为 80386 编程使用到它们时必须设置为 0。在位9 至位 11的 AVL字段供软件使用。表项的最低位是存在属性位,记作P。P位表示该表项是否有效。P=1表项有效;P=0表项无效,此时表项中的其余各位均可供软件使用,80386不解释 P=0的表项中的任何其它的位。在通过页目录表和页表进行的线性地址到物理地址的转换过程中,无论在页目录表还是在页表中遇到无效表项,都会引起页故障。其它属性位的作用在下文中介绍。
3.线性地址到物理地址的转换
分页管理机制通过上述页目录表和页表实现 32 位线性地址到32 位物理地址的转换。控制寄存器 CR3的高 20位作为页目录表所在物理页的页码。首先把线性地址的最高10 位(即位22 至位 31)作为页目录表的索引,对应表项所包含的页码指定页表;然后,再把线性地址的中间10 位(即位12 至位 21)作为所指定的页目录表中的页表项的索引,对应表项所包含的页码指定物理地址空间中的一页;最后,把所指定的物理页的页码作为高20 位,把线性地址的低 12位不加改变地作为 32位物理地址的低 12位。
cr3的结构如下:
cr3又叫PDBR(Page-Directory Base Register)它的高20位将是页目录表首地址的高20位,页目录表首地址的低12位会是零,也就是说,页目录表会是4KB对齐的。
为了避免在每次存储器访问时都要访问内存中的页表,以便提高访问内存的速度,80386处理器的硬件把最近使用的线性—物理地址转换函数存储在处理器内部的页转换高速缓存中。在访问存储器页表之前总是先查阅高速缓存,仅当必须的转换不在高速缓存中时,才访问存储器中的两级页表。页转换高速缓存也称为页转换查找缓存,记为TLB。
在分页机制转换高速缓存中的数据与页表中数据的相关性,不是由80386 处理器进行维护的,而必须由操作系统软件保存,也就是说,处理器不知道软件什么时候会修改页表,在一个合理的系统中,页表只能由操作系统修改,操作系统可以直接地在软件修改页表后通过刷新高速缓存来保证相关性。高速缓存的刷新通过装入处理器控制寄存器CR3 完成.
一个重要的修改页表项的特殊情况不需要对页转换高速缓存刷新,这种情况是指修改不存在表项的任一部分,即使P 位本身从 P=0
改变为 P=1时也一样,因为无效的表项不会存入高速缓存。因此,当无效的表项被改变时,不需要刷新高速缓存。这表明在从磁盘上读入一页使其存在时,不必刷新高速缓存。
在一个多处理器系统中,必须特别注意是否在一个处理器中执行的程序,会改变可能由另外的处理器同时访问的页表。在80386 处理器中,每当要更新页表项并设置 D 位和A 位时,通过使用不可分的读/修改/写周期支持多处理器的配置。对于页表项的软件更新需要借助于使用LOCK 前缀,从而保证修改页表的指令工作在不可分的读/修改/写周期中。在改变一个可能由另外的处理器使用的页表之前,最好使用一条加锁的AND 指令在一个不可分的操作中将 P位清除为 0,然后,该表项可根据要求进行修改,并随后把P 位置成 1而使表项成为可用。当修改页表项时必须及时通知(通常使用中断方式)系统中该表项已被高速缓存的所有处理器刷新各自的页转换高速缓存,以撤消该表项的旧拷贝。在表项的旧拷贝被刷新之前,各处理器仍可继续访问旧的页,并可以设置正被修改的表项的D 位。如果这样做引起表项修改失败,则分页机制高速缓存最好在标记为不存在之后,并在对表项进行另外的修改之前进行刷新。
4.不存在的页表
采用上述页映射表结构,存储全部 1K 张页表需要4M 字节,此外还需要 4K字节用于存储页目录表。这样的两级页映射表似乎反而比单一的整张页映射表多占用4K 字节。其实不然,事实上不需要在内存中存储完整的两级页映射表。两级页映射表结构中对于线性地址空间中不存在的或未使用的部分不必分配页表。除必须给页目录表分配物理页外,仅当在需要时才给页表分配物理页,于是页映射表的大小就对应于实际使用的线性地址空间大小。因为任何一个实际运行的程序使用的线性地址空间都远小于4G 字节,所以用于分配给页表的物理页也远小于 4M 字节。
页目录表项中的存在位 P表明对应页表是否有效。如果 P=1,表明对应页表有效,可利用它进行地址转换;如果P=0,表明对应页表无效。如果试图通过无效的页表进行线性地址到物理地址的转换,那么将引起页故障。因此,页目录表项中的属性位P 使得操作系统只需给覆盖实际使用的线性地址范围的页表分配物理页。
页目录表项中的属性位 P页可用于把页表存储在虚拟存储器中。当发生由于所需页表无效而引起的页故障时,页故障处理程序再申请物理页,从磁盘上把对应的页表读入,并把对应页目录表项中的P 位置 1。换言之,可以当需要时才为所要的页表分配物理页。这样页表占用的物理页数量可降到最小。
5.页的共享
由上述页映射表结构可见,分页机制没有全局页和局部页的规定。每一个任务可使用自己的页映射表独立地实现线性地址到物理地
址的转换。但是,如果使每一个任务所用的页映射表具有部分相同的映射,那么也就可以实现部分页的共享。
常用的实现页共享的方法是线性地址空间的共享,也就是不同任务的部分相同的线性地址空间的映射信息相同,具体表现为部分页表相同或页表内的部分表项的页码相同。例如,如果任务A 和任务 B分别使用的页目录表 A 和页目录表B 内的第 0项中的页码相同,也就是页表 0 相同,那么任务A 和任务 B的 00000000H至 003FFFFFH线性地址空间就映射到相同的物理页。再如,任务A 和任务 B
使用的页表 0不同,但这两张页表内第 0 至第0FFH 项的页码对应相同,那么任务 A和任务 B的 00000000H至 000FFFFFH线性地址空间就映射到相同的物理页。
需要注意的是,共享的页表最好由两个页目录中同样的目录项所指定。这一点很重要,因为它保证了在两个任务中同样的线性地址 范围将映射到该全局区域。
<三>页级保护和虚拟存储器支持
1.页级保护
页目录和页表中分别存放为页目录项和页表项, 它们的格式如下:
80386 不仅提供段级保护,也提供页级保护。分页机制只区分两种特权级。特权级0、1和 2统称为系统特权级,特权级 3 称为用户特权级。在上图所示页目录表和页表的表项中的保护属性位R/W 和 U/S就是用于对页进行保护。
表项的位 1是读写属性位,记作 R/W。R/W位指示该表项所指定的页是否可读、写或执行。若R/W=1,对表项所指定的页可进行读、写或执行;若R/W=0,对表项所指定的页可读或执行,但不能对该指定的页写入。但是,R/W位对页的写保护只在处理器处于用户特权级时发挥作用;当处理器处于系统特权级时,R/W位被忽略,即总可以读、写或执行。
表项的位 2是用户/系统属性位,记作U/S。U/S位指示该表项所指定的页是否是用户级页。若U/S=1,表项所指定的页是用户级页,可由任何特权级下执行的程序访问;如果U/S=0,表项所指定的页是系统级页,只能由系统特权级下执行的程序访问。下表列出了上述属性位R/W 和 U/S所确定的页级保护下,用户级程序和系统级程序分别具有的对用户级页和系统级页进行操作的权限。
由上表可见,用户级页可以规定为只允许读/执行或规定为读/写/执行。系统级页对于系统级程序总是可读/写/执行,而对用户级程序总是不可访问的。于分段机制一样,外层用户级执行的程序只能访问用户级的页,而内层系统级执行的程序,既可访问系统级页,也可访问用户级页。与分段机制不同的是,在内层系统级执行的程序,对任何页都有读/写/执行访问权,即使规定为只允许读/执行的用户页,内层系统级程序也对该页有写访问权。
页目录表项中的保护属性位 R/W和 U/S对由该表项指定页表所指定的全部 1K 各页起到保护作用。所以,对页访问时引用的保护属性位R/W 和 U/S的值是组合计算页目录表项和页表项中的保护属性位的值所得。下表列出了组合计算前后的保护属性位的值,组合计算是“与”操作。
正如在 80386地址转换机制中分页机制在分段机制之后起作用一样,由分页机制支持的页级保护也在由分段机制支持的段级保护之后起作用。先测试有关的段级保护,如果启用分页机制,那么在检查通过后,再测试页级保护。如果段的类型为读/写,而页规定为只允许读/执行,那么不允许写;如果段的类型为只读/执行,那么不论页保护如何,也不允许写。
页级保护的检查是在线性地址转换为物理地址的过程中进行的,如果违反页保护属性的规定,对页进行访问(读/写/执行),那么将引起页异常。
2.对虚拟存储器的支持
页表项中的 P位是支持采用分页机制虚拟存储器的关键。P=1,表示表项指定的页存在于物理存储器中,并且表项的高20 位是物理页的页码;P=0,表示该线性地址空间中的页所对应的物理地址空中的页不在物理存储器中。如果程序访问不存在的页,会引起页异常,这样操作系统可把该不存在的页从磁盘上读入,把所在物理页的页码填入对应表项并把表项中的P 位置为 1,然后使引起异常的程序恢复运行。
此外,表项中的访问位 A和写标志位 D也用于支持有效地实现虚拟存储器。
表项的位 5是访问属性位,记作 A。在为了访问某存储单元而进行线性地址到物理地址的转换过程中,处理器总是把页目录表内的对应表项和其所指定页表内的对应表项中的A 位置 1,除非页表或页不存在,或者访问违反保护属性规定。所以,A=1表示已访问过对应的物理页。处理器永不清除A 位。通过周期性地检测及清除 A位,操作系统就可确定哪些页在最近一段时间未被访问过。当存储器资源紧缺时,这些最近未被访问的页很可能就被选择出来,将它们从内存换出到磁盘上去。
表项的位 6是写标志位,记作 D。在为了访问某存储单元而进行线性地址到物理地址的转换过程中,如果是写访问并且可以写访问,处理器就把页表内对应表项中的D 位置 1,但并不把页目录表内对应表项中的D 置 1。当某页从磁盘上读入内存时,页表中对应对应表项的D 位被清 0。所以,D=1表示已写过对应的物理页。当某页需要从内存换出到磁盘上时,如果该页的D 位为 1,那么必须进行写操作(把内存中的页写入磁盘时,处理器并不清除对应页表项的D 位)。但是,如果要写到磁盘上的页的D 位为 0,那么不需要实际的磁盘写操作,而只要简单地放弃内存中该页即可。因为内存中的页与磁盘中的页具有完全相同的内容。
此外:G 是全局位. 用来指示该表项所指向的页是否为全局性质的. 如果页是全局的, 那么, 它将在高速缓存中一直保存(也就意味着地址转换速度会很快). 因为页高速缓存容量有限, 只能存放频繁使用的那些表项. 而且, 当因任务切换等原因改变CR3寄存器的内容时, 整个页高速缓存的内容都会被刷新.
<四>页异常
启用分页机制后,线性地址不再直接等于物理地址,线性地址要经过分页机制转换才成为物理地址。在转换过程中,如果出现下列
情况之一就会引起页异常:
(1)涉及的页目录表内的表项或页表内的表项中的P=0,即涉及到页不在内存;
(2)发现试图违反页保护属性的规定而对页进行访问。
报告页异常的中断向量号是 14(0EH)。页异常属于故障类异常。在进入故障处理程序时,保存的指令指针CS 及 EIP指向发生故障
的指令。一旦引起页故障的原因被排除后,即可从页故障处理程序通过一条IRET 指令,直接地重新执行产生故障的指令。当页故障发生时,处理器把引起页故障的线性地址装入CR2。页故障处理程序可以利用该线性地址确定对应的页目录项和页表项。
页故障还在堆栈中提供一个出错码,出错码的格式如下图所示。其中,
U位表示引起故障程序的特权级,U=1表示用户特权级(特权级3),U=0表示系统特权级(特权级0、1或 2);
W位表示访问类型,W=0表示读/执行,W=1表示写;
P位表示异常类型,P=0表示页不存在故障,P=1表示保护故障。页故障的响应处理模式同其它故障一样。
编写代码启动分页机制(Linux):
; ==========================================
; pmtest6.asm
; 编译方法:nasm pmtest6.asm -o pmtest6.com
; ==========================================
;----------------------------------------------------------------------------
DA_32 EQU 4000h ; 32 位段
DA_LIMIT_4K EQU 8000h ; 段界限粒度为 4K 字节
DA_DPL0 EQU 00h ; DPL = 0
DA_DPL1 EQU 20h ; DPL = 1
DA_DPL2 EQU 40h ; DPL = 2
DA_DPL3 EQU 60h ; DPL = 3
;----------------------------------------------------------------------------
; 存储段描述符类型值说明
;----------------------------------------------------------------------------
DA_DR EQU 90h ; 存在的只读数据段类型值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
DA_C EQU 98h ; 存在的只执行代码段属性值
;----------------------------------------------------------------------------
; 系统段描述符类型值说明
;----------------------------------------------------------------------------
DA_LDT EQU 82h ; 局部描述符表段类型值
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; SA_ : Selector Attribute
SA_RPL0 EQU 0 ; ┓
SA_RPL1 EQU 1 ; ┣ RPL
SA_RPL2 EQU 2 ; ┃
SA_RPL3 EQU 3 ; ┛
SA_TIG EQU 0 ; ┓TI
SA_TIL EQU 4 ; ┛
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 分页机制使用的常量说明
;----------------------------------------------------------------------------
PG_P EQU 1 ; 页存在属性位
PG_RWR EQU 0 ; R/W 属性位值, 读/执行
PG_RWW EQU 2 ; R/W 属性位值, 读/写/执行
PG_USS EQU 0 ; U/S 属性位值, 系统级
PG_USU EQU 4 ; U/S 属性位值, 用户级
;----------------------------------------------------------------------------
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限 1 (2 字节)
dw %1 & 0FFFFh ; 段基址 1 (2 字节)
db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节)
db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节)
%endmacro ; 共 8 字节
PageDirBase equ 200000h ; 页目录开始地址: 2M
PageTblBase equ 201000h ; 页表开始地址: 2M+4K
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限, 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32 ; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorPageDir equ LABEL_DESC_PAGE_DIR - LABEL_GDT
SelectorPageTbl equ LABEL_DESC_PAGE_TBL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 进入保护模式后显示此字符串
OffsetPMMessage equ PMMessage - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [SPValueInRealMode], sp
; 初始化 16 位代码段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
; 初始化堆栈段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 关闭 A20 地址线
out 92h, al ; ┛
sti ; 开中断
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
call SetupPaging
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 显示完毕
; 到此停止
jmp SelectorCode16:0
; 启动分页机制 --------------------------------------------------------------
SetupPaging:
; 为简化处理, 所有线性地址对应相等的物理地址.
; 首先初始化页目录
mov ax, SelectorPageDir ; 此段首地址为 PageDirBase
mov es, ax
mov ecx, 1024 ; 共 1K 个表项
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表 (1K 个, 4M 内存空间)
mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase
mov es, ax
mov ecx, 1024 * 1024 ; 共 1M 个页表项, 也即有 1M 个页
xor edi, edi
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .2
mov eax, PageDirBase
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分页机制启动完毕 ----------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and eax, 7FFFFFFEh ; PE=0, PG=0
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
部分代码分析:
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables
页目录表开始地址是2M,并且也目录表示连续的,共1024个PDE,每个PDE4字节,所以功能1024*4=4KB大,所以目录表段界限为4095(下标从0开始)。
表示段界限以 4K 字节为单位,于是 20 位的界限可表示的范围是 4K 字节至 4G 字节,增量为 4K 字节。当段界限以 4K 字节为单位时,实际的段界限 LIMIT 可通过下面的公式从 20 位段界限 Limit 计算出来:
LIMIT=limit*4K+0FFFH=(Limit SHL 12)+0FFFH,故该段界限是1023.
; 启动分页机制 --------------------------------------------------------------
SetupPaging:
; 为简化处理, 所有线性地址对应相等的物理地址.
; 首先初始化页目录
mov ax, SelectorPageDir ; 此段首地址为 PageDirBase
mov es, ax
mov ecx, 1024 ; 共 1K 个表项
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表 (1K 个, 4M 内存空间)
mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase
mov es, ax
mov ecx, 1024 * 1024 ; 共 1M 个页表项, 也即有 1M 个页
xor edi, edi
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .2
mov eax, PageDirBase
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分页机制启动完毕 ----------------------------------------------------------