实模式与保护模式切换

四.实模式与保护模式切换实例
本文介绍两个实现实模式与保护模式切换的实例,通过他们说明如何实现实模式与保护模式的切换, 也说明保护模式下的80386及其编程。
<一>演示实模式和保护模式切换的实例(实例一)
实例一的逻辑功能是,以十六进制数的形式显示从内存地址110000h开始的256个字节的值。本实例指定该内存区域的目的仅仅是想说明切换到保护模式的必要性,因为在实模式下不能访问该指定内存区域,只有在保护模式下才能访问到该指定区域。
本实例的具体实现步骤是:(1)作切换到保护方式的准备;(2)切换到保护方式;(3)把指定内存区域的内容传送到位于常规内存的缓冲区中;(4)切换回实模式;(5)显示缓冲区内容。
1.包含文件
386保护模式汇编语言程序用到的包含文件如下所示,该包含文件在后面的程序中还要用到。
;名称:386scd.inc
;功能:符号常量等的定义
;----------------------------------------------------------------------------
;ifndef __386scd_inc
;__386scd_inc equ 1
;----------------------------------------------------------------------------
.386p
;----------------------------------------------------------------------------
;打开a20地址线
;----------------------------------------------------------------------------
enablea20 macro
push ax
in al,92h
or al,00000010b
out 92h,al
pop ax
endm
;----------------------------------------------------------------------------
;关闭a20地址线
;----------------------------------------------------------------------------
disablea20 macro
push ax
in al,92h
and al,11111101b
out 92h,al
pop ax
endm
;----------------------------------------------------------------------------
;16位偏移的段间直接转移指令的宏定义(在16位代码段中使用)
;----------------------------------------------------------------------------
jump16 macro selector,offset
db 0eah ;操作码
dw offset ;16位偏移量
dw selector ;段值或段选择子
endm
;----------------------------------------------------------------------------
;32位偏移的段间直接转移指令的宏定义(在32位代码段中使用)
;----------------------------------------------------------------------------
comment
jump32 macro selector,offset
db 0eah ;操作码
dd offset
dw selector ;段值或段选择子
endm

;-------------------------------------------------
jump32 macro selector,offset
db 0eah ;操作码
dw offset
dw 0
dw selector ;段值或段选择子
endm
;----------------------------------------------------------------------------
;16位偏移的段间调用指令的宏定义(在16位代码段中使用)
;----------------------------------------------------------------------------
call16 macro selector,offset
db 9ah ;操作码
dw offset ;16位偏移量
dw selector ;段值或段选择子
endm
;----------------------------------------------------------------------------
;32位偏移的段间调用指令的宏定义(在32位代码段中使用)
;----------------------------------------------------------------------------
comment
call32 macro selector,offset
db 9ah ;操作码
dd offset
dw selector ;段值或段选择子
endm

;-------------------------------------------------
call32 macro selector,offset
db 9ah ;操作码
dw offset
dw 0
dw selector ;段值或段选择子
endm
;----------------------------------------------------------------------------
;存储段描述符结构类型定义
;----------------------------------------------------------------------------
desc struc
limitl dw 0 ;段界限(bit0-15)
basel dw 0 ;段基地址(bit0-15)
basem db 0 ;段基地址(bit16-23)
attributes db 0 ;段属性
limith db 0 ;段界限(bit16-19)(含段属性的高4位)
baseh db 0 ;段基地址(bit24-31)
desc ends
;----------------------------------------------------------------------------
;门描述符结构类型定义
;----------------------------------------------------------------------------
gate struc
offsetl dw 0 ;32位偏移的低16位
selector dw 0 ;选择子
dcount db 0 ;双字计数
gtype db 0 ;类型
offseth dw 0 ;32位偏移的高16位
gate ends
;----------------------------------------------------------------------------
;伪描述符结构类型定义(用于装入全局或中断描述符表寄存器)
;----------------------------------------------------------------------------
pdesc struc
limit dw 0 ;16位界限
base dd 0 ;32位基地址
pdesc ends
;----------------------------------------------------------------------------
;任务状态段结构类型定义
;----------------------------------------------------------------------------
tss struc
trlink dw 0 ;链接字段
dw 0 ;不使用,置为0
tresp0 dd 0 ;0级堆栈指针
trss0 dw 0 ;0级堆栈段寄存器
dw 0 ;不使用,置为0
tresp1 dd 0 ;1级堆栈指针
trss1 dw 0 ;1级堆栈段寄存器
dw 0 ;不使用,置为0
tresp2 dd 0 ;2级堆栈指针
trss2 dw 0 ;2级堆栈段寄存器
dw 0 ;不使用,置为0
trcr3 dd 0 ;cr3
treip dd 0 ;eip
treflag dd 0 ;eflags
treax dd 0 ;eax
trecx dd 0 ;ecx
tredx dd 0 ;edx
trebx dd 0 ;ebx
tresp dd 0 ;esp
trebp dd 0 ;ebp
tresi dd 0 ;esi
tredi dd 0 ;edi
tres dw 0 ;es
dw 0 ;不使用,置为0
trcs dw 0 ;cs
dw 0 ;不使用,置为0
trss dw 0 ;ss
dw 0 ;不使用,置为0
trds dw 0 ;ds
dw 0 ;不使用,置为0
trfs dw 0 ;fs
dw 0 ;不使用,置为0
trgs dw 0 ;gs
dw 0 ;不使用,置为0
trldtr dw 0 ;ldtr
dw 0 ;不使用,置为0
trtrip dw 0 ;调试陷阱标志(只用位0)
triomap dw $+2 ;指向i/o许可位图区的段内偏移
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 ;可用386任务状态段类型值
at386cgate equ 8ch ;386调用门类型值
at386igate equ 8eh ;386中断门类型值
at386tgate equ 8fh ;386陷阱门类型值
;----------------------------------------------------------------------------
;dpl值说明
;----------------------------------------------------------------------------
dpl0 equ 00h ;dpl=0
dpl1 equ 20h ;dpl=1
dpl2 equ 40h ;dpl=2
dpl3 equ 60h ;dpl=3
;----------------------------------------------------------------------------
;rpl值说明
;----------------------------------------------------------------------------
rpl0 equ 00h ;rpl=0
rpl1 equ 01h ;rpl=1
rpl2 equ 02h ;rpl=2
rpl3 equ 03h ;rpl=3
;----------------------------------------------------------------------------
;iopl值说明
;----------------------------------------------------------------------------
iopl0 equ 0000h ;iopl=0
iopl1 equ 1000h ;iopl=1
iopl2 equ 2000h ;iopl=2
iopl3 equ 3000h ;iopl=3
;----------------------------------------------------------------------------
;其它常量值说明
;----------------------------------------------------------------------------
d32 equ 40h ;32位代码段标志
gl equ 80h ;段界限以4k为单位标志
til equ 04h ;ti=1(局部描述符表标志)
vmfl equ 00020000h ;vmf=1
vmflw equ 0002h
ifl equ 00000200h ;if=1
rfl equ 00010000h ;rf=1(重启动标志,为1表示忽略调试故障)
rflw equ 0001h
ntl equ 00004000h ;nt=1
;----------------------------------------------------------------------------
;分页机制使用的常量说明
;----------------------------------------------------------------------------
pl equ 1 ;页存在属性位
rwr equ 0 ;r/w属性位值,读/执行
rww equ 2 ;r/w属性位值,读/写/执行
uss equ 0 ;u/s属性位值,系统级
usu equ 4 ;u/s属性位值,用户级
;----------------------------------------------------------------------------
;endif

2.实例源程序
实例一的源程序如下所示:
;名称:asm1.asm
;功能:演示实方式和保护方式切换(切换到16位代码段)
;----------------------------------------------------------------------------
include 386scd.inc
;----------------------------------------------------------------------------
;字符显示宏指令的定义
;----------------------------------------------------------------------------
echoch macro ascii
mov ah,2
mov dl,ascii
int 21h
endm
;----------------------------------------------------------------------------
dseg segment use16 ;16位数据段
;----------------------------------------------------------------------------
gdt label byte ;全局描述符表
dummy desc <> ;空描述符
code desc <0ffffh,,,atce,,> ;代码段描述符
datas desc <0ffffh,0,11h,atdw,,> ;源数据段描述符
datad desc <0ffffh,,,atdw,,> ;目标数据段描述符
;----------------------------------------------------------------------------
gdtlen = $-gdt ;全局描述符表长度
vgdtr pdesc ;伪描述符
;----------------------------------------------------------------------------
code_sel = code-gdt ;代码段选择子
datas_sel = datas-gdt ;源数据段选择子
datad_sel = datad-gdt ;目标数据段选择子
;----------------------------------------------------------------------------
buflen = 256 ;缓冲区字节长度
buffer db buflen dup(0) ;缓冲区
;----------------------------------------------------------------------------
dseg ends ;数据段定义结束
;----------------------------------------------------------------------------
cseg segment use16 ;16位代码段
assume cs:cseg,ds:dseg
;----------------------------------------------------------------------------
start proc
mov ax,dseg
mov ds,ax
;准备要加载到gdtr的伪描述符
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 ;代码段开始偏移为0
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
;加载gdtr
lgdt qword ptr vgdtr
cli ;关中断
enablea20 ;打开地址线a20
;切换到保护方式
mov eax,cr0
or eax,1
mov cr0,eax
;清指令预取队列,并真正进入保护方式
jump16 code_sel,
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 ;设置4字节为单位的缓冲区长度
repz movsd ;传送
;切换回实模式
mov eax,cr0
and al,11111110b
mov cr0,eax
;清指令预取队列,进入实方式
jump16 ,
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,

这条段间转移指令在实模式下被预取并在保护方式下被执行。利用这条段间转移指令可把保护模式下代码段的选择子装入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.实例二源程序
实例二的源程序如下所示:
;名称:asm2.asm
;功能:演示实方式和保护方式切换(切换到32位代码段)
;----------------------------------------------------------------------------
include 386scd.inc
;----------------------------------------------------------------------------
dseg segment use16 ;16位数据段
;----------------------------------------------------------------------------
gdt label byte ;全局描述符表
dummy desc <> ;空描述符
normal desc <0ffffh,,,atdw,,> ;规范段描述符
code32 desc ;32位代码段描述符
code16 desc <0ffffh,,,atce,,> ;16位代码段描述符
datas desc ;源数据段描述符
datad desc <3999,8000h,0bh,atdw,,> ;显示缓冲区描述符
stacks desc ;堆栈段描述符
;----------------------------------------------------------------------------
gdtlen = $-gdt ;全局描述符表长度
vgdtr pdesc ;伪描述符
;----------------------------------------------------------------------------
savesp dw ? ;用于保存sp寄存器
savess dw ? ;用于保存ss寄存器
;----------------------------------------------------------------------------
normal_sel = normal-gdt ;规范段描述符选择子
code32_sel = code32-gdt ;32位代码段选择子
code16_sel = code16-gdt ;16位代码段选择子
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 ;16位代码段
assume cs:cseg1,ds:dseg
;----------------------------------------------------------------------------
start proc
mov ax,dseg
mov ds,ax
;准备要加载到gdtr的伪描述符
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
;设置32位代码段描述符
mov ax,cseg2
mul bx
mov word ptr code32.basel,ax
mov byte ptr code32.basem,dl
mov byte ptr code32.baseh,dh
;设置16位代码段描述符
mov ax,cseg3
mul bx
mov word ptr code16.basel,ax ;代码段开始偏移为0
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
;加载gdtr
lgdt qword ptr vgdtr
cli ;关中断
enablea20 ;打开地址线a20
;切换到保护方式
mov eax,cr0
or al,1
mov cr0,eax
;清指令预取队列,并真正进入保护方式
jump16 code32_sel,
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,
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,

从该宏指令的定义可知,该转移指令含48位指针,其高16位是16位代码段的选择子,低32位是16位代码段的入口偏移。该指令在32位方式下预取并执行。由于在32位方式下执行,所以要使用48位指针。
(3)显示指定内存区域的内容
在本实例中,采用直接写显示缓冲区的方法实现显示。假设显示缓冲区的开始物理地址是0b8000h, 3号文本显示模式,在屏幕的第一行进行显示。
3.特别说明
本实例在保护方式下使用了涉及堆栈操作的指令,因此建立了一个16位的保护模式下的堆栈段。
本实例仍作了大量的简化处理。如:没有建立idt和ldt等,各特权级均是0。也没有采用分页管理机制。
从本实例的gdt中可见,两个数据段的界限都是根据实际大小而设置的。从源程序代码段cseg3可见,在切换到实模式之前,把一个指向似乎没有用的数据段的描述符norm
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值