9.1 实地址模式与保护模式之间的切换
我们知道,IA-32计算机在加电或者Reset信号有效之后,首先进入实地址模式,执行BIOS程序,然后再进入保护模式,执行Windows环境下的程序。因此,IA-32 CPU在工作的时候,需要从实地址模式切换到保护模式。从实地址模式切换到保护模式,通常需要建立描述符表(descriptor table),设置控制寄存器CR0的PE位,如例9.1所示。
例9.1 IA-32 CPU从实地址模式切换到保护模式,然后又切换回实地址模式。
;
;IA-32 CPU enters into protected mode from real address mode,
;a string is displayed on the screen, IA-32 CPU returns into real
;address mode from protected mode.
;
.386p
.model small,c
.stack 100h
;
Descriptor STRUCT
limit word 0
basel word 0
basem byte 0
attrib word 0
baseh byte 0
Descriptor ENDS
;
Data Segment use16
;
gdt0 Descriptor <>
;
DataSel equ $-gdt0
DataDes Descriptor <0ffffh,,,92h,>
;
CodeSel equ $-gdt0
CodeDes Descriptor <0ffffh,,,98h,>
;
VideoSel equ $-gdt0
VideoDes Descriptor <0ffffh,8000h,0Bh,92h,>
;
GdtLen equ $-gdt0
;
GdtPtr word GdtLen
dword 0
;
Mess byte ‘IA-32 CPU is in Protected Mode.’,0
;
JmpTable dword 0
;
Data ends
;
Code Segment use16
assume cs:Code,ds:Data
;
Start: xor eax,eax ;Clear eax.
;
mov ax,Data ;Initialize DS.
mov ds,ax ;
;
;Prepare for entering into protected mode.
;
shl eax,4 ;create base address for data segment
;
mov dword ptr [GdtPtr+2],eax ;Base address for load GDTR
;
mov DataDes.basel,ax ;Fill base address for data segment
shr eax,16 ;into data segment descriptor.
mov DataDes.basem,al ;
mov DataDes.baseh,ah ;
;
xor eax,eax ;Fill base address for code segment
mov ax,Code ;into code segment descriptor.
shl eax,4 ;
mov CodeDes.basel,ax ;
shr eax,16 ;
mov CodeDes.basem,al ;
mov CodeDes.baseh,ah ;
;
lgdt fword ptr GdtPtr ;Load GDTR
;
cli ;Disable interrupt.
mov eax,cr0 ;SetPE of CR0 into 1.
or eax,1
mov cr0,eax
;
mov dx,CodeSel ;Use “jmp far ptr [esi]” to flush
shl edx,16 ;instruction prefetch queue.
mov dx,offset Code16
mov JmpTable,edx
lea esi,JmpTable
jmp far ptr [esi]
;
;Enter into protected mode.
;
Code16: mov ax,DataSel ;The string is displayed on the screen.
mov ds,ax
mov ax,VideoSel
mov es,ax
mov si,offset Mess
mov di,80*46+48
mov ah,47h
Load: mov al,[si]
inc si
cmp al,0
jz ReadyToRmode
mov es:[di],ax
add di,2
jmp Load
;
;Prepare for returning to real address mode.
;
ReadyToRmode: mov eax,cr0 ;Clear PE of CR0.
and eax,0fffffffeh
mov cr0,eax
;
mov dx,seg Rmode ;Use “jmp far ptr [esi]” to flush
shl edx,16 ;instruction prefetch queue.
mov dx,offset Rmode
mov JmpTable,edx
lea esi,JmpTable
jmp far ptr [esi]
;
;Return to real address mode.
;
Rmode: sti ;Set IF.
;
mov ax,Data ;Initialize DS and ES.
mov ds,ax
mov es,ax
;
mov ax,4c00h ;Retire.
int 21h
;
Code ends ;Code segment is over.
;
end Start ;The source program is over.
课堂练习:对于例9.1中所示的程序,我们可以在MASM 6.11环境下进行汇编和链接,然后运行并观察其结果:
A. 启动PWB。
B. 更改设置:点Options,在弹出的选单上选中Build Options,在弹出的Build Options窗口中选中Use Release Options,点OK。
C. 打开源程序文件sam9-1.asm,建立可执行程序sam9-1.exe。注意,请不要进入CodeView环境去运行sam9-1.exe。
D. 重启计算机,根据计算机的不同,进行适当操作,使其进入MS-DOS方式。
E. 在MS-DOS方式下,运行sam9-1.exe,即可看到在屏幕上显示出的字符串。
现在对例9.1中所示程序的工作原理进行分析:
1. 声明结构类型Descriptor
保护模式下的段寄存器:其内容不再是段的基地址,而是称之为选择符(selector),其功能在于从全局段描述符表(Global Descriptor Table,GDT)或者局部段描述符表(Local Descriptor Table,LDT)中选出一个段描述符。选择符的字段结构如表9.1所示。
表9.1 选择符的字段结构
b15~b3 |
b2 |
b1~b0 |
Index |
TI |
RPL |
选择符的位b1~b0称之为请求特权级(Requested Privilege Level,RPL)。RPL的数字必须小于当前特权级(Current privilege level,CPL)的数字,才可以访问某个段的数据。CPL是当前正在执行的任务所具有的特权级,也就是段寄存器CS和SS中b1~b0所具有的数字。另外,段描述符的访问权限字节(字节5)的b6~b5称之为段描述符特权级(Descriptor privilege level,DPL)。DPL、CPL、RPL是Windows操作系统中保护机制的重要基础。
TI位:为0时,说明段描述符在GDT中,应该访问GDT;为1时,说明段描述符在LDT中,应该访问LDT。
Index:利用它可以从GDT或者LDT中选出一个段描述符。具体作法是,由CPU硬件首先把Index的值乘以8,然后把乘积加到段描述符表的基地址上。段描述符表的基地址和限长分别存在全局段描述符表寄存器(Global Descriptor Table Register,GDTR)和局部段描述符表寄存器(Local Descriptor Table Register,LDTR)中。
GDT和LDT都放在内存中,由Windows操作系统维护。通常,系统任务的段描述符存放在GDT中,用户任务的段描述符存放在LDT中。每个段描述符表包含8192( )个段描述符。
GDT和LDT中的段描述符可以认为是一种数据结构,每个段描述符具有8个字节的长度,其结构如表9.2所示。
表9.2 段描述符的结构
字节7 |
字节6 |
字节5 |
字节4~2 |
字节1~0 |
|
基址b31~b24 |
控制位 |
段限b19~b16 |
访问权限 |
基址b23~b0 |
段限b15~b0 |
在例9.1中,所声明的结构类型Descriptor与表9.2所示的段描述符结构是完全一致的。
2. 关于完全段
本例中的数据段和代码段都使用了完全段来定义。原因在于只有使用完全段才可以实现在保护模式下,定义16位段的功能。
3. 数据段
定义结构变量必须在数据段中,而声明结构类型则可不必在数据段中。
gdt0:NULL段描述符,Windows操作系统要求第一个段描述符必须定义为NULL。
DataSel:定义数据段选择符。
DataDes:定义数据段描述符,基地址需用指令填充。
CodeSel:定义代码段选择符。
CodeDes:定义代码段描述符,基地址需用指令填充。
VideoSel:定义视频缓冲区段选择符。
VideoDes:定义视频缓冲区段描述符。
GdtLen:全局段描述符表限长。
GdtPtr:指向全局段描述符表限长和基地址的指针。
Mess:进入保护模式后显示在屏幕上的字符串。
JmpTable:在jump指令的间接寻址方式下,用来存放转移目标地址。
4. 代码段:初始化DS,建立数据段基地址,建立用来装入GDTR的基地址,把数据段基地址填入数据段描述符,把代码段基地址填入代码段描述符。
5. 代码段:装全局描述符表寄存器指令
该指令助记符用“LGDT”表示,其功能是把内存数据段中的GDT的限长和基地址装入GDTR。
GDTR的字段结构如表9.3所示。
表9.3 GDTR的字段结构
b47~b16 |
b15~b0 |
GDT的基地址 |
GDT的限长 |
LGDT指令只需要一个操作数。由表9.3知道GDTR的长度具有6个字节,所以其操作数的类型也应该是6个字节。LGDT指令的形式和操作列在表9.4中。
表9.4 LGDT指令的形式和操作
汇编语句格式 |
编码示例 |
寻址方式与操作 |
LGDT m16&32 |
LGDT fword ptr GdtPtr |
直接寻址 |
注:在表9.4中,m代表memory,ptr代表pointer,以下同。
LGDT指令只能由操作系统使用,用户程序不能使用。通常,LGDT指令运行在实地址模式下,以便于CPU在切换到保护模式以前能够执行初始化操作。
6. 代码段:关中断,设置CR0的PE位,清除指令预取队列
用cli指令关中断。
控制寄存器(Control Register,CR)CR0具有32位的长度,其b0为保护模式允许位(Protection Enable,PE)。当该位为1时,允许CPU工作在保护模式下;当该位为0时,禁止CPU工作在保护模式下。系统加电启动时,该位被置0。
用“jmp far ptr [esi]”指令,执行一个无条件转移,以便于清除指令预取队列,保证顺利地进入保护模式。此处jmp指令使用了间接寻址方式,方法是:首先把转移目标地址存入内存数据段变量JmpTable中,然后把JmpTable的偏移量传送到esi中,最后执行一个以esi内容为指针的far转移。
7. 代码段:进入保护模式
在屏幕上显示字符串“IA-32 CPU is i.n Protected Mode.”。此处采用直接写显示缓冲区的方法。
显示缓冲区的基地址:实地址模式下为B8000H(以物理地址形式表示),保护模式下为000B8000H(以物理地址形式表示)。
指定屏幕上的显示坐标:mov di,80*46+48。屏幕上可显示字符的容量为24行×80字符。在显示缓冲区中,每个字符用两个字节来表示,高字节表示颜色与属性,低字节存储字符的ASCII代码。
指定屏幕上的显示属性:mov ah,47h,请参考MASM 6.11的联机帮助。
8. 代码段:准备返回到实地址模式
清除CR0的PE位,以便于CPU能够工作在实地址模式下。
用“jmp far ptr [esi]”指令,执行一个无条件转移,以便于清除指令预取队列,保证顺利地返回到实地址模式。
9. 代码段:返回到实地址模式
设置ds和es,返回到系统。
课堂练习:调节屏幕显示坐标和颜色与属性。