DOS下内存分页程序的编写
一. 前言
首先,我们先要弄清楚80386编程中涉及到的三个地址概念:物理地址、逻辑地址和线性地址。
1.物理地址:本质上,一个物理地址是CPU插脚上可测量的电压。CPU通过就是物理地址来访问内存的。
2.逻辑地址:这是基于CUP内存分段来表示内存的一种方式。它通常表示为:XXXX:YYYYYYYY,这里XXXX称为段选择子,而YYYYYYYY是针对段选择子所选择的段的偏移量。程序员在编程过程中接触的地址基本都是这个地址。
3.线性地址:将逻辑地址中的段基地址加上偏移量便形成了线性地址。在CPU内存不分页的情况下,线性地址就是物理地址。若进行了分页管理,线性地址则需要进行页表地址转换来得出相应的物理地址。
这三个地址的映射关系图如下:
二. 分页管理
80386的分段机制是不能关闭的。但是分页机制却可以通过控制寄存器CR0中的最高位PG位来控制。如果PG=1,分页机制生效,把线性地址转换为物理地址。如果PG=0,分页机制无效,线性地址就直接作为物理地址。
分页机制把线性地址空间和物理地址空间分别划分为大小相同的块。这样的块称之为页(80386中,页的大小为4K字节)。80386通过页目录/页表将线性地址和物理地址对照起来。下图就是80386的这种转换关系:
80386有一个页目录和最多1024个页表,页目录有CR3寄存器来指定。页表/页目录由4字节的项组成,每项的格式如下:
写好80386的分页程序的关键就是需要先在内存中建立起页目录表/页表关系,将页目录表的物理地址赋给CR3寄存器。然后打开CR0的PG位(PG=1)就进入了内存分页了。
三. 实例
简略介绍了一下内存以及分页机制,下面我们来写一个内存分页程序。我们把环境设在DOS下面。比照上面的页目录表/页表图,我们将要在内存中建立的页目录表/页表图绘制如下:
源代码如下:
.386P DESC STRUC LIMIT DW 0 BASEL DW 0 BASEM DB 0 ATTR DW 0 BASEH DB 0 DESC ENDS
REG STRUC LIMIT DW 0 BASE DD 0 REG ENDS
;************************** PDIR SEGMENT USE16 DD 00204001H ;dir 0 DD 1023 DUP (0) PDIR ENDS
PAGE1 SEGMENT USE16 DD 200001H ;page 0 DD 201001H ;page 1 DD 202001H ;page 2 DD 0B8001H ;page 3 DD 201H DUP (0) ;page 4 ~ 204h DD 205001H ;page 305h DD 400H - 205H DUP (0) ;page 206h~400h PAGE1 ENDS
PDATA SEGMENT USE16 PMSG DB 'CAN YOU SEE ME? :)', 0 PDATA ENDS
PSTACK SEGMENT USE16 DB 256 DUP (0) PSTACKLEN = 256 PSTACK ENDS
PCODE SEGMENT USE16 MOV AX, GDTDATA4SEL MOV DS, AX MOV AX, GDTDISP4SEL MOV ES, AX MOV SI, OFFSET PMSG MOV DI, 80 * 10 CLD
PNEXT: LODSB OR AL, AL JZ POVER MOV AH, 7 STOSW JMP PNEXT
POVER: RETF
PCODE ENDS
PTEMP SEGMENT USE16 MOV EAX, 00203000H MOV CR3, EAX
MOV EAX, CR0 OR EAX, 80000000H MOV CR0, EAX
JMP TEMP1 TEMP1:
MOV AX, GDTSTACK4SEL ;LINEAR ADDRESS: 2000H MOV SS, AX MOV SP, PSTACKLEN
DB 9AH DW 0 DW GDTCODE4SEL ;LINEAR ADDRESS: 1000H
;;;;;;;;;;;;; MOV EAX, CR0 AND EAX, 07FFFFFFFH MOV CR0, EAX
JMP TEMP2 TEMP2: MOV EAX, 0 MOV CR3,EAX
DB 0EAH DW OFFSET FROMTEMP DW GDTCODESEL PTEMP ENDS
;***************************
DATA SEGMENT USE16 GDTNULL DESC <0, 0, 0, 0, 0> GDTNULLSEL = 0
GDTDATA DESC <0FFFFH, 0, 0, 92H, 0> GDTDATASEL = OFFSET GDTDATA - OFFSET GDTNULL
GDTCODE DESC <0FFFFH, 0, 0, 98H, 0> GDTCODESEL = OFFSET GDTCODE - OFFSET GDTNULL
GDTSTACK DESC <0FFFFH, 0, 0, 92H, 0> GDTSTACKSEL = OFFSET GDTSTACK - OFFSET GDTNULL
GDTDISP DESC <0FFFFH, 8000H, 0BH, 92H, 0> GDTDISPSEL = OFFSET GDTDISP - OFFSET GDTNULL
;FROM GDTDATA0 DESC <1000H, 0, 0, 92H, 0> GDTDATA0SEL = OFFSET GDTDATA0 - OFFSET GDTNULL
GDTCODE0 DESC <1000H, 0, 0, 92H, 0> GDTCODE0SEL = OFFSET GDTCODE0 - OFFSET GDTNULL GDTCODE1 DESC <1000H, 0, 0, 98H, 0> GDTCODE1SEL = OFFSET GDTCODE1 - OFFSET GDTNULL
GDTTEMP0 DESC <1000H, 0, 0, 92H, 0> GDTTEMP0SEL = OFFSET GDTTEMP0 - OFFSET GDTNULL GDTTEMP1 DESC <1000H, 0, 0, 98H, 0> GDTTEMP1SEL = OFFSET GDTTEMP1 - OFFSET GDTNULL
GDTSTACK0 DESC <1000H, 0, 0, 92H, 0> GDTSTACK0SEL = OFFSET GDTSTACK0 - OFFSET GDTNULL
GDTPDIR0 DESC <1000H, 0, 0, 92H, 0> GDTPDIR0SEL = OFFSET GDTPDIR0 - OFFSET GDTNULL
GDTPAGE0 DESC <1000H, 0, 0, 92H, 0> GDTPAGE0SEL = OFFSET GDTPAGE0 - OFFSET GDTNULL
;TO GDTDATA2 DESC <1000H, 0, 30H, 92H, 0> GDTDATA2SEL = OFFSET GDTDATA2 - OFFSET GDTNULL
GDTCODE2 DESC <1000H, 1000H, 20H, 92H, 0> GDTCODE2SEL = OFFSET GDTCODE2 - OFFSET GDTNULL GDTCODE3 DESC <1000H, 1000H, 20H, 98H, 0> GDTCODE3SEL = OFFSET GDTCODE3 - OFFSET GDTNULL
GDTTEMP2 DESC <1000H, 5000H, 20H, 92H, 0> GDTTEMP2SEL = OFFSET GDTTEMP2 - OFFSET GDTNULL GDTTEMP3 DESC <1000H, 5000H, 20H, 98H, 0> GDTTEMP3SEL = OFFSET GDTTEMP3 - OFFSET GDTNULL
GDTSTACK2 DESC <1000H, 2000H, 20H, 92H, 0> GDTSTACK2SEL = OFFSET GDTSTACK2 - OFFSET GDTNULL
GDTPDIR2 DESC <1000H, 3000H, 20H, 92H, 0> GDTPDIR2SEL = OFFSET GDTPDIR2 - OFFSET GDTNULL
GDTPAGE2 DESC <1000H, 4000H, 20H, 92H, 0> GDTPAGE2SEL = OFFSET GDTPAGE2 - OFFSET GDTNULL
;VIR GDTDATA4 DESC <1000H, 0, 0, 92H, 0> GDTDATA4SEL = OFFSET GDTDATA4 - OFFSET GDTNULL
GDTCODE4 DESC <1000H, 1000H, 0, 98H, 0> GDTCODE4SEL = OFFSET GDTCODE4 - OFFSET GDTNULL
GDTSTACK4 DESC <1000H, 2000H, 0, 92H, 0> GDTSTACK4SEL = OFFSET GDTSTACK4 - OFFSET GDTNULL
GDTDISP4 DESC <1000H, 3000H, 0, 92H, 0> GDTDISP4SEL = OFFSET GDTDISP4 - OFFSET GDTNULL
GDTR REG <OFFSET GDTR - OFFSET GDTNULL, 0> DATA ENDS
STACK SEGMENT USE16 DB 256 DUP (0) STACKLEN = $ STACK ENDS
CODE SEGMENT USE16 ASSUME CS: CODE, DS:DATA, SS:STACK
START: MOV AX, DATA MOV DS, AX CLI MOV AX, STACK MOV SS, AX MOV SP, STACKLEN STI
MOV AX, DATA MOV BX, 10H MUL BX MOV GDTDATA.BASEL, AX MOV GDTDATA.BASEM, DL MOV GDTDATA.BASEH, DH
ADD AX, OFFSET GDTNULL ADC DX, 0
MOV WORD PTR GDTR.BASE, AX MOV WORD PTR GDTR.BASE + 2, DX
MOV AX, CODE MOV BX, 10H MUL BX MOV GDTCODE.BASEL, AX MOV GDTCODE.BASEM, DL MOV GDTCODE.BASEH, DH
MOV AX, STACK MOV BX, 10H MUL BX MOV GDTSTACK.BASEL, AX MOV GDTSTACK.BASEM, DL MOV GDTSTACK.BASEH, DH
MOV AX, PDATA MOV BX, 10H MUL BX MOV GDTDATA0.BASEL, AX MOV GDTDATA0.BASEM, DL MOV GDTDATA0.BASEH, DH
MOV AX, PCODE MOV BX, 10H MUL BX MOV GDTCODE0.BASEL, AX MOV GDTCODE0.BASEM, DL MOV GDTCODE0.BASEH, DH MOV GDTCODE1.BASEL, AX MOV GDTCODE1.BASEM, DL MOV GDTCODE1.BASEH, DH
MOV AX, PTEMP MOV BX, 10H MUL BX MOV GDTTEMP0.BASEL, AX MOV GDTTEMP0.BASEM, DL MOV GDTTEMP0.BASEH, DH MOV GDTTEMP1.BASEL, AX MOV GDTTEMP1.BASEM, DL MOV GDTTEMP1.BASEH, DH
MOV AX, PSTACK MOV BX, 10H MUL BX MOV GDTSTACK0.BASEL, AX MOV GDTSTACK0.BASEM, DL MOV GDTSTACK0.BASEH, DH
MOV AX, PDIR MOV BX, 10H MUL BX MOV GDTPDIR0.BASEL, AX MOV GDTPDIR0.BASEM, DL MOV GDTPDIR0.BASEH, DH
MOV AX, PAGE1 MOV BX, 10H MUL BX MOV GDTPAGE0.BASEL, AX MOV GDTPAGE0.BASEM, DL MOV GDTPAGE0.BASEH, DH
;;;;;;;;;;;;;;;;;;;; LGDT FWORD PTR GDTR
CLI
MOV EAX, CR0 OR EAX, 1 MOV CR0, EAX
DB 0EAH DW OFFSET PROTECT DW GDTCODESEL
PROTECT: MOV AX, GDTDATASEL MOV DS, AX MOV ES, AX
MOV AX, GDTSTACKSEL MOV SS, AX MOV SP, STACKLEN
;COPY PDATA to 200 000H MOV AX, GDTDATA0SEL MOV DS, AX MOV AX, GDTDATA2SEL MOV ES, AX MOV CX, 1000H MOV SI, 0 MOV DI, 0 CLD REP MOVSB
;COPY PCODE TO 201 000H MOV AX, GDTCODE0SEL MOV DS, AX MOV AX, GDTCODE2SEL MOV ES, AX MOV CX, 1000H MOV SI, 0 MOV DI, 0 CLD REP MOVSB
;COPY PTEMP TO 205 000H MOV AX, GDTTEMP0SEL MOV DS, AX MOV AX, GDTTEMP2SEL MOV ES, AX MOV CX, 1000H MOV SI, 0 MOV DI, 0 CLD REP MOVSB
;COPY PSTACK TO 202 000H MOV AX, GDTSTACK0SEL MOV DS, AX MOV AX, GDTSTACK2SEL MOV ES, AX MOV CX, 1000H MOV SI, 0 MOV DI, 0 CLD REP MOVSB
;COPY PDIR TO 203 000H MOV AX, GDTPDIR0SEL MOV DS, AX MOV AX, GDTPDIR2SEL MOV ES, AX MOV CX, 1000H MOV SI, 0 MOV DI, 0 CLD REP MOVSB
;COPY PAGE0 TO 204 000H MOV AX, GDTPAGE0SEL MOV DS, AX MOV AX, GDTPAGE2SEL MOV ES, AX MOV CX, 1000H MOV SI, 0 MOV DI, 0 CLD REP MOVSB
;;;;;;;COPY COMPLIETE;;;;;;;;;;; MOV AX, GDTPAGE2SEL MOV ES, AX
XOR EAX, EAX MOV AX, DATA MOV CL, 4 SHL EAX, CL AND EAX, 0FFFFF000H
MOV EDI, EAX OR EAX, 1
MOV CL, 12 SHR EDI, CL MOV CL, 2 SHL EDI, CL
MOV ES:[DI], EAX
;JMP TEMP DB 0EAH DW 0 DW GDTTEMP3SEL
FROMTEMP: MOV AX, GDTDATASEL MOV DS, AX MOV ES, AX MOV AX, GDTSTACKSEL MOV SS, AX MOV SP, STACKLEN
MOV EAX, CR0 AND EAX, 0FFFFFFFEH MOV CR0, EAX
DB 0EAH DW OFFSET REAL DW SEG REAL REAL: MOV AX, DATA MOV DS, AX MOV ES, AX MOV FS, AX MOV GS, AX
MOV AX, STACK MOV SS, AX MOV SP, STACKLEN
STI
MOV AH, 4CH INT 21H CODE ENDS END START
|
下面来分析一下这个程序:
1. 使用下面的代码来启动分页管理:
MOV EAX, CR0 OR EAX, 80000000H MOV CR0, EAX
JMP TEMP1 TEMP1:
|
在启用分页机制前,线性地址就是物理地址;在启用分页机制后,线性地址要通过分页机制的转换,才成为物理地址。尽管使用一条转移指令,可清除预取队列,但随后在取指令时使用的线性地址就要经过分页机制转换才成为物理地址。
为了顺利过渡,在启用分页机制之后的过渡代码段,仍要维持线性地址等同于物理地址。为了实现这一点:我们将启动分页这段代码放在205000H起的段中,并且将页表205H项指向地址205000H。其实仅仅做到这一点还是不够的,还有一个最重要的也是最容易忽略的:就是要将GDTR指向的地址等同于物理地址,上图中就是xxx000h所在的数据段。
2.当需要访问的物理地址(注意是物理地址)超过1M的偶数地址时,注意打开A20门。由于本文中200000H的第20位(从0开始)为0,所以不需要打开A20门;假如地址像300000H,则必须要打开A20门,否则所有的访问还是为200000H地址范围了。重申一遍:A20门仅仅和物理地址有关,与逻辑地址和线性地址没有任何关系。
四. 参考文献
1.《80X86汇编语言程序设计教程》,杨季文 等。