1、 理论知识参考"《80X86汇编语言程序设计教程》二十二 分页管理机制与虚拟8086模式"。演示分页机制实例:初始化页目录表和部分页表;启用分页管理机制;关闭分页管理机制等。逻辑功能:在屏幕上显示一条表示已启用分页管理机制的提示信息。大体步骤是:在实模式下拷贝显示串程序的代码到预定义区域,转保护模式,初始化页目录和2个页表,开启分页机制,转入预定义区执行显示代码,然后关闭分页机制,重新回到实模式,程序终止。
2、 源代码
“386scd.asm”不再贴上来。参考"《80X86汇编语言程序设计教程》十三 任务内无特权级变换转移实例",演示代码如下:
1 ;DosTest.Asm
2 ;演示分页机制实例:初始化页目录表和部分页表;启用分页管理机制;关闭分页管理机制等。
3 ;逻辑功能:在屏幕上显示一条表示已启用分页管理机制的提示信息
4
5
6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义
7 .386p
8
9 ;------------------------------------------
10 ;常量值
11 PL = 001B ;存在属性位P值
12 RWR = 000B ;R/W属性位值,读/执行
13 RWW = 010B ;R/W属性位值,读/写/执行
14 USS = 000B ;U/S属性位值,系统级
15 USU = 100B ;U/S属性位值,用户级
16 ;
17 PDT_AD = 00200000h ;页目录表所在物理页地址
18 PT0_AD = 00202000h ;页表0所在物理页的地址
19 PT1_AD = 00201000h ;页表1所在物理页的地址
20 ;
21 PhVB_AD = 000b8000h ;对应视频缓冲区物理地址
22 LoVB_AD = 000f0000h ;程序使用的视频缓冲区逻辑地址
23 MpVB_AD = 00301000h ;线性地址0b8000h所映射的物理地址
24 PhSC_AD = 00303000h ;部分演示代码所在内存的物理地址
25 LoSC_AD = 00402000h ;部分演示代码的逻辑地址
26
27
28 ;------------------------------------------
29 ;全局描述符表GDT
30 GDTSeg segment para use16
31 GDT label byte
32 ;空描述符
33 dummy DESCRIPTOR<>
34
35 ;规范数据段描述符,存在的可读写数据段
36 Normal DESCRIPTOR<0ffffh,0,0,ATDW,0>
37 Normal_Sel = Normal - GDT
38
39 ;页目录表所在段描述符(在保护方式下初始化用)
40 PDTable DESCRIPTOR<0fffh,PDT_AD and 0ffffh,PDT_AD shr 16,ATDW,0>
41 PDT_Sel = PDTable - GDT
42
43 ;页表0所在段描述符(在保护方式下初始化用)
44 PTable0 DESCRIPTOR<0fffh,PT0_AD and 0ffffh,PT0_AD shr 16,ATDW,0>
45 PT0_Sel = PTable0 - GDT
46
47 ;页表1所在段描述符(在保护方式下初始化用)
48 PTable1 DESCRIPTOR<0fffh,PT1_AD and 0ffffh,PT1_AD shr 16,ATDW,0>
49 PT1_Sel = PTable1 - GDT
50
51 ;逻辑上的显示缓冲区所在段描述符
52 LoVideoB DESCRIPTOR<3999,LoVB_AD and 0ffffh,LoVB_AD shr 16,ATDW,0>
53 LoVideoB_Sel = LoVideoB - GDT
54
55 ;逻辑上的部分演示代码所在段描述符
56 LoCode DESCRIPTOR<SCodeLen - 1,LoSC_AD and 0ffffh,LoSC_AD shr 16,ATCE,0>
57 LoCode_Sel = LoCode - GDT
58
59 ;预定内存区域(用于部分演示代码)所在段的描述符
60 TPSCode DESCRIPTOR<SCodeLen - 1,PhSC_AD and 0ffffh,PhSC_AD shr 16,ATDW,>
61 TPSCode_Sel = TPSCode - GDT
62
63 ;以下描述符需要动态初始化
64 EFFGDT label byte
65
66 ;临时代码段描述符(16位段,DPL = 0,RPL = 0)
67 TempCode DESCRIPTOR<0ffffh,TempCodeSeg,,ATCE,>
68 TempCode_Sel = TempCode - GDT
69
70 ;演示任务代码段描述符
71 DemoCode DESCRIPTOR<DemoCodeLen - 1,DemoCodeSeg,,ATCE,>
72 DemoCode_Sel = DemoCode - GDT
73
74 ;演示任务数据段描述符
75 DemoData DESCRIPTOR<DemoDataLen - 1,DemoDataSeg,,ATDW,>
76 DemoData_Sel = DemoData - GDT
77
78 ;在初始化时要移动的代码段描述符(移动时作为只读数据对待)
79 SCode DESCRIPTOR<SCodeLen - 1,SCodeSeg,,ATDR,>
80 SCode_Sel = SCode - GDT
81
82 ;GDT中需要初始化基地址的描述符个数
83 GDTNum = ($ - EFFGDT)/(size DESCRIPTOR)
84
85 ;GDT段长度
86 GDTLen = $ - GDT
87 GDTSeg ends
88
89 ;------------------------------------------
90 ;这部分代码在初始化时被复制到预定内存区域
91 ;其功能是在屏幕上显示提示信息
92 SCodeSeg segment para use16
93 assume cs:SCodeSeg,ds:DemoDataSeg
94 SBegin:
95 mov al,LoVideoB_Sel
96 mov es,ax
97 mov di,0
98 mov ah,17h
99 mov cx,MessLen
100 s1:
101 lodsb
102 stosw
103 loop s1
104 JUMP16 DemoCode_Sel,<offset Demo3>
105 MLen = $ - SBegin
106 SCodeLen = $ - SCodeSeg
107 SCodeSeg ends
108
109 ;------------------------------------------
110 ;演示任务数据段
111 DemoDataSeg segment para use16
112 Mess db 'Page is ok!'
113 MessLen = $ - Mess
114 DemoDataLen = $ - DemoDataSeg
115 DemoDataSeg ends
116
117 ;------------------------------------------
118 ;演示任务的代码段
119 DemoCodeSeg segment para use16
120 assume cs:DemoCodeSeg
121 DemoBegin:
122 ;初始化页目录表,保护模式未开启分页机制情况下
123 ;先把全部表项置为无效
124 mov ax,PDT_Sel
125 mov es,ax
126 xor di,di
127 mov cx,1024 ;1024个表项
128 xor eax,eax
129 rep stosd ;每项为4B
130 ;再置表项0和表项1
131 ;用户级可读/写/执行存在页
132 mov dword ptr es:[0],PT0_AD or (USU + RWW + PL)
133 mov dword ptr es:[4],PT1_AD or (USU + RWW + PL)
134 ;
135 mov ax,PT0_Sel ;初始化页表0
136 mov es,ax
137 xor di,di ;无效表项值
138 mov cx,1024
139 xor eax,eax ;物理地址从0开始
140 ;系统级可读/写/执行存在页
141 or eax,USS + RWW + PL
142 ;先全部置成直接对应等地址的物理页
143 Demo1:
144 stosd
145 add eax,1000h ;物理地址增加1000h
146 loop Demo1
147 ;再特别设置两个表项
148 ;线性地址PhVB_AD(对应视频缓冲区物理地址)转换为物理地址Mp_VB
149 mov di,(PhVB_AD shr 12) * 4
150 mov dword ptr es:[di],MpVB_AD + USS + RWW + PL
151 ;线性地址LoVB_AD转换为物理地址PhVB_AD(对应视频缓冲区物理地址)
152 mov di,(LoVB_AD shr 12) * 4
153 mov dword ptr es:[di],PhVB_AD + USU + RWR + PL
154 ;
155 mov ax,PT1_Sel ;初始化页表1
156 mov es,ax
157 xor di,di ;无效表项值
158 mov cx,1024
159 mov eax,400000h ;物理地址从400000h开始
160 Demo2:
161 stosd ;先把全部表项置成无效
162 add eax,1000h
163 loop Demo2
164 ;再特别设置1项
165 ;线性地址LoSC_AD转换为物理地址PhSC_AD(对应视部分演示代码物理地址)
166 mov di,((LoSC_AD shr 12) and 3ffh) * 4
167 mov dword ptr es:[di],PhSC_AD + USU + RWR + PL
168 ;
169 ;将页目录表所在物理页地址装入CR3
170 mov eax,PDT_AD
171 mov cr3,eax
172 ;启用分页机制
173 mov eax,cr0
174 or eax,80000000h
175 mov cr0,eax
176 ;此部分代码段线性地址等于物理地址
177 jmp short PageE
178 PageE:
179 mov ax,DemoData_Sel
180 mov ds,ax
181 mov si,offset Mess
182 ;转位于较大线性地址处的代码执行
183 JUMP16 LoCode_Sel,<offset SBegin>
184 Demo3:
185 mov eax,cr0
186 and eax,7fffffffh ;关闭分页机制
187 mov cr0,eax
188 jmp short PageD
189 PageD:
190 JUMP16 TempCode_Sel,ToDOS
191 DemoCodeLen = $ - DemoCodeSeg
192 DemoCodeSeg ends
193
194 ;------------------------------------------
195 ;临时代码段
196 TempCodeSeg segment para use16
197 assume cs:TempCodeSeg
198 Virtual:
199 ;在保护模式下
200 cld ;为演示在启用分页机制后执行位于
201 mov ax,SCode_Sel ;较高线性地址空间中的代码作准备
202 mov ds,ax
203 mov ax,TPSCode_Sel
204 mov es,ax
205 mov si,offset SBegin
206 mov di,si
207 mov cx,MLen ;把部分演示代码复制到预定的内存
208 rep movsb
209 ;转演示代码段
210 JUMP16 DemoCode_Sel,DemoBegin
211 ToDOS:
212 ;演示结束后准备返回实模式
213 mov ax,Normal_Sel
214 mov ds,ax
215 mov es,ax
216 mov eax,cr0
217 and eax,0fffffffeh
218 mov cr0,eax
219 JUMP16 <seg Real>,<offset Real>
220 TempCodeSeg ends
221
222 ;------------------------------------------
223 ;实模式下的初始化代码和数据
224 RCodeSeg segment para use16
225 VGDTR PDESC<GDTLen - 1,>
226 assume cs:RCodeSeg,ds:RCodeSeg
227 start:
228 push cs
229 pop ds
230 cld
231 ;初始化GDT
232 call INIT_GDT
233 call ENABLEA20
234 ;装载GDTR和切换到保护模式
235 lgdt fword ptr VGDTR
236 cli
237 mov eax,cr0
238 or eax,1
239 mov cr0,eax
240 JUMP16 <TempCode_Sel>,<offset Virtual>
241 Real:
242 ;又回到实模式
243 call DISABLEA20
244 sti
245 mov ax,4c00h
246 int 21h
247 ;------------------------------------------
248 ;初始化全局描述符表的子程序
249 ;(1)把定义时预置的段值转换成32位段基地址并置入描述符内相应字段
250 ;(2)初始化为GDTR准备的伪描述符
251 INIT_GDT proc near
252 push ds
253 mov ax,GDTSeg
254 mov ds,ax
255 mov cx,GDTNum ;初始化描述符的个数
256 mov si,offset EFFGDT ;开始偏移
257 assume si:ptr DESCRIPTOR
258 INITG:
259 mov ax,[si].BaseL ;取出预置的段值
260 movzx eax,ax ;扩展到32位
261 shl eax,4
262 shld edx,eax,16 ;分解到2个16位寄存器
263 mov [si].BaseL,ax ;置入描述符相应字段
264 mov [si].BaseM,dl
265 mov [si].BaseH,dh
266 add si,size DESCRIPTOR ;调整到下一个描述符
267 loop INITG
268 assume si:nothing
269 pop ds
270 ;
271 mov bx,16 ;初始化为GDTR准备的伪描述符
272 mov ax,GDTSeg
273 mul bx
274 mov word ptr VGDTR.Base,ax
275 mov word ptr VGDTR.Base + 2,dx
276 ret
277 INIT_GDT endp
278
279 ;打开地址线A20号
280 ENABLEA20 proc
281 push ax
282 in al,92h
283 or al,2
284 out 92h,al
285 pop ax
286 ret
287 ENABLEA20 endp
288
289 ;关闭地址线A20号
290 DISABLEA20 proc
291 push ax
292 in al,92h
293 and al,0fdh
294 out 92h,al
295 pop ax
296 ret
297 DISABLEA20 endp
298
299 RCodeSeg ends
300 end start
3、 关于源代码的几处说明
1) 计算段长度时保持惯有错误
2) USS和USU两个常量定义的值刚好写反
3) 开关A20地址线之前在模式切换读取高端内存时使用过,之后一直没用,因为代码全部使用的低端内存
4、 测试效果
5、 测试说明
1) 整体说明
页目录被置物理页码00200H,页表有2张,页表0被置物理页码00202H而页表1被置物理页码00201。页目录初始化时所有页表项全部置为无效,然后初始化索引为0和1的页表项,使分别对应页表0和页表1。在页表0中存有线性地址00000000H~003FFFFFH到物理地址的映射关系,而页表1中存有线性地址00400000H~007FFFFFH到物理地址的映射关系。页表0和页表1初始化时,所有页表项对应线性地址被映射为相同的物理地址,页表1有两个页表项例外,及000B8H线性地址页码映射的不再是物理页码000B8H,为了测试逻辑地址与线性地址的映射,特意将线性地址000B8H映射到了物理页码00301H,而物理地址页码000B8H使用逻辑页码000F0H访问,它就是实际访问视频输出缓存区物理地址(0B8000H)时所使用到的线性地址页码。页表1同样如此,只有一个页表项意外,就是线性地址页码00402H的页,它被映射到了物理地址页00303H,这个页就是部分演示代码所在内存的物理页,在跳转切入来显示信息时,使用的线性地址页就是00402H,它实际*问的物理页是00303H。
2) 部分演示代码的移动
为了说明地址转换机制,特意指定了一个物理内存区域,在映射表中分配它的映射关系,从而在代码中实现使用逻辑地址对它成功进行访问。预定义区域在00303000H(物理地址)开始的内存中,由于使用了高端内存,所以须开启地址线A20,并在初始化时移动代码到预定义区域,这里采用了别名技术。
3) 映射表的初始化
映射表初始化必须在保护模式下,开启分页机制之前进行。这里使用了1张目录表,2张页表,共12KB。在初始化时,地址计算“mov di,(PhVB_AD shr 12) * 4”中PhVB_AD右移12位得到线性地址页的页码,右移以后取低10位作为页表项索引,乘以4得到页表字节偏移。这里的页码为000b8H,高10位为0,所以没有做取低10位的操作,直接乘以4。而“mov di,((LoSC_AD shr 12) and 3ffh) * 4”做了取低10位的操作。
4) 切换分页管理机制
将CR0的PG位(31位)置1或清0来实现切换,须注意,在开启分页管理机制时,线性地址和物理地址没有必然联系,而在关闭分页管理机制时,线性地址就等于物理地址。在切换前后,一个连续的代码段在不同的机制下要求被联系执行,那么这部分代码段必须得保证在保护模式下线性地址也是物理地址。当然,这里其实采用像模式切换时那样的转移来清预取指令同样是可行的。
5) 页级保护的演示
页目录表中对页表0和页表1的表项使用了“用户级可读/写/执行存在”页属性,在页表0中,对线性地址空间页PhVB_AD使用了“系统级可读/写/执行存在”页属性,所以组合属性是“系统级可读/写/执行存在”。由于整个程序在CPL = 0上运行,属于系统级,这些属性限制实际上是没有作用的,但是采用CPL = 3的代码去访问这个页,必然造成页故障。