实例二的逻辑功能是,以十六进制数和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
SEG
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位代码段。