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 <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 ;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,<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 ;设置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兼容机上都是可行的。
 
2/2 首页 上一页 1 2 |