四.实模式与保护模式切换实例
本文介绍两个实现实模式与保护模式切换的实例,通过他们说明如何实现实模式与保护模式的切换, 也说明保护模式下的80386及其编程。
<一>演示实模式和保护模式切换的实例(实例一)
实例一的逻辑功能是,以十六进制数的形式显示从内存地址110000H开始的256个字节的值。本实例指定该内存区域的目的仅仅是想说明切换到保护模式的必要性,因为在实模式下不能访问该指定内存区域,只有在保护模式下才能访问到该指定区域。
本实例的具体实现步骤是:(1)作切换到保护方式的准备;(2)切换到保护方式;(3)把指定内存区域的内容传送到位于常规内存的缓冲区中;(4)切换回实模式;(5)显示缓冲区内容。
1.包含文件
386保护模式汇编语言程序用到的包含文件如下所示,该包含文件在后面的程序中还要用到。
.386P
EnableA20 MACRO
push ax
in al,92h
or al,00000010b
out 92h,al
pop ax
ENDM
DisableA20 MACRO
push ax
in al,92h
and al,11111101b
out 92h,al
pop ax
ENDM
JUMP16 MACRO Selector
,Offset
DB 0eah
DW Offset
DW Selector
ENDM
JUMP32>
JUMP32 MACRO Selector
,Offset
DB 0eah
DW OFFSET
DW 0
DW Selector
ENDM
CALL16 MACRO Selector
,Offset
DB 9ah
DW Offset
DW Selector
ENDM
CALL32>
CALL32 MACRO Selector
,Offset
DB 9ah
DW Offset
DW 0
DW Selector
ENDM
Desc
STRUC
LimitL
DW 0
BaseL
DW 0
BaseM
DB 0
Attributes
DB 0
LimitH
DB 0
BaseH
DB 0
Desc
ENDS
Gate
STRUC
OffsetL
DW 0
Selector
DW 0
DCount
DB 0
GType
DB 0
OffsetH
DW 0
Gate
ENDS
PDesc
STRUC
Limit
DW 0
Base
DD 0
PDesc
ENDS
TSS
STRUC
TRLink
DW 0
DW 0
TRESP0
DD 0
TRSS0
DW 0
DW 0
TRESP1
DD 0
TRSS1
DW 0
DW 0
TRESP2
DD 0
TRSS2
DW 0
DW 0
TRCR3
DD 0
TREIP
DD 0
TREFlag
DD 0
TREAX
DD 0
TRECX
DD 0
TREDX
DD 0
TREBX
DD 0
TRESP
DD 0
TREBP
DD 0
TRESI
DD 0
TREDI
DD 0
TRES
DW 0
DW 0
TRCS
DW 0
DW 0
TRSS
DW 0
DW 0
TRDS
DW 0
DW 0
TRFS
DW 0
DW 0
TRGS
DW 0
DW 0
TRLDTR
DW 0
DW 0
TRTrip
DW 0
TRIOMap
DW $+2
TSS
ENDS
ATDR
EQU 90h
ATDW
EQU 92h
ATDWA
EQU 93h
ATCE
EQU 98h
ATCER
EQU 9ah
ATCCO
EQU 9ch
ATCCOR
EQU 9eh
ATLDT
EQU 82h
ATTaskGate
EQU 85h
AT386TSS
EQU 89h
AT386CGate
EQU 8ch
AT386IGate
EQU 8eh
AT386TGate
EQU 8fh
DPL0
EQU 00h
DPL1
EQU 20h
DPL2
EQU 40h
DPL3
EQU 60h
RPL0
EQU 00h
RPL1
EQU 01h
RPL2
EQU 02h
RPL3
EQU 03h
IOPL0
EQU 0000h
IOPL1
EQU 1000h
IOPL2
EQU 2000h
IOPL3
EQU 3000h
D32
EQU 40h
GL
EQU 80h
TIL
EQU 04h
VMFL
EQU 00020000h
VMFLW
EQU 0002h
IFL
EQU 00000200h
RFL
EQU 00010000h
RFLW
EQU 0001h
NTL
EQU 00004000h
PL
EQU 1
RWR
EQU 0
RWW
EQU 2
USS
EQU 0
USU
EQU 4
2.实例源程序
实例一的源程序如下所示:
INCLUDE 386SCD
.INC
EchoCh
MACRO ascii
mov ah,2
mov dl,ascii
int 21h
ENDM
DSEG
SEGMENT USE16
GDT
LABEL BYTE
DUMMY Desc
<>
Code Desc
<0ffffh,,,ATCE
,,>
DataS Desc
<0ffffh,0,11h,ATDW
,,>
DataD Desc
<0ffffh,,,ATDW
,,>
GDTLen
= $-GDT
VGDTR PDesc
<GDTLen
-1,>
Code_Sel
= Code-GDT
DataS_Sel
= Datas
-GDT
DataD_Sel
= DataD
-GDT
BufLen
= 256
Buffer
DB BufLen
DUP(0)
DSEG
ENDS
CSEG
SEGMENT USE16
ASSUME CS:CSEG
,DS:DSEG
Start
PROC
mov ax,DSEG
mov ds,ax
mov bx,16
mul bx
add ax,OFFSET GDT
adc dx,0
mov WORD PTR VGDTR
.Base
,ax
mov WORD PTR VGDTR
.Base
+2,dx
mov ax,cs
mul bx
mov WORD PTR Code.BaseL
,ax
mov BYTE PTR Code.BaseM
,dl
mov BYTE PTR Code.BaseH
,dh
mov ax,ds
mul bx
add ax,OFFSET Buffer
adc dx,0
mov WORD PTR DataD
.BaseL
,ax
mov BYTE PTR DataD
.BaseM
,dl
mov BYTE PTR DataD
.BaseH
,dh
lgdt QWORD PTR VGDTR
cli
EnableA20
mov eax,cr0
or eax,1
mov cr0,eax
JUMP16 Code_Sel
,<OFFSET Virtual
>
Virtual:
mov ax,DataS_Sel
mov ds,ax
mov ax,DataD_Sel
mov es,ax
cld
xor si,si
xor di,di
mov cx,BufLen
/4
repz movsd
mov eax,cr0
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real
>,<OFFSET Real
>
Real:
DisableA20
sti
mov ax,DSEG
mov ds,ax
mov si,OFFSET Buffer
cld
mov bp,BufLen
/16
NextLine: mov cx,16
NextCh: lodsb
push ax
shr al,1
call ToASCII
EchoCh
al
pop ax
call ToASCII
EchoCh
al
EchoCh
' '
loop NextCh
EchoCh
0dh
EchoCh
0ah
dec bp
jnz NextLine
mov ax,4c00h
int 21h
Start
ENDP
ToASCII
PROC
and al,0fh
add al,90h
daa
adc al,40h
daa
ret
ToASCII
ENDP
CSEG
ENDS
END Start
3.关于实例步骤的注释
在源程序的开头首先包含了文件“386SCD.INC”,在此包含文件中定义了保护模式程序设计要用到的一些结构、宏及常量。下面对各实现步骤作些说明。
(1)切换到保护方式的准备工作
在从实模式切换到保护模式之前,必须作必要的准备。准备工作的内容根据实际而定。最起码的准备工作是建立合适的全局描述符表,并使用GDTR指向该GDT。因为在切换到保护方式时,至少要把代码段的选择子装载到CS,所以GDT中至少含有代码段的描述符。
从本实例源程序可见,全局描述符表GDT仅有四个描述符:第一个是空描述符;第二个是代码段描述符;第三个和第四个分别为源数据段及目标数据段描述符。本实例各描述符中的段界限是在定义时设置的,并且除伪描述符VGDTR中的界限按GDT的实际长度设置外,各使用的存储段描述符的界限都规定为0FFFFH。另外,描述符中的段属性也根据所描述段的类型被预置,各属性的定义在包含文件386SCD.INC中均有说明。从属性值可知,这三个段都是16位段。
由于在切换到保护方式后就要引用GDT,所以在切换到保护方式前必须装载GDTR。实例中使用如下指令装载GDTR:
LGDT QWORD PTR VGDTR
该指令的功能是把存储器中的伪描述符VGDTR装入到全局描述符表寄存器GDTR中。伪描述符VGDTR的结构如前所述结构类型PDESC所示,低字是以字节位单位的全局描述符表段的界限,高双字为描述符表段的线性基地址(本实例不启用分页机制,所以线性地址等同于物理地址)。本实例中未涉及到局部描述符表及中断描述符表,后面的文章将作详细说明。
(2)由实模式切换到保护模式
在做好准备后,从实模式切换到保护模式并不难。原则上只要把控制寄存器CR0中的PE位置1即可。本实例采用如下三条指令设置PE位:
mov eax,cr0
or eax,1
mov cr0,eax
实际情况要比这复杂些。执行上面的三条指令后,处理器转入保护模式,但CS中的内容还是实模式下代码段的段值,而不是保护模式下代码段的选择子,所以在取指令之前得把代码段的选择子装入CS。为此,紧接着这三条指令,安排一条如下所示的段间转移指令:
JUMP16 Code_Sel
,<OFFSET Virtual
>
这条段间转移指令
在实模式下被预取并在保护方式下被执行。利用这条段间转移指令可把保护模式下代码段的选择子装入CS,同时也刷新指令预取队列。从此真正进入保护模式。
(3)由保护模式切换到实模式
在80386上,从保护模式切换到实模式的过程类似于从实模式切换到保护模式。原则上只要把控制寄存器CR0中的PE位清0即可。实际上,在此之后也要安排一条段间转移指令,一方面清指令预取队列,另一方面把实模式下代码段的段值送CS。
这条段间转移指令在保护方式下被预取并在实模式下被执行。
(4)保护模式下的数据传送
首先,把源数据段和目标数据段的选择子装入DS和ES寄存器,这两个描述符已在实模式下设置好,把选择子装入段寄存器就意味着把包括基地址在内的段信息装入到了段描述符高速缓冲寄存器。然后设置指针寄存器SI和DI的初值,也设置计数器CX的初值。根据预置的段属性,在保护方式下,代码段也仅是16位段,串操作指令只使用16位的SI、DI和CX等寄存器。最后利用串操作指令实施传送。
(5)显示缓冲区中的内容
由于缓冲区在常规内存中,所以在实模式下根据要求按十六进制显示其内容是很容易理解的,这里就不再多说。
4.内存映象
在源程序中没有把GDT作为一个单独的段对待,但在进入保护方式后,它是一个独立的段。从对代码段和源数据段描述符所赋的基地址和段界限值可见,代码段和数据段有部分覆盖。尽管这样做不利于代码和数据的安全,但如果需要,这样做是可行的。本实例运行时的内存映象如下图所示。
5.特别说明
作为第一个实模式和保护模式切换的例子,本实例作了大量的简化处理。
通常,由实模式切换到保护模式的准备工作还应包含建立中断描述符表。但本实例没有建立中断描述符表。为此,要求整个过程在关中断的情况下进行;要求不使用软中断指令;假设不发生任何异常。否则会导致系统崩溃。
本实例未使用局部描述符表,所以在进入保护模式后没有设置局部描述符表寄存器LDTR。为此,在保护模式下使用的段选择子都指定GDT中的描述符。
本实例未定义保护模式下的堆栈段,GDT中没有堆栈段描述符,在保护模式下没有设置SS,所以在保护方式下没有涉及堆栈操作的指令。
本实例各描述符特权级DPL和各选择子的请求特权级RPL均为0,在保护方式下运行时的当前特权级CPL也是0。
本实例没有采用分页管理机制,也即CR0中的PG位为0,线性地址就是存储单元的物理地址。
6.打开和关闭地址线A20
PC及其兼容机的第21根地址线(A20)较特殊,计算机系统中一般安排一个 “门”控制该地址线是否有效。为了访问地址在1M以上的存储单元,应先打开控制地址线A20的“门”。这种设置与实模式下只使用最低端的1M字节存储空间有关,与处理器是否工作在实模式或保护方式无关,即使在关闭地址线A20时,也可进入保护模式。
如何打开和关闭地址线A20与计算机系统的具体设置有关。在本文中介绍的包含文件386SCD.INC中定义了两个宏,打开地址线A20的宏EnableA20和关闭地址线A20的宏DisableA20,此两个宏指令在一般的PC兼容机上都是可行的。
<二>演示32位代码段和16位代码段切换的实例(实例二)
实例二的逻辑功能是,以十六进制数和ASCII字符两种形式显示从内存地址100000H开始的16个字节的内容。
从功能上看,本实例类似于实例一,但在实现方法上却有了改变,它更能反映出实模式和保护模式切换的情况。具体实现步骤是:(1)作切换到保护方式的准备;(2)切换到保护方式的一个32位代码段;(3)把指定内存区域的内容以字节为单位,转换成对应的十六进制数的ASCII码,并直接填入显示缓冲区实现显示;(4)再变换到保护方式下的一个16位代码段;(5)把指定内存区域的内容直接作为ASCII码填入显示缓冲区中实现显示;(6)切换回实模式。
1.实例二源程序
实例二的源程序如下所示:
INCLUDE 386SCD
.INC
DSEG
SEGMENT USE16
GDT
LABEL BYTE
DUMMY Desc
<>
Normal Desc
<0ffffh,,,ATDW
,,>
Code32 Desc
<C32Len
-1,,,ATCE
,D32
,>
Code16 Desc
<0ffffh,,,ATCE
,,>
DataS Desc
<DataLen
-1,0,10h,ATDR
,,>
DataD Desc
<3999,8000h,0bh,ATDW
,,>
Stacks Desc
<StackLen
-1,,,ATDW
,,>
GDTLen
= $-GDT
VGDTR PDesc
<GDTLen
-1,>
SaveSP
DW ?
SaveSS
DW ?
Normal_Sel
= Normal
-GDT
Code32_Sel
= Code32
-GDT
Code16_Sel
= Code16
-GDT
DataS_Sel
= Datas
-GDT
DataD_Sel
= DataD
-GDT
Stacks_Sel
= Stacks
-GDT
DataLen
= 16
DSEG
ENDS
StackSeg
SEGMENT PARA STACK USE16
StackLen
= 256
DB StackLen
DUP(0)
StackSeg
ENDS
CSEG1
SEGMENT USE16 'REAL'
ASSUME CS:CSEG1
,DS:DSEG
Start
PROC
mov ax,DSEG
mov ds,ax
mov bx,16
mul bx
add ax,OFFSET GDT
adc dx,0
mov WORD PTR VGDTR
.Base
,ax
mov WORD PTR VGDTR
.Base
+2,dx
mov ax,CSEG2
mul bx
mov WORD PTR Code32
.BaseL
,ax
mov BYTE PTR Code32
.BaseM
,dl
mov BYTE PTR Code32
.BaseH
,dh
mov ax,CSEG3
mul bx
mov WORD PTR Code16
.BaseL
,ax
mov BYTE PTR Code16
.BaseM
,dl
mov BYTE PTR Code16
.BaseH
,dh
mov ax,ss
mov WORD PTR SaveSS
,ax
mov WORD PTR SaveSP
,sp
mov ax,StackSeg
mul bx
mov WORD PTR Stacks
.BaseL
,ax
mov BYTE PTR Stacks
.BaseM
,dl
mov BYTE PTR Stacks
.BaseH
,dh
lgdt QWORD PTR VGDTR
cli
EnableA20
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 Code32_Sel
,<OFFSET SPM32
>
ToReal:
mov ax,DSEG
mov ds,ax
mov sp,SaveSP
mov ss,SaveSS
DisableA20
sti
mov ax,4c00h
int 21h
Start
ENDP
CSEG1
ENDS
CSEG2
SEGMENT USE32 'PM32'
ASSUME CS:CSEG2
SPM32
PROC
mov ax,Stacks_Sel
mov ss,ax
mov esp,StackLen
mov ax,DataS_Sel
mov ds,ax
mov ax,DataD_Sel
mov es,ax
xor esi,esi
xor edi,edi
mov ecx,DataLen
cld
Next: lodsb
push ax
CALL ToASCII
mov ah,7
shl eax,16
pop ax
shr al,4
CALL ToASCII
mov ah,7
stosd
mov al,20h
stosw
loop Next
JUMP32 Code16_Sel
,<OFFSET SPM16
>
SPM32
ENDP
ToASCII
PROC
and al,00001111b
add al,30h
cmp al,39h
jbe Isdig
add al,7
IsDig: ret
ToASCII
ENDP
C32Len
= $
CSEG2
ENDS
CSEG3
SEGMENT USE16 'PM16'
ASSUME CS:CSEG3
SPM16
PROC
xor si,si
mov di,DataLen
*3*2
mov ah,7
mov cx,DataLen
AGain: lodsb
stosw
loop AGain
mov ax,Normal_sel
mov ds,ax
mov es,ax
mov ss,ax
mov eax,cr0
and al,11111110b
mov cr0,eax
jmp FAR PTR ToReal
SPM16
ENDP
CSEG3
ENDS
END Start
2.关于实现步骤的注释
(1)切换到保护模式的准备工作
建立全局描述符表,这里的全局描述符表含有两个16位数据段的描述符、一个16位代码段的描述符和一个16位的堆栈段描述符。此外,GDT中还有一个32位的代码段描述符,描述32位代码段,该描述符的属性字段中的D位为1。
(2)由实模式切换到保护模式
由实模式切换到保护模式32位代码段的方法与切换到16位代码段的方法相同。由保护模式16位代码段切换回实模式的方法与实例一相似。
在保护模式下,通过如下直接段间转移指令从32位代码段切换到16位代码段:
JUMP32 Code16_Sel
,<OFFSET SPM16
>
从该宏指令的定义可知,该转移指令含48位指针,其高16位是16位代码段的选择子,低32位是16位代码段的入口偏移。
该指令在32位方式下预取并执行。由于在32位方式下执行,所以要使用48位指针。
(3)显示指定内存区域的内容
在本实例中,采用直接写显示缓冲区的方法实现显示。假设显示缓冲区的开始物理地址是0B8000H, 3号文本显示模式,在屏幕的第一行进行显示。
3.特别说明
本实例在保护方式下使用了涉及堆栈操作的指令,因此建立了一个16位的保护模式下的堆栈段。
本实例仍作了大量的简化处理。如:没有建立IDT和LDT等,各特权级均是0。也没有采用分页管理机制。
从本实例的GDT中可见,两个数据段的界限都是根据实际大小而设置的。从源程序代码段CSEG3可见,在切换到实模式之前,把一个指向似乎没有用的数据段的描述符Normal的选择子装载到DS和ES。这是为什么呢?
实模式下段描述符高速缓冲寄存器的内容 | 段寄存器 | 段基地址 | 段界限(固定) | 段属性(固定) |
存在性 | 特权级 | 已存取 | 粒度 | 扩展方向 | 可读性 | 可写性 | 可执行 | 堆栈大小 | 一致特权 |
CS | 当前CS*16 | 0000FFFFH | Y | 0 | Y | B | U | Y | Y | Y | - | N |
SS | 当前SS*16 | 0000FFFFH | Y | 0 | Y | B | U | Y | Y | N | W | - |
DS | 当前DS*16 | 0000FFFFH | Y | 0 | Y | B | U | Y | Y | N | - | - |
ES | 当前ES*16 | 0000FFFFH | Y | 0 | Y | B | U | Y | Y | N | - | - |
FS | 当前FS*16 | 0000FFFFH | Y | 0 | Y | B | U | Y | Y | N | - | - |
GS | 当前GS*16 | 0000FFFFH | Y | 0 | Y | B | U | Y | Y | N | - | - |
在分段管理机制一文中已介绍过,每个段寄存器都配有段描述符高速缓冲寄存器,这些高速缓冲寄存器在实方式下仍发挥作用,只是内容上与保护模式下有所不同。如上表所示,其中“Y”表示“是”; “N”表示“否”;“B”表示字节;“U”表示向上扩展,“W”表示以字方式操作堆栈。段基地址仍是 32位,其值是相应段寄存器值(段值)乘以16,在把段值装载到段寄存器时刷新。由于其值是16位段值乘上16,所以在实模式下基地址实际上有效位只有20位。每个段的32位段界限都固定为0FFFFH,段属性的许多位也是固定的。所谓固定是指在实方式下不可设置这些属性值,只能继续沿用保护方式下所设置的值。因此,在准备结束保护模式回到实模式之前,要通过加载一个合适的描述符选择子到有关段寄存器,以使得对应段描述符高速缓冲寄存器中含有合适的段界限和属性。本实例GDT中的描述符Normal就是这样一个描述符,在返回实模式之前把对应选择子Normal_Sel加载到DS和ES就是此目的。由于SS段描述符中的内容已符合实模式的需要,所以尽管也改变了SS,但不需要重新加载SS(本实例中重新加载了SS,这除了稍增加运行时间外,并没有什么坏处)。16位代码段描述符中的内容也符合实模式的需要,所以在通过16位代码段返回实模式时,CS段描述符中的内容也符合实模式的要求。需要注意的是,不能从32位代码段返回实模式,这是因为无法实现从32位代码段返回时CS高速缓冲寄存器中的属性符合实模式的要求(实模式不能改变段属性)。顺便说以下,实例一中的描述符都是符合实模式要求的。段描述符高速缓冲寄存器中含有合适的段界限
4.关于32位代码段程序设计的说明
在32位代码段中,缺省的操作数大小是32位,缺省的存储单元地址大小是32位。由于串操作指令使用的指针寄存器是ESI和EDI,LOOP指令使用的计数器是ECX,所以,在代码段CSEG2中,为了使用串操作指令,对ESI和EDI等寄存器赋初值。请比较代码段CSEG3中的相关片段和实例一中的相关片段,它们是16位代码段。
参考资料 | 书 名 | 出 版 社 | 作 者 |
《保护方式下的80386及其编程》 | 清华大学出版社 | 周明德主编 |
《80X86汇编语言程序设计教程》 | 清华大学出版社 | 扬季文主编 |