Orange'S:一个操作系统的实现

页式存储

Posted by BINBIN Blog on October 31, 2019

什么是页

所谓页,就是一块内存,在80386中,页的大小是固定的4096字节(4KB),在Pentium中,页的大小还可以是2MB 或者 4 MB,并且可以访问到多于4GB 的内存,这个超出我们讨论范畴! 只讨论页大小为4KB!

逻辑地址、线性地址、物理地址

在未打开分页机制的时候,线性地址等同于物理地址,于是可以认为,逻辑地址通过分段机制直接转换成物理地址。但当分页开启时,情况发生变化,分段机制将逻辑地址转换成线性地址,线性地址再通过分页机制转换成物理地址。

传统上,段地址和偏移地址称为逻辑地址!

段的管理是由处理器的段部件负责进行的,段部件将段地址和偏移地址相加,得到访问内存的地址。一般来说,段部件产生的地址就是物理地址。 IA-32 处理器支持多任务。在多任务环境下,任务的创建需要分配内存空间;当任务终止后, 还要回收它所占用的内存空间。在分段模型下,内存的分配是不定长的,程序大时,就分配一大块内存;程序小时,就分配一小块。时间长了,内存空间就会碎片化,就有可能出现一种情况:内存空间是有的,但都是小块,无法分配给某个任务。为了解决这个问题, IA-32 处理器支持分页功能, 分页功能将物理内存空间划分成逻辑上的页。页的大小是固定的,一般为 4KB,通过使用页,可以简化内存管理。 如图 10-3 所示,当页功能开启时,段部件产生的地址就不再是物理地址了,而是线性地址(Linear Address),线性地址还要经页部件转换后,才是物理地址。

为什么要分页呢?

分段管理机制已经提供了很好的保护机制,为啥还要加分页管理机制!除了管理内存,主要目的是在于实现虚拟存储器。 可以看出,线性地址中任意一个页都能映射到物理地址的中的任何一个页,这样使得内存管理变得相当灵活!

分页机制概述

我们可以知道分页机制就像一个函数!

物理地址 = f(线性地址)

如图所示:

转换使用两级页表,第一级叫做页目录,大小为4kb,存储在一个物理页中,每个表项4字节长,共有1024个表项。每个表项对应第二级的一个页表,第二级的每一个页表也有1024个表项,每一个表项对应一个物理页。

页目录表的表项简称PDE (Page Directory ENtry),页表的表项简称PTE (Page Table Entry)。

进行转换时,先是从由寄存器cr3 指定的页目录中根据线性地址的高10位得到页表地址,然后在页表中根据线性地址的第12到21位得到物理页首地址,将这个首地址加上线性地址低12位便得到了物理地址。

分页机制是否生效的开关位于cr0的最高位PG位。 如果PG= 1,则分页机制生效。 所以,当我们准备好了页目录表和页表,并将cr3指向页目录表之后,只需要置PG位,分页机制就开始工作了。下面我们就来写一段代码试验一下。

在 80386中,页的大小固定为 4K 字节,每一页的边界地址必须是4K 的倍数。因此,4G大小的地址空间被划分为 1M 个页,页的开始地址具有“XXXXX000H”的形式。为此,我们把页开始地址的高20 位 XXXXXH称为页码。线性地址空间页的页码也就是页开始边界线性地址的高20 位;物理地址空间页的页码也就是页开始边界物理地址的高20 位。可见,页码左移 12位就是页的开始地址,所以页码规定了页。

由于页的大小固定为 4K字节,且页的边界是 4K 的倍数,所以在把32 位线性地址转换成 32位物理地址的过程中,低 12 位地址保持不变。也就是说,线性地址的低12 位就是物理地址的低 12位。

线性地址空间的页到物理地址空间的页之间的映射用表来描述。由于4G 的地址空间划分为 1M个页,因此,如果用一张表来描述这种映射,那么该映射表就要有1M 个表项,若每个表项占用 4个字节,那么该映射表就要占用 4M 字节。为避免映射表占用如此巨大的存储器资源,所以80386 把页映射表分为两级。

页映射表的第一级称为页目录表,存储在一个 4K 字节的物理页中。页目录表共有1K 个表项,其中,每个表项为 4字节长,包含对应第二级表所在物理地址空间页的页码。页映射表的第二级称为页表,每张页表也安排在一个4K 字节的页中。每张页表都有 1K个表

项,每个表项为 4字节长,包含对应物理地址空间页的页码。由于页目录表和页表均由1K 个表项组成,所以使用 10位的索引就能指定表项,即用 10 位的索引值乘以4 (每个表项4字节)加基地址就得到了表项的物理地址。

控制寄存器CR3 指定页目录表;页目录表可以指定 1K个页表,这些页表可以分散存放在任意的物理页中,而不需要连续存放;每张页表可以指定1K 个物理地址空间的页,这些物理地址空间的页可以任意地分散在物理地址空间中。需要注意的是,存储页目录表和页表的基地址是对齐在4K 字节边界上的。

编辑代码启动分页机制

书上是以pmtest2.asm的基础上进行修改, 将试验内存写入和读取的描述符、代码以及数据都去掉,添加一个函数 SetupPaging !

代码如下:


1  	; ==========================================
2  	; pmtest6.asm
3  	; 编译方法:nasm pmtest6.asm -o pmtest6.com
4  	; ==========================================
5  
6  	%include	"pm.inc"	; 常量, 宏, 以及一些说明
7  
8  	PageDirBase		equ	200000h	; 页目录开始地址: 2M
9  	PageTblBase		equ	201000h	; 页表开始地址: 2M+4K
10 
11 	org	0100h
12 		jmp	LABEL_BEGIN
13 
14 	[SECTION .gdt]
15 	; GDT
16 	;                            段基址,       段界限, 属性
17 	LABEL_GDT:           Descriptor 0,              0, 0     		; 空描述符
18 	LABEL_DESC_NORMAL:   Descriptor 0,         0ffffh, DA_DRW		; Normal 描述符
19 	LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
20 	LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables
21 	LABEL_DESC_CODE32:   Descriptor 0, SegCode32Len-1, DA_C+DA_32		; 非一致代码段, 32
22 	LABEL_DESC_CODE16:   Descriptor 0,         0ffffh, DA_C			; 非一致代码段, 16
23 	LABEL_DESC_DATA:     Descriptor 0,      DataLen-1, DA_DRW		; Data
24 	LABEL_DESC_STACK:    Descriptor 0,     TopOfStack, DA_DRWA + DA_32	; Stack, 32 位
25 	LABEL_DESC_VIDEO:    Descriptor 0B8000h,   0ffffh, DA_DRW		; 显存首地址
26 	; GDT 结束
27 
28 	GdtLen		equ	$ - LABEL_GDT	; GDT长度
29 	GdtPtr		dw	GdtLen - 1	; GDT界限
30 			dd	0		; GDT基地址
31 
32 	; GDT 选择子
33 	SelectorNormal		equ	LABEL_DESC_NORMAL	- LABEL_GDT
34 	SelectorPageDir		equ	LABEL_DESC_PAGE_DIR	- LABEL_GDT
35 	SelectorPageTbl		equ	LABEL_DESC_PAGE_TBL	- LABEL_GDT
36 	SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
37 	SelectorCode16		equ	LABEL_DESC_CODE16	- LABEL_GDT
38 	SelectorData		equ	LABEL_DESC_DATA		- LABEL_GDT
39 	SelectorStack		equ	LABEL_DESC_STACK	- LABEL_GDT
40 	SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
41 	; END of [SECTION .gdt]
42 
43 	[SECTION .data1]	 ; 数据段
44 	ALIGN	32
45 	[BITS	32]
46 	LABEL_DATA:
47 	SPValueInRealMode	dw	0
48 	; 字符串
49 	PMMessage:		db	"In Protect Mode now. ^-^", 0	; 进入保护模式后显示此字符串
50 	OffsetPMMessage		equ	PMMessage - $$
51 	DataLen			equ	$ - LABEL_DATA
52 	; END of [SECTION .data1]
53 
54 
55 	; 全局堆栈段
56 	[SECTION .gs]
57 	ALIGN	32
58 	[BITS	32]
59 	LABEL_STACK:
60 		times 512 db 0
61 
62 	TopOfStack	equ	$ - LABEL_STACK - 1
63 
64 	; END of [SECTION .gs]
65 
66 
67 	[SECTION .s16]
68 	[BITS	16]
69 	LABEL_BEGIN:
70 		mov	ax, cs
71 		mov	ds, ax
72 		mov	es, ax
73 		mov	ss, ax
74 		mov	sp, 0100h
75 
76 		mov	[LABEL_GO_BACK_TO_REAL+3], ax
77 		mov	[SPValueInRealMode], sp
78 
79 		; 初始化 16 位代码段描述符
80 		mov	ax, cs
81 		movzx	eax, ax
82 		shl	eax, 4
83 		add	eax, LABEL_SEG_CODE16
84 		mov	word [LABEL_DESC_CODE16 + 2], ax
85 		shr	eax, 16
86 		mov	byte [LABEL_DESC_CODE16 + 4], al
87 		mov	byte [LABEL_DESC_CODE16 + 7], ah
88 
89 		; 初始化 32 位代码段描述符
90 		xor	eax, eax
91 		mov	ax, cs
92 		shl	eax, 4
93 		add	eax, LABEL_SEG_CODE32
94 		mov	word [LABEL_DESC_CODE32 + 2], ax
95 		shr	eax, 16
96 		mov	byte [LABEL_DESC_CODE32 + 4], al
97 		mov	byte [LABEL_DESC_CODE32 + 7], ah
98 
99 		; 初始化数据段描述符
100		xor	eax, eax
101		mov	ax, ds
102		shl	eax, 4
103		add	eax, LABEL_DATA
104		mov	word [LABEL_DESC_DATA + 2], ax
105		shr	eax, 16
106		mov	byte [LABEL_DESC_DATA + 4], al
107		mov	byte [LABEL_DESC_DATA + 7], ah
108
109		; 初始化堆栈段描述符
110		xor	eax, eax
111		mov	ax, ds
112		shl	eax, 4
113		add	eax, LABEL_STACK
114		mov	word [LABEL_DESC_STACK + 2], ax
115		shr	eax, 16
116		mov	byte [LABEL_DESC_STACK + 4], al
117		mov	byte [LABEL_DESC_STACK + 7], ah
118
119		; 为加载 GDTR 作准备
120		xor	eax, eax
121		mov	ax, ds
122		shl	eax, 4
123		add	eax, LABEL_GDT		; eax <- gdt 基地址
124		mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址
125
126		; 加载 GDTR
127		lgdt	[GdtPtr]
128
129		; 关中断
130		cli
131
132		; 打开地址线A20
133		in	al, 92h
134		or	al, 00000010b
135		out	92h, al
136
137		; 准备切换到保护模式
138		mov	eax, cr0
139		or	eax, 1
140		mov	cr0, eax
141
142		; 真正进入保护模式
143		jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处
144
145	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
146
147	LABEL_REAL_ENTRY:		; 从保护模式跳回到实模式就到了这里
148		mov	ax, cs
149		mov	ds, ax
150		mov	es, ax
151		mov	ss, ax
152
153		mov	sp, [SPValueInRealMode]
154
155		in	al, 92h		; ┓
156		and	al, 11111101b	; ┣ 关闭 A20 地址线
157		out	92h, al		; ┛
158
159		sti			; 开中断
160
161		mov	ax, 4c00h	; ┓
162		int	21h		; ┛回到 DOS
163	; END of [SECTION .s16]
164
165
166	[SECTION .s32]; 32 位代码段. 由实模式跳入.
167	[BITS	32]
168
169	LABEL_SEG_CODE32:
170		call	SetupPaging
171
172		mov	ax, SelectorData
173		mov	ds, ax			; 数据段选择子
174		mov	ax, SelectorVideo
175		mov	gs, ax			; 视频段选择子
176
177		mov	ax, SelectorStack
178		mov	ss, ax			; 堆栈段选择子
179
180		mov	esp, TopOfStack
181
182
183		; 下面显示一个字符串
184		mov	ah, 0Ch			; 0000: 黑底    1100: 红字
185		xor	esi, esi
186		xor	edi, edi
187		mov	esi, OffsetPMMessage	; 源数据偏移
188		mov	edi, (80 * 10 + 0) * 2	; 目的数据偏移。屏幕第 10 行, 第 0 列。
189		cld
190	.1:
191		lodsb
192		test	al, al
193		jz	.2
194		mov	[gs:edi], ax
195		add	edi, 2
196		jmp	.1
197	.2:	; 显示完毕
198
199		; 到此停止
200		jmp	SelectorCode16:0
201
202	; 启动分页机制 --------------------------------------------------------------
203	SetupPaging:
204		; 为简化处理, 所有线性地址对应相等的物理地址.
205
206		; 首先初始化页目录
207		mov	ax, SelectorPageDir	; 此段首地址为 PageDirBase
208		mov	es, ax
209		mov	ecx, 1024		; 共 1K 个表项
210		xor	edi, edi
211		xor	eax, eax
212		mov	eax, PageTblBase | PG_P  | PG_USU | PG_RWW
213	.1:
214		stosd
215		add	eax, 4096		; 为了简化, 所有页表在内存中是连续的.
216		loop	.1
217
218		; 再初始化所有页表 (1K 个, 4M 内存空间)
219		mov	ax, SelectorPageTbl	; 此段首地址为 PageTblBase
220		mov	es, ax
221		mov	ecx, 1024 * 1024	; 共 1M 个页表项, 也即有 1M 个页
222		xor	edi, edi
223		xor	eax, eax
224		mov	eax, PG_P  | PG_USU | PG_RWW
225	.2:
226		stosd
227		add	eax, 4096		; 每一页指向 4K 的空间
228		loop	.2
229
230		mov	eax, PageDirBase
231		mov	cr3, eax
232		mov	eax, cr0
233		or	eax, 80000000h
234		mov	cr0, eax
235		jmp	short .3
236	.3:
237		nop
238
239		ret
240	; 分页机制启动完毕 ----------------------------------------------------------
241
242	SegCode32Len	equ	$ - LABEL_SEG_CODE32
243	; END of [SECTION .s32]
244
245
246	; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
247	[SECTION .s16code]
248	ALIGN	32
249	[BITS	16]
250	LABEL_SEG_CODE16:
251		; 跳回实模式:
252		mov	ax, SelectorNormal
253		mov	ds, ax
254		mov	es, ax
255		mov	fs, ax
256		mov	gs, ax
257		mov	ss, ax
258
259		mov	eax, cr0
260		and	eax, 7FFFFFFEh		; PE=0, PG=0
261		mov	cr0, eax
262
263	LABEL_GO_BACK_TO_REAL:
264		jmp	0:LABEL_REAL_ENTRY	; 段地址会在程序开始处被设置成正确的值
265
266	Code16Len	equ	$ - LABEL_SEG_CODE16
267
268	; END of [SECTION .s16code]
269

可以看出代码增加了两个宏:


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

PageDirBase (PDE)指定了页目录表的位置。PageTblBase (PTE)指定了页表在内存中的位置!

页目录表位于地址2MB处,有1024个表项,占用4kb空间,紧接着页目录表便是页表,位于地址2MB + 4 kb处。 在这里,我们假定最大的可能,共有1024个页表。由于每个页表占用4096字节,所以这些页表共占用4MB空间!

也就是说,本程序所需的内存至少大于6MB! 可能至少有两个表项吧,因为一个表项有1024个页表,每个页表4096字节!

为了逻辑清晰和代码编写简便,分别定义两个段,用来存放页目录表和页表,大小分别是4kb 和 4 mb!

简单起见,程序将所有的线性地址映射到相同的物理地址,于是线性地址和物理地址的关系符合下面的公式:

物理地址 = f(线性地址) = 线性地址

所以程序大部分代码都是写入页目录表和页表,以便于上面的公式成立。

PDE 和 PTR

页目录表和页表中的表项都采用如下图所示的格式。从图中可见,最高20 位(位12—位31)包含物理地址空间页的页码,也就是物理地址的高20 位。低 12位包含页的属性。下图所示的属性中内容为0 的位是 Intel公司为 80486等处理器所保留的位,在为 80386 编程使用到它们时必须设置为 0。在位9 至位 11的 AVL字段供软件使用。表项的最低位是存在属性位,记作P。P位表示该表项是否有效。P=1表项有效;P=0表项无效,此时表项中的其余各位均可供软件使用,80386不解释 P=0的表项中的任何其它的位。在通过页目录表和页表进行的线性地址到物理地址的转换过程中,无论在页目录表还是在页表中遇到无效表项,都会引起页故障。其它属性位的作用在下文中介绍。

线性地址到物理地址的转换

分页管理机制通过上述页目录表和页表实现 32 位线性地址到32 位物理地址的转换。控制寄存器 CR3的高 20位作为页目录表所在物理页的页码。首先把线性地址的最高10 位(即位22 至位 31)作为页目录表的索引,对应表项所包含的页码指定页表;然后,再把线性地址的中间10 位(即位12 至位 21)作为所指定的页目录表中的页表项的索引,对应表项所包含的页码指定物理地址空间中的一页;最后,把所指定的物理页的页码作为高20 位,把线性地址的低 12位不加改变地作为 32位物理地址的低 12位。

页目录和页表中分别存放为页目录项和页表项, 它们的格式如下:

分页机制只区分两种特权级。特权级0、1和 2统称为系统特权级,特权级 3 称为用户特权级。在上图所示页目录表和页表的表项中的保护属性位R/W 和 U/S就是用于对页进行保护。

位0 –目录表项中的存在位 P表明对应页表是否有效。如果 P=1,表明对应页表有效,可利用它进行地址转换;如果P=0,表明对应页表无效。

表项的位 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地址转换机制中分页机制在分段机制之后起作用一样,由分页机制支持的页级保护也在由分段机制支持的段级保护之后起作用。先测试有关的段级保护,如果启用分页机制,那么在检查通过后,再测试页级保护。如果段的类型为读/写,而页规定为只允许读/执行,那么不允许写;如果段的类型为只读/执行,那么不论页保护如何,也不允许写。

页级保护的检查是在线性地址转换为物理地址的过程中进行的,如果违反页保护属性的规定,对页进行访问(读/写/执行),那么将引起页异常。

cr3的结构如下:

cr3又叫PDBR(Page-Directory Base Register)它的高20位将是页目录表首地址的高20位,页目录表首地址的低12位会是零,也就是说,页目录表会是4KB对齐的。

回头看看代码:

第207行 - 208行

; 首先初始化页目录
207		mov	ax, SelectorPageDir	; 此段首地址为 PageDirBase
208		mov	es, ax

将es段寄存器指向了对应页目录表段,下面让edi等于0,于是es:edi 只想了页目录表的开始。

第214行的指令 stosd 第一次执行时就把eax 中的PageTblBase | PG_P | PG_USU | PG_RWW 存入了页目录表的第一个PDE中!

214		stosd ; 字符串处理
215		add	eax, 4096		; 为了简化, 所有页表在内存中是连续的.
216		loop	.1

看看PDE 是什么值,PageTblBase | PG_P | PG_USU | PG_RWW 第212行 ` mov eax, PageTblBase | PG_P | PG_USU | PG_RWW` 让当前(第一个)PDE 对应的页表首地址编程PageTblBase , 而且属性显示其指向的是存在可读可写的用户级别页表!

实际上,当为页目录表的第一个PDE赋值时,一个循环就已经开始了

循环的每一次执行中,es:edi会自动指向下一个PDE ,而第215行也将下一个页表的首地址增加4096字节,以便与上一个页表首尾相接。这样,经过1024次循环(第209行由ecs指定 mov ecx, 1024 ; 共 1K 个表项)之后,页目录表中的所有PDE 都被赋值完毕,他们的属性相同,都为指向可读可写的用户级别页表,并且所有的页表连续排列在以PageTblBase为首地址的4MB (4096 × 1024 ) 的空间中。

接下来的工作是初始化所有页表中的PTE (第218行-228行)。由于总共有1024的2次幂 个PTE,也就是1024 × 1024 ,以便让循环进行1024 × 1024 次。 开始对es和edi的处理让es:edi 指向了页表段的首地址,即地址PageTblBase处,也是第一个页表的首地址!

第一个页表中的第一个PTE 被赋值为PG_P PG_USU PG_RWW, 不难理解,它表示此PTE指示的页首地址为0,并且是个可读可写的用户级别页!

同时也意味着第0个页表中的第0个PTE指示的页的首地址是0,于是线性地址0~0FFFh将被映射到物理地址0~0FFFh,即f(x) = x,其中0<=x<=0FFFh。

接下来的进行的循环初始化了剩下的所有页表中的PTE,将4GB空间的线性地址映射到相同的物理地址。如图所示

这样,页目录表和所有的页表都被初始化完毕。接下来就是正式启动分页机制了!

第230-231行 230 mov eax, PageDirBase 231 mov cr3, eax

第232-234行 设置cr3的PG位!


232		mov	eax, cr0
233		or	eax, 80000000h
234		mov	cr0, eax

这样分页机制就启动完成了!

编译运行!ok!

界面没有pmtest2试验显示的字符串,其他没啥变化!