自己动手写操作系统80x86保护模式(4)--分页机制

时间:2021-07-23 14:27:31

80x86保护机制除了分段机制、特权等级机制、门机制之外还有分页机制。分页机制主要实现了线性地址到物理地址转换,涉及的内容主要有PDE、PTE、cr3,机制如下

自己动手写操作系统80x86保护模式(4)--分页机制

转换使用两级页表,第一级叫做页目录,大小为4KB,存储在一个物理页中,每个表项4字节长,共有1024 个表项。每个表项对应第二级的一个页表,第二级的每一个页表也有1024个表项,每一个表项对应一个物 理页。页目录表的表项简称PDE(Page Directory Entry),页表的表项简称TE(Page Table Entry)。

线性地址转换物理地址的具体步骤是:
(1)先是从寄存器cr3指定的页目录中根据线性地址的高10位得到页表。
(2)在页表中,根据线性地址的第12—21位得到物理页首地址。
(3)将这个首地址,加上线性地址的低12位便得到了物理地址。


1、cr3的结构图如下:

自己动手写操作系统80x86保护模式(4)--分页机制

2、PDE的结构图如下:

自己动手写操作系统80x86保护模式(4)--分页机制

其中特别需要注意的几个属性:
(1)P存在位,表示当前条目所指向的页或页表是否在物理内存中。当P=0表示页不在内存中,如果此时处理器试图访问此页,将会产生页异常(Page-fault exception, #PF);P=1表示页在内存中。
(2)A指示页或页表是否被访问。此位往往在页或页表刚刚被加载到物理内存中时被内存管理程序清零,处理器会在第一次访问此页或也表时设置该位。而且,处理器并不会自动清除此位,只有软件能清除它。在时钟页面置换算法中,需要通过该位来标识此页面是否已经被访问。
(3)D指示页或页表是否被写入,此位往往在页或页表刚刚被加载到物理内存中时被内存管理程序清零,处理器会在第一次写入此页或页面时设置此位。而且,处理器并不会自动清除此位,只有软件能清除它。由于该位的存在,当往某页写入内容时,并不需要将其同步到磁盘上,只有当该页被置换出时,判断该位D=0,则表示该页没有被写入,则不需要将其写入磁盘;D=1,则表示该页已被写入,则需要将其写入到磁盘。

3、PTE的结构图如下:

自己动手写操作系统80x86保护模式(4)--分页机制

其中各属性位与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对齐的页表和页。

5、分页机制开启流程
(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:

自己动手写操作系统80x86保护模式(4)--分页机制