实方式和保护方式切换的实例
<80X86汇编语言程序设计教程 杨季文>
程序逻辑功能是:先往内存地址110000H开始的256个字节写入数据(66h)然后以十六进制数的形式显示从内存地址110000H开始的256个字节的值。本实例指定该内存区域的目的仅仅是想说明切换到保护模式的必要性,因为在实模式下不能访问该指定内存区域,只有在保护模式下才能访问到该指定区域。
具体实现步骤是:
(1)作切换到保护方式的准备;
(2)切换到保护方式;
(3)向指定内存区域写入数据;
(4)把指定内存区域 的内容传送到位于常规内存的缓冲区中;
(5)切换回实模式;
(6)显示缓冲区内容。
源程序参考:
;16位偏移的段间直接转移指令的宏定义
jump macro selector,offsetv
db 0eah ;操作码
dw offsetv ;16位偏移
dw selector ;段值或者选择子
endm
;字符显示宏指令的定义
ECHOCH macro ascii
mov ah,2 ;中断功能号
mov dl,ascii ;需要显示的字符
int 21h
endm
;存储段描述符结构类型的定义
DESCRIPTOR struc
limit dw 0 ;段界限(0-15位)
basel dw 0 ;段基地址(0-15位)
basem db 0 ;段基地址(16-23位)
attributes dw 0 ;段属性
baseh db 0 ;段基址(24-31位)
DESCRIPTOR ends
;伪描述符结构类型的定义
PDESC struc
limit dw 0 ;16界限
base dd 0 ;基地址
PDESC ends
;常量定义
ATDW = 92h ;存在的可读数据段属性值
ATCE = 98h ;存在的只执行代码段属性值
.386p
dseg segment use16 ;16位段
GDT LABEL BYTE ;全局描述符表GDT
DUMMY DESCRIPTOR <> ;空描述表
CODE DESCRIPTOR <0FFFFh,,,ATCE,>
CODE_SEL = CODE-GDT ;代码段描述的选择子
DATAS DESCRIPTOR <0FFFFh,0h,11h,ATDW,0>
DATAS_SEL= DATAS-GDT ;源数据段描述符的选择子
DATAD DESCRIPTOR <0FFFFh,,,ATDW,>
DATAD_SEL =DATAD-GDT ;目标数据段描述符的选择子
GDTLEN =$-GDT ;GDT长度
VGDTR PDESC <GDTLEN-1,> ;伪描述符
BUFFERLEN = 256 ;缓冲区字节长度
BUFFER db BUFFERLEN dup(0) ;缓冲区
dseg ends
;代码段
cseg segment use16
assume cs:cseg,ds:dseg
start:
mov ax,DSEG
mov ds,ax
;准备要加载到GDTR的伪描述符
xor dx,dx
mov bx,16
mul bx ;段值*16
add ax,offset GDT ;段值*16+偏移=基地址,界限已在定义时设置完毕
adc dx,0
mov word ptr VGDTR.BASE,ax
mov word ptr VGDTR.BASE+2,dx
;设置代码段描述符
mov ax,cs
mul bx
mov CODE.BASEL,ax
mov CODE.BASEM,dl
mov CODE.BASEH,dh
;设置目标数据段描述符
mov ax,ds
xor dx,dx
mul bx ;段值*16
add ax,offset BUFFER ;段值*16+偏移=基地址
adc dx,0
mov DATAD.BASEL,ax
mov DATAD.BASEM,dl
mov DATAD.BASEH,dh
;加载GDTR
lgdt fword ptr ds:VGDTR
cli ;关中断
call ENABLEA20 ;打开地址线A20
;切换到保护模式
mov eax,cr0
or eax,1
mov cr0,eax
;清指令预取队列,并真正进入保护模式
jump <CODE_SEL>,<offset VIRTUAL>
VIRTUAL: ;现在开始在保护方式下
mov ax,DATAS_SEL
mov es,ax ;加载目标数据段描述符
mov eax,66666666h ;写入目标地点的数据
cld ;设置传输方向
xor di,di ;设置指针初始值
mov cx,BUFFERLEN/4 ;写入数据长度单位4字节
rep stosd ;将eax的值存入es:di所指内存中
mov ax,DATAS_SEL
mov ds,ax ;加载源数据段描述符
mov ax,DATAD_SEL
mov es,ax ;加载目标数据段描述符
cld
xor si,si ;设置指针初始值
xor di,di
mov cx,BUFFERLEN/4 ;设置4字节为单位的缓冲区长度
rep movsd
;切换回实模式
mov eax,cr0
and eax,0fffffffeh
mov cr0,eax
;清指令预取队列,进入实模式
jump <SEG REAL>,<offset REAL>
REAL: ;现在又回到实模式
call DISABLEA20 ;关闭地址线A20
sti ;开中断
mov ax,DSEG ;重置数据段寄存器
mov ds,ax
mov si,offset BUFFER
cld
mov bp,BUFFERLEN/16 ;每行显示16个字节,计算有几行
NEXTLINE:
mov cx,16
NEXTCH:
lodsb ;将ds:si指向内存的数据放到al中
mov bx,ax ;暂存al
shr al,4 ;取al的高4位显示
call TOASCII
ECHOCH al
mov ax,bx ;回复al,并显示低4位
call TOASCII
ECHOCH al
ECHOCH ' ' ;空格分割
loop NEXTCH
;换行
ECHOCH 0dh
ECHOCH 0ah
dec bp
jnz NEXTLINE
mov ax,4c00h
int 21h
;子程序功能:
;把al低4位的十六进制数转换成对应的ASCII,保存在AL
TOASCII proc
and al,0fh
add al,'0'
cmp al,'9'
seta dl ;如果大于'9',则设置dl=1,否则dl=0
movzx dx,dl
imul dx,7
add al,dl ;如果dl=1则al+37,否则+30
ret
TOASCII endp
;打开地址线A20
ENABLEA20 proc
push ax
in al,92h
or al,2
out 92h,al
pop ax
ret
ENABLEA20 endp
;关闭地址线A20
DISABLEA20 proc
push ax
in al,92h
and al,0fdh ;0fdh=not 20h
out 92h,al
pop ax
ret
DISABLEA20 endp
cseg ends
end start
源程序分析:
;16位偏移的段间直接转移指令的宏定义
jump macro selector,offsetv
db 0eah ;操作码
dw offsetv ;16位偏移
dw selector ;段值或者选择子
endm
直接的jmp分3种:
(1)short jump(短跳转)机器码 EB rel8 只能跳转到256字节的范围内 。
(2)near jump(近跳转)机器码 E9 rel 16/32 可跳至同一个段的范围内的地址。
(3)far jump(远跳转)机器码EA ptr 16:16/32 可跳至任意地址,使用48位/32位全指针。
在从实模式切换到保护模式之前,必须作必要的准备。准备工作的内容根据实际而定。最起码的准备工作是建立合适的全局描述符表,并使用 GDTR 指向该 GDT。因为在切换到保护方式时,至少要把代码段的选择子装载到 CS,所以 GDT 中至少含有代码段的描述符。
(通常,由实模式切换到保护模式的准备工作还应包含建立中断描述符表。但本实例没有建立中断描述符表。为此,要求整个过程在关中断的情况下进行;要求不使用软中断指令;假设不发生任何异常。否则会导致系统崩溃。)
从本实例源程序可见,全局描述符表 GDT 仅有四个描述符:第一个是空描述符;第二个是代码段描述符;第三个和第四个分别为源数据段及目标数据段描述符。本实例各描述符中的段界限是在定义时设置的,并且除伪描述符 VGDTR 中的界限按 GDT 的实际长度设置外,各使用的存储段描述符的界限都规定为 0FFFFH。另外,描述符中的段属性也根据所描述段的类型被预置,各属性的定义在包含文件 386SCD.INC 中均有说明。从属性值可知,这三个段都是 16 位段。
由于在切换到保护方式后就要引用 GDT,所以在切换到保护方式前必须装载 GDTR。实例中使用如下指令装载
GDTR:LGDTFWORD PTR VGDTR
GDTR寄存器结构如下:
与我们定义的PDESC(伪描述符结构体类型一致)。
该指令的功能是把存储器中的伪描述符 VGDTR 装入到全局描述符表寄存器 GDTR 中。伪描述符 VGDTR 的结构如前所述结构类型PDESC 所示,低字是以字节位单位的全局描述符表段的界限,高双字为描述符表段的线性基地址(本实例不启用分页机制,所以线性地址等同于物理地址)。本实例中未涉及到局部描述符表及中断描述符表。
反汇编代码:
(2)由实模式切换到保护模式
在做好准备后,从实模式切换到保护模式并不难。原则上只要把控制寄存器CR0 中的PE 位置1 即可。本实例采用如下三条指令设置PE位:
;切换到保护模式
mov eax,cr0
or eax,1
mov cr0,eax
模式下代码段的选择子,所以在取指令之前得把代码段的选择子装入CS。为此,紧接着这三条指令,安排一条如下所示的段间转移指令:JUMP16 Code_Sel,<OFFSETVirtual>
这条段间转移指令在实模式下被预取并在保护方式下被执行。利用这条段间转移指令可把保护模式下代码段的选择子装入CS,同时也刷新指令预取队列。从此真正进入保护模式。
(3)由保护模式切换到实模式
在80386上,从保护模式切换到实模式的过程类似于从实模式切换到保护模式。原则上只要把控制寄存器CR0中的PE位清0即可。实际上,在此之后也要安排一条段间转移指令,一方面清指令预取队列,另一方面把实模式下代码段的段值送CS。这条段间转移指令在保护方式下被预取并在实模式下被执行。
(4)保护模式下的数据传送
首先,把源数据段和目标数据段的选择子装入DS 和ES 寄存器,这两个描述符已在实模式下设置好,把选择子装入段寄存器就意味着把包括基地址在内的段信息装入到了段描述符高速缓冲寄存器。然后设置指针寄存器SI 和DI 的初值,也设置计数器CX 的初值。根据预置的段属性,在保护方式下,代码段也仅是16 位段,串操作指令只使用16 位的SI、DI和 CX等寄存器。最后利用串操作指令实施传送。
(5).打开和关闭地址线A20
PC 及其兼容机的第 21根地址线(A20)较特殊,计算机系统中一般安排一个“门”控制该地址线是否有效。为了访问地址在1M 以上的存储单元,应先打开控制地址线A20 的“门”。这种设置与实模式下只使用最低端的1M 字节存储空间有关,与处理器是否工作在实模式或保护方式无关,即使在关闭地址线A20 时,也可进入保护模式。如何打开和关闭地址线A20 与计算机系统的具体设置有关。
在纯DOS下的运行结果:
《Orange's 一个操作系统的实现》学习笔记--保护模式理论初步(一)
《Orange's 一个操作系统的实现》学习笔记--保护模式理论初步(二)
实践参考:
《Orange's 一个操作系统的实现》学习笔记--实践认识保护模式
注:Linux下汇编与Windows汇编格式有所不同,Linux采用的是nasm编译器,建议先看nasm手册。
希望能互相交流学习