这篇还是上一篇的加强版. 上一篇我们漏说了很多东西, 包括我们在内存中使用了4M+4K的内存来存放页目录和页表. 这肯定是有问题的. 不行的. 那么我们应该动态的获取系统内存分配页表大小. 另外上一篇虽然对0b8000h做了下映射, 估计还是不过瘾.因为数据映射了下, 而且是没有开启分页之前的, 我们应该再接再厉. 将代码也可以随便映射..

 

关于这个分页机制的话, 上一篇已经说的比较清楚了, 这里不再罗嗦. 主要上一篇我们总共分配了4M+4K的内存, 这篇我们动态的获取内存大小. 那么就可以动态改动页表的大小了, 注意了. 这里可以动态修改页表的数量, 但是页目录是动不得的, 必须给4K. 我刚开始给页目录也动态的搞一下. 结果老是出错!

其他就好像没有什么问题了.. 这篇代码也不算复杂了, 相对前面的来说! 所以这里我主要说说代码的逻辑.当然, 上来首先获取了本地内存的大小. 这个是调用15h中断搞定的. 看截图上打印出来的就知道, 我用的Bochs虚拟出来的内存是512的, 基本有510M是可以给OS使用的. 然后把获取到的内存存放在dwMemSize变量中. 然后跳入保护模式..

我这里不和以前一样搞了, 搞那么多得段太麻烦了. 直接使用一个全局的数据段, 宽度4G, 一个代码段宽度4G, 一个堆栈段4G.用起来多舒服.. 我看Windows也是这么搞的嘛.. 然后进入保护模式以后直接从16过渡段跳到32位段. 然后开始搞分页.

分页的话. 还是和上一篇一样先初始化页目录, 页目录根据实际内存填写, 剩下的为NULL. 然后填写页目录, 页目录的话也是根据实际内存填写的. 这样就可以节约很多内存了.. 将所有的线性地址映射到等同的物理地址上. 然后单独修改0b8000h将其映射到线性地址0处.. 打印了一个字符串..

然后Copy了两段代码分别搞到100000h和102000h处. 然后调用100000h的函数, 这个没有撒稀奇的, 主要是下面. 我们修改页表将100000h处的线性地址映射到102000h处的物理地址上. 这样我们再次调用100000h的函数就调用到另外一个Test2函数了. 非常暴力!

还是和以前一样. 有图有真相!

PageTest.rar

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
;============================================================================
	;演示保护模式下分页机制, 所有的线性地址对应物理地址
	;编译选项请参见 makefile  TAB = 8
;============================================================================
	.686p
	Include pm.inc
	option casemap:none
PageDirBase		equ	200000h	; 页目录开始地址: 2M
Stack_Len		equ	300000h	;堆栈段开始地址
PageTblBase		equ	201000h	; 页表开始地址:  2M+4K
Test1Base		equ	100000h	;测试任务1的地址
Test2Base		equ	102000h	;测试任务2的地址
;============================================================================

GdtSeg		Segment use16		;全局描述符表
;					;段基址		;段界限	;属性
Dummy:			Descriptor	0, 		0, 	0				;空的描述符
Normal:			Descriptor	0, 	        0ffffh,	DA_DRW 				;规范段描述符
g_DataDesc:		Descriptor	0,     	       0fffffh, DA_DRW or DA_LIMIT_4K		;全局4G数据段
g_CodeDesc:		Descriptor	0,     	       0fffffh, DA_C or DA_32 or DA_LIMIT_4K	;32位全局代码段
g_StackDesc:		Descriptor	0,     	       0fffffh, DA_DRW or DA_32 or DA_LIMIT_4K	;32位全局堆栈段
g_CodeTempDesc:		Descriptor 	0, 		0ffffh, DA_C				;非一致代码段16位
GDTLen			equ		$ - GdtSeg						;GDT长度
;----------------------------------------------------------------------------
GDT_Ptr		word	GDTLen-1				;VGDT
		dword	0
_RegSp		word	?					;用于保存SS:SP
_RegSs		word	?
;----------------------------------------------------------------------------
SzString	byte	"This is a historic moment, we have entered into the paging mode!", 0
SzMemInof	byte	"BaseAddressL   BaseAddressH   LengthLow   LengthHigt   Type", 0
SzMemSize	byte	"RAM Size:", 0
SzTest1		byte	"I'm Test1!", 0
SzTest2		byte	"I'm Test2!", 0

Buf		byte	256	dup (0)
dwStMemSize	dword	0
dwPageDirSize	dword	?					;页目录大小
dwPageTblSize	dword	?					;页表大小
dwMemSize	dword	0					;系统内存大小
;----------------------------------------------------------------------------
NormalSelector		equ	Normal 			- GdtSeg			;规范段选择子
g_DataSelector		equ	g_DataDesc		- GdtSeg  			;全局数据段
g_CodeTempSelector	equ	g_CodeTempDesc 		- GdtSeg			;临时代码段选择子
g_StackSelector		equ	g_StackDesc		- GdtSeg			;全局堆栈段
g_CodeSelector		equ	g_CodeDesc		- GdtSeg			;全局32位代码段
GdtSeg		Ends
;============================================================================
	;执行分段工作
;============================================================================
CodePageSeg	Segment use32
;----------------------------------------------------------------------------
	;显示一个字符串
ShowString	Proc	uses esi edi _lpStr:dword, _dwCoord:dword, _dwStringLen:dword
	
	mov	esi, _lpStr
	mov	edi, _dwCoord
	cld
	mov	ecx, _dwStringLen
	
@@:	lodsb
	mov	ah, 0eh
	stosw
	loop	@b
	ret
ShowString 	Endp
;----------------------------------------------------------------------------
Test1		Proc					;测试任务1
	
	mov	esi, GdtSeg
	shl	esi, 4
	add	esi, offset SzTest1
	mov	ecx, sizeof SzTest1
	mov	edi, 16 * 160 + 5 * 2			;16行5列
	
@@:	lodsb
	mov	ah, 0fh
	stosw
	loop	@b
	ret
Test1Len	equ	$ - Test1
Test1 		Endp
;----------------------------------------------------------------------------
Test2		Proc					;测试任务2
	mov	esi, GdtSeg
	shl	esi, 4
	add	esi, offset SzTest2
	mov	ecx, sizeof SzTest2
	mov	edi, 17 * 160 + 5 * 2			;16行5列
	
@@:	lodsb
	mov	ah, 0fh
	stosw
	loop	@b
	ret
Test2Len	equ	$ - Test2
Test2 		Endp
;----------------------------------------------------------------------------	
PageTest	Proc					;分页测试
	
	xchg	bx, bx
;----------------------------------------------------------------------------
	;复制两段代码到不同的地址上, 用于做页映射演示	
	mov	esi, CodePageSeg
	shl	esi,4
	add	esi, offset Test1
	mov	ecx, Test1Len
	mov	edi, Test1Base
	rep	movsb
	
	mov	esi, CodePageSeg
	shl	esi, 4
	add	esi, offset Test2
	mov	ecx, Test2Len
	mov	edi, Test2Base
	rep	movsb
;----------------------------------------------------------------------------	
	mov	eax, Test1Base
	call	eax					;调用Test1
;----------------------------------------------------------------------------
	;求的Test1所在的地址, 然后修改为Test2的映射
	mov	esi, Test1Base
	shr	esi, 12
	and	esi, 3ffh				;10BIT 1111111111
	shl	esi, 2					;页表一个项是4字节
	add	esi, PageTblBase
	
	mov	eax, Test2Base
	or	eax,  PG_P or PG_USU or PG_RWW
	mov	dword ptr [esi], eax			;将Test2的页写入Test刚对应的页
	
	mov	eax, cr3				;刷新cr3
	mov	cr3, eax
;----------------------------------------------------------------------------
	;神奇的事情发生了, 居然会调用同一地址, 转到了不同的位置上	
	mov	eax, Test1Base
	call	eax
	ret
PageTest 	Endp
;----------------------------------------------------------------------------
InitPage	Proc			
	local	_dwDataBase:dword
	
	mov	eax, g_DataSelector
	mov	ds, ax
;----------------------------------------------------------------------------
	;这里根据内存大小来分配页表, 就不用占据4M内存了
	mov	ebx, 400000h				;4M = 4096 * 1024一个页表的大小
	
	mov	eax, GdtSeg
	shl	eax, 4
	mov	dword ptr ss:[_dwDataBase], eax
	lea	ecx, dwMemSize
	add	eax, ecx
	mov	esi, dword ptr [eax]			;esi-->内存大小
	
	
	mov	eax, esi				;eax=内存大小
	cdq
	div	ebx
	or	edx, edx
	jz	@f
	inc	eax					;如果有余数就再增加一个页表
@@:
	mov	ecx, _dwDataBase
	add	ecx, offset dwPageDirSize
	mov	dword ptr ds:[ecx], eax			;存放页目录数
;----------------------------------------------------------------------------
	;为了简单, 所有的线性地址对应物理地址, 这里初始化页目录, 
	;虽然页表可以动态分配, 但是页目录还是固定的4K
	push	ds
	pop	es
	mov	edi, PageDirBase
	
	mov	ebx, eax
	mov	ecx, eax
	cld
	mov	eax, PageTblBase or PG_P or PG_USU or PG_RWW;属性:可读可写的用户页目录
@@:
	stosd
	add	eax, 4096				
	loop	@b
;----------------------------------------------------------------------------
	;初始化页表(1K个, 4M内存空间), 页表之间的跨度是4096个字节, 也就是1000h
	;因为线性地址的低12位的索引的最大限度就是4096, 所以跨度是这样的. 
	;这里根据实际的内存大小初始化页表
	mov	eax, _dwDataBase
	add	eax, offset dwPageDirSize
	mov	eax, dword ptr ds:[eax]
	
	mov	ebx, 1024
	mul	ebx					
	mov	ecx, eax				; PTE个数 = 页表个数 * 1024
	
	mov	eax, _dwDataBase
	add	eax, offset dwPageTblSize
	mov	dword ptr ds:[eax], ecx			;存起来, PTE个数
	mov	edi, PageTblBase
	mov	eax, PG_P  or PG_USU or PG_RWW
	
@@:	stosd
	add	eax, 4096
	loop	@b
;----------------------------------------------------------------------------
	;将物理地址b8000h映射到线性地址000000h上面, 一般修改映射都是修改页表
	mov	esi, PageTblBase
	
	mov	ecx, 0b8000h 
	or	ecx, PG_P  or PG_USU or PG_RWW		;属性:可读可写的用户页目录
	mov	dword ptr ds:[esi], ecx
;----------------------------------------------------------------------------	
	;开启分页
	mov	eax, PageDirBase			;装载页目录基址
	mov	cr3, eax
	
	mov	eax, cr0
	or	eax, 80000000h				;开启分页
	mov	cr0, eax
;----------------------------------------------------------------------------
	;打印一个字符串, 向线性地址0地址写, 写到了b8000h处
	mov	esi, _dwDataBase
	add	esi, offset SzString
	mov	edi, 15 * 160 + 5 * 2			;11行5列
	Invoke	ShowString, esi, edi, sizeof SzString
;----------------------------------------------------------------------------
	call	PageTest				;分页测试
;----------------------------------------------------------------------------	
	;返回实模式
	Jmp32	g_CodeTempSelector, _GoToProtect
InitPage 	Endp
CodePageSegLen		equ	$ - CodePageSeg		;长度
CodePageSeg	Ends
;============================================================================
	;16位段, 由实模式跳入
;============================================================================
g_Code16Seg	Segment	use16
_GoToProtect	Proc					;返回实模式
	mov	ax, NormalSelector			
	mov	fs, ax					;规范选择子
	mov	es, ax
	mov	ds, ax
	mov	ss, ax	
	
	
	mov	eax, cr0				;关PE位, 进入实模式
	and	al, 0feh
	and	eax, 7fffffffh				;关分页, 进入实模式
	mov	cr0, eax
	;刷新段选择子缓冲区, 退回实模式
	Jmp16	<seg StartCodeSeg >, < offset _RealProtect >
_GoToProtect 	Endp
;----------------------------------------------------------------------------
_ProtectEntry	Proc					;实模式跳入入口
	
	mov	ax, g_StackSelector
	mov	ss, ax
	mov	esp, Stack_Len				;32位段
	
	;16位-32位执行分页\
	mov	eax, g_CodeSelector
	push	eax
	mov	eax, CodePageSeg
	shl	eax, 4
	lea	ecx, InitPage
	add	eax, ecx
	push	eax
	byte	66h
	retf						;通过retf转换到32位段
	;Jmp16	g_CodePageSelector,_InitPage
;----------------------------------------------------------------------------	
_ProtectEntry 	Endp	
g_Code16Seg	Ends
;============================================================================
	;起始代码段初始化保护模式的各个结构, 然后跳入保护模式
;============================================================================
StartCodeSeg	Segment use16
_InitGdt	Proc	uses es				;初始化全局描述符表
	
	xor	eax, eax
	mov	ax, GdtSeg
	mov	es, ax					;es-->全局描述符表
;----------------------------------------------------------------------------
	shl	eax, 4					
	mov	dword ptr es:[GDT_Ptr+2], eax		;初始化VGDT描述符
;----------------------------------------------------------------------------	
	xor	eax, eax
	mov	ax, g_Code16Seg				;初始化十六位的代码段
	shl	eax, 4
	mov	word ptr es:[g_CodeTempDesc+2], ax	;段基址低位
	shr	eax, 16
	mov	byte ptr es:[g_CodeTempDesc+4], al	;段基址高地址低位
	mov	byte ptr es:[g_CodeTempDesc+7], ah	;段基址高地址高位	
;----------------------------------------------------------------------------
	lgdt	fword ptr es:[GDT_Ptr]			;装载GDT
;----------------------------------------------------------------------------
	ret
_InitGdt 	Endp
;;将al中的数字转成ASCII加上显示属性并在EAX中返回
_HexToAscii     Proc     uses ebx
 
        mov     bl, al
 
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h
        daa
        mov     ah, 0ah
        shl     eax, 16                                 ;转换低位          
 
        mov     al, bl
        shr     al, 4
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h                                 ;将高位转成ASCII
        daa
        mov     ah, 0ah                                 ;属性
 
        ret
_HexToAscii     Endp
;============================================================================
        ;显示一条信息_lpStr:字符串首地址
        ;_dwXY开始显示地址
;============================================================================
_PrintMessage   Proc    uses esi edi  _lpStr:dword, _dwXY:dword
 
        mov     esi, _lpStr
        xor     ecx, ecx
;----------------------------------------------------------------------------
@@:     mov     al, byte ptr ds:[esi]
        inc     esi
        inc     ecx
        or      al, al
        jnz     @b                                      ;ecx == 字符串长度
        dec     ecx
;----------------------------------------------------------------------------
        mov     esi, _lpStr
        mov     edi, _dwXY
@@:     lodsb
        mov     ah, 0ch                                 ;属性
        stosw
        loop    @b
        ret
_PrintMessage   Endp
;============================================================================
        ;换行, _dwValue传递当前的行列号, 计算好在Eax中返回下一行开始
;============================================================================
_PrintLn        Proc    uses ebx  _dwValue:dword
        mov     eax, _dwValue
        mov     bl, 160
        div     bl
        and     eax, 0FFh
        inc     eax
        mov     bl, 160
        mul     bl
        ret
_PrintLn        Endp
;----------------------------------------------------------------------------
	;eax要显示的Int类型值, es:edi指向需要显示的位置
_ShowInt	Proc	uses ebx			;显示一个Int类型的值
	
	mov	ebx, eax
	shr	eax, 24
	call	_HexToAscii
	stosd
	mov	eax, ebx
	shr	eax, 16
	call	_HexToAscii
	stosd
	mov	eax, ebx
	shr	eax, 8
	call	_HexToAscii
	stosd
	mov	eax, ebx
	call	_HexToAscii
	stosd
	ret
_ShowInt 	Endp
;----------------------------------------------------------------------------
_GetMemSize	Proc	uses es	ds			;获取内存大小

;----------------------------------------------------------------------------	
	;调用BIOS中断获取内存信息
	xor	ebx, ebx				;后续值
	mov	ax, GdtSeg
	mov	es, ax
	lea	di, Buf					;es:di-->地址范围描述符结构

@@:	mov	ax, 0e820h				;功能号
	mov	ecx, 20					;结构大小, 一般只需要20字节
	mov	edx, 0534d4150h				;SMAP校验值
	int	15h					;调用BIOS中断获取内存信息	
	jc	@f					;出错了
	add	di, 20
	inc	dword ptr es:[dwStMemSize]		;结构++
	or	ebx, ebx
	jne	@b
;----------------------------------------------------------------------------
@@:	cmp	dword ptr es:[dwStMemSize], 0		;内存结构为0吗?
	jz	_ret
	
	mov	ax, GdtSeg
	mov	ds, ax
	mov	ax, 0b800h
	mov	es, ax
	mov	eax, 5*160+5*2				;5行5列
	lea	ecx, SzMemInof
	Invoke	_PrintMessage, ecx, eax			;显示一条字符串, 准备打印内存信息
;----------------------------------------------------------------------------
	xor	esi, esi
	xor	edi, edi
	;显示内存信息
	mov	di, (6 * 160) + (5 * 2)			;6行5列	
	lea	si, Buf
	
_While:	mov	eax, 0a780a30h				;打印0x
	stosd
	mov	eax, dword ptr [si]
	call	_ShowInt				;显示BaseAddressL
;============================================================================
	;显示整体内存信息
	add	di, 10					;中间间隔若干空格
	mov	eax, 0a780a30h				;打印0x
	stosd
	mov	eax, dword ptr [si+4]
	call	_ShowInt				;显示BaseAddressH
;----------------------------------------------------------------------------	
	add	di, 10					;中间间隔3个位置
	mov	eax, 0a780a30h				;打印0x
	stosd
	mov	eax, dword ptr [si+8]
	call	_ShowInt				;显示LengthLow	
;----------------------------------------------------------------------------	
	add	di, 4					;中间间隔若干空格
	mov	eax, 0a780a30h				;打印0x
	stosd
	mov	eax, dword ptr [si+0ch]
	call	_ShowInt				;显示LengthHigh
;----------------------------------------------------------------------------	
	add	di, 6					;中间间隔若干空格
	mov	eax, 0a780a30h				;打印0x
	stosd
	mov	eax, dword ptr [si+10h]
	call	_ShowInt				;显示Type
;----------------------------------------------------------------------------	
	cmp	dword ptr [si+10h], 1			;类型是操作系统可以使用的吗?
	jnz	@f
	mov	eax, dword ptr ds:[si]
	add	eax, dword ptr ds:[si+8]
	cmp	eax, dword ptr ds:[dwMemSize]
	jb	@f
	mov	dword ptr ds:[dwMemSize], eax		;内存大小
@@:	
	add	si, 20
	Invoke	_PrintLn, edi				;换行
	mov	di, ax
	add	di, 5 * 2				;从下一行的5开始显示
	dec	dword ptr ds:[dwStMemSize]
	cmp	dword ptr ds:[dwStMemSize], 0
	jnz	_While
;----------------------------------------------------------------------------
	lea	ecx, SzMemSize
	Invoke	_PrintMessage, ecx, edi			;显示内存大小字符串
	
	add	edi, (sizeof SzMemSize) * 2
	mov	eax, dword ptr ds:[dwMemSize]		;显示内存大小
	call	_ShowInt
;----------------------------------------------------------------------------	
_ret:	ret
_GetMemSize 	Endp
;----------------------------------------------------------------------------
Jmain		Proc
	
	call	_InitGdt				;初始化GDT全局描述符
	call	_GetMemSize				;获取内存大小
;----------------------------------------------------------------------------	
	mov	ax, GdtSeg
	mov	ds, ax
	
	mov	ds:[_RegSs], ss
	mov	ds:[_RegSp], sp				;保存SS:SP
	cli
	_EnableA20					;关中断开A20地址线
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax				;开启分段, 进入保护模式
;----------------------------------------------------------------------------
	Jmp16	g_CodeTempSelector, <offset _ProtectEntry>;跳入保护模式
	
Jmain 		Endp
;----------------------------------------------------------------------------
_RealProtect	Proc                                    ;返回保护模式
 
        mov     ax, GdtSeg
        mov     ds, ax
        lss	sp, dword ptr ds:[_RegSp]		;恢复SS:SP
        
        _DisableA20                                     ;关A20地址线, 开中断
        sti
        mov     ax, 4c00h
        int     21h
_RealProtect	Endp
StartCodeSeg	Ends

End		Jmain