80x86保护机制除了分段机制、特权等级机制、门机制之外还有分页机制。分页机制主要实现了线性地址到物理地址转换,涉及的内容主要有PDE、PTE、cr3,机制如下:
转换使用两级页表,第一级叫做页目录,大小为4KB,存储在一个物理页中,每个表项4字节长,共有1024 个表项。每个表项对应第二级的一个页表,第二级的每一个页表也有1024个表项,每一个表项对应一个物 理页。页目录表的表项简称PDE(Page Directory Entry),页表的表项简称TE(Page Table Entry)。
线性地址转换物理地址的具体步骤是:
(1)先是从寄存器cr3指定的页目录中根据线性地址的高10位得到页表。
(2)在页表中,根据线性地址的第12—21位得到物理页首地址。
(3)将这个首地址,加上线性地址的低12位便得到了物理地址。
1、cr3的结构图如下:
2、PDE的结构图如下:
其中特别需要注意的几个属性:
(1)P存在位,表示当前条目所指向的页或页表是否在物理内存中。当P=0表示页不在内存中,如果此时处理器试图访问此页,将会产生页异常(Page-fault exception, #PF);P=1表示页在内存中。
(2)A指示页或页表是否被访问。此位往往在页或页表刚刚被加载到物理内存中时被内存管理程序清零,处理器会在第一次访问此页或也表时设置该位。而且,处理器并不会自动清除此位,只有软件能清除它。在时钟页面置换算法中,需要通过该位来标识此页面是否已经被访问。
(3)D指示页或页表是否被写入,此位往往在页或页表刚刚被加载到物理内存中时被内存管理程序清零,处理器会在第一次写入此页或页面时设置此位。而且,处理器并不会自动清除此位,只有软件能清除它。由于该位的存在,当往某页写入内容时,并不需要将其同步到磁盘上,只有当该页被置换出时,判断该位D=0,则表示该页没有被写入,则不需要将其写入磁盘;D=1,则表示该页已被写入,则需要将其写入到磁盘。
3、PTE的结构图如下:
其中各属性位与PDE中具有相同的含义。
4、页对其方式
cr3中的高20位将是页目录表首地址的高20位,PDE的高20位是页表首地址,PTE的高20位是物理页的首地址。保护模式下,寻址的范围是0-4GB,为什么却用20位来存储这些首地址呢?
cr3中的高20位是页目录表首地址的高20位,页目录表首地址的低12位将会是0,这样就保证了页目录表会是4KB对齐的。同理,PDE中的页表基址(Page-Table Base Address),以及PTE中的物理页基址(Page Base Address)也是用高20位来表示4KB对齐的页表和页。
(1)根据内存大小计算页表项的数目
(2)初始化页目录表
(3)初始化页表
(4)把页目录表的基址写入cr3寄存器
6、分页机制开启流程
;/*
;nasm boot.asm -o boot.com
;*/
;
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro ; 共 8 字节
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_32 EQU 4000h ; 32 位段
DA_C EQU 98h ; 存在的只执行代码段属性值
;----------------------------------------------------------------------------
; 分页机制使用的常量说明
;----------------------------------------------------------------------------
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 属性位值, 用户级
PageDirBase equ 200000h ; 页目录开始地址: 2M
PageTblBase equ 201000h ; 页表开始地址: 2M + 4K
org 0x0100
jmp LABEL_BEGIN
[section .gdt]
;GDT 段基址, 段界限, 段属性
LABEL_GDT_NULL: Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0FFFFh, DA_DRW
LABEL_DSCPTR_CODE16: Descriptor 0, 0FFFFh, DA_C ;16位非一致代码段;为什么段界限为0FFFFh程序才没错
LABEL_DSCPTR_STACK: Descriptor 0, TopOfStack-1, DA_DRW + DA_32;32位堆栈
LABEL_DESCPTR_DATA: Descriptor 0, DataLen-1, DA_DRW
LABEL_DESCPTR_TEST: Descriptor 0500000h, 0FFFFh, DA_DRW
LABEL_CODE_DESC: Descriptor 0, Code32Len-1, DA_C + DA_32 ;非一致代码段
LABEL_VEDIO_DESC: Descriptor 0B8000H, 0FFFFH, DA_DRW;显存
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW ; Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096 * 8 - 1, DA_DRW ; Page Tables
;GDT end
Gdtlen equ $ - LABEL_GDT_NULL
Gdtptr dw Gdtlen; GDTR段界限
dd 0 ;GDTR段基址
;GDT选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT_NULL
SelectorCode32 equ LABEL_CODE_DESC - LABEL_GDT_NULL
SelectorCode16 equ LABEL_DSCPTR_CODE16 - LABEL_GDT_NULL
SelectorStack equ LABEL_DSCPTR_STACK - LABEL_GDT_NULL
SelectorData equ LABEL_DESCPTR_DATA - LABEL_GDT_NULL
SelectorTest equ LABEL_DESCPTR_TEST - LABEL_GDT_NULL
SelectorVedio equ LABEL_VEDIO_DESC - LABEL_GDT_NULL
SelectorPageDir equ LABEL_DESC_PAGE_DIR - LABEL_GDT_NULL
SelectorPageTbl equ LABEL_DESC_PAGE_TBL - LABEL_GDT_NULL
;end section .gdt
[section .code16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov es, ax
mov ds, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GOBACK_REAL+3] , ax ;;为跳转回实模式做准备
mov [_SPValueInRealMode], sp
;;得到内存情况
mov ebx, 0
mov di, _MemChkBuf
.loop:
mov eax, 0E820h
mov ecx, 20
mov edx, 0534D4150h
int 15h
jc LABEL_MEM_CHK_FAIL
add di, 20
inc dword[_dwMCRNumber]
cmp ebx, 0
jne .loop
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:
;初始化32位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_CODE32
mov word [LABEL_CODE_DESC + 2], ax
shr eax, 16
mov byte [LABEL_CODE_DESC + 4], al
mov byte [LABEL_CODE_DESC + 7], ah
;初始化数据段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESCPTR_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESCPTR_DATA + 4], al
mov byte [LABEL_DESCPTR_DATA + 7], ah
;;初始化堆栈段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DSCPTR_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DSCPTR_STACK + 4], al
mov byte [LABEL_DSCPTR_STACK + 7], ah
;;初始化16位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_CODE16
mov word [LABEL_DSCPTR_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DSCPTR_CODE16 + 4], al
mov byte [LABEL_DSCPTR_CODE16 + 7], ah
;为加载GDTR做准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT_NULL
mov dword[Gdtptr + 2], eax
;加载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
;;============跳转回实模式执行代码 down================
LABEL_REAL_ENTRY:
mov ax, cs
mov ds, ax
mov ss, ax
mov es, ax
mov sp, [_SPValueInRealMode]
;;关闭A20地址线
in al, 92h
and al, 11111101b
out 92h, al
;;开中断
sti
;;返回dos
mov ax, 4c00h
int 21h
;;============^up^跳转回实模式执行代码================
; end of section .code16
[section .data32]
ALIGN 32
[BITS 32]
LABEL_DATA:
_SPValueInRealMode dw 0
;字符串
_Message: db "In Protect Mode Now ^-^", 0Ah, 0Ah, 0
_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 ; 进入保护模式后显示此字符串
;变量
_szRamSize: db "RAM Size:", 0
_szReturn db 0Ah,0
_dwMCRNumber: dd 0;Memory Check Result
_dwMemSize: dd 0
_dwDispPos: dd (80 * 6 + 0) * 2 ;屏幕显示位置
_ARDStruct: ;Address Range Descriptor Structure
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
_MemChkBuf: times 256 db 0
;保护模式用以下符号
OffsetMessage equ _Message - $$
szReturn equ _szReturn - $$
szMemChkTitle equ _szMemChkTitle - $$
szRamSize equ _szRamSize - $$
dwMCRNumber equ _dwMCRNumber - $$
dwMemSize equ _dwMemSize - $$
dwDispPos equ _dwDispPos - $$
ARDStruct equ _ARDStruct - $$
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
MemChkBuf equ _MemChkBuf - $$
DataLen equ $ - LABEL_DATA
;;end of .data32
[section .stack32]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK
;;end of .stack32
[section .code32]
[BITS 32]
LABEL_CODE32:
mov ax, SelectorVedio
mov gs, ax
mov ax, SelectorStack
mov ss, ax
mov ax, SelectorData
mov ds, ax
mov ax, SelectorData
mov es, ax
mov esp, TopOfStack-1
;;显示一个字符串
push OffsetMessage
call DispStr
add esp , 4
push szMemChkTitle
call DispStr
add esp, 4
call DispMemSize ;显示内存信息
call SetupPaging ;启动分页
;;跳转回实模式代码段
jmp SelectorCode16:0
; 启动分页机制 --------------------------------------------------------------
SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
div ebx
mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
test edx, edx
jz .no_remainder
inc ecx ; 如果余数不为 0 就需增加一个页表
.no_remainder:
push ecx ; 暂存页表个数
; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
; 首先初始化页目录
mov ax, SelectorPageDir ; 此段首地址为 PageDirBase
mov es, ax
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表
mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase
mov es, ax
pop eax ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE个数 = 页表个数 * 1024
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
; 分页机制启动完毕 ----------------------------------------------------------
DispMemSize:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, dword[dwMCRNumber]
.loop:
mov edx, 5
mov edi, ARDStruct
.1:
push dword[esi]
call DispInt
pop eax
stosd ;;填充ARDStruct
add esi, 4
dec edx
cmp edx, 0
jnz .1
call DispReturn
cmp dword[dwType], 1
jne .2
mov eax, [dwBaseAddrLow]
add eax, [dwLengthLow]
cmp eax, [dwMemSize]
jb .2
mov [dwMemSize], eax
.2:
loop .loop
call DispReturn
push szRamSize
call DispStr
add esp, 4
push dword[dwMemSize]
call DispInt
add esp, 4
pop ecx
pop edi
pop esi
ret
%include "lib.inc"
Code32Len equ $ - LABEL_CODE32
;end of .code32
[section .code16]
ALIGN 32
[BITS 16]
LABEL_CODE16:
mov ax, SelectorNormal
mov ds, ax
mov ss, ax
mov es, ax
mov gs, ax
mov fs, ax
;;cr0 PE位清零 并且取消分页
mov eax, cr0
and eax, 7FFFFFFEh
mov cr0, eax
LABEL_GOBACK_REAL:
jmp 0:LABEL_REAL_ENTRY ;跳转回实模式 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_CODE16
;;end of code16
没图说JB: