虚拟86模式
在Windows中,当要运行一个MS-DOS程序时,Windows就会打开一个窗口,用户可以在里面运行大多数的DOS程序,在命令中输入命令,就像在DOS下一样工作。这一切都是依靠80386处理器的一个工作模式:虚拟86模式,或V86模式。
虚拟86模式也处在保护模式下,这种模式的设计主要是为了兼顾软件的兼容性和保护模式的强大保护功能。处于虚拟86模式下的代码采用实模式下的寻址方式,即段寄存器乘16加编移地址得到线性地址。
当标志寄存器EFLAG的VM位为1时,处理器即处于虚拟86模式。但不能简单地通过将VM位置1来切入V86模式。在编写程序时,需要设置了两个任务状态段,其中,一个EFLAG中的VM位为1,为V86所用。程序进入保护模式后,设置当前任务状态段,然后用一长跳转指令,它的目标是V86任务,这就进入了V86模式。可以参考下面任务切换的例3,编写进入V86模式。
进入V86模式的方法不止一种,可以通过任务切换,也可以通过用IRET指令进入。虚拟86方式必须工作在特权级3上,而中断/异常处理程序是工作在0特权级上的。因此,当在处理器工作在虚拟86模式又发生中断/异常时,处理器产生中断并离开虚拟86模式,进入中断/异常处理程序,这时0级堆栈中放有标志寄存器EFLAG的值,如图5.13所示。它的VM位是1,当中断返回时会再载入EFLAG,再进入虚拟86方式。我们可以模拟一个中断的环境,之后执行IRET指令,进入虚拟86模式。
5.4.4 任务转换
80386不仅支持多种工作模式,而且还支持多任务。所谓多任务,是指系统在同一时间内能够运行多个任务。但我们知道,CPU在每一个确定的时刻只能执行一个指令流,要实现多任务,只能不断地在多个任务之间做切换。现在来看看如何进行任务切换。
要进行任务切换,就要用到任务状态段TSS。任务状态段保存着各个任务的各个寄存器的值(通用寄存器、堆栈指针、标志寄存器等)、操作系统的有关信息、任务所访问的I/O口等。
任务切换有两种方法,可以直接通过任务状态段TSS切换,也可以通过任务门进行切换。直接通过TSS切换时,用CALL指令指向一个任状态段,执行CALL指令,则CPU会保存当前的任务状态,并取出目标任务状态段的状态装入CPU,执行新的任务状态的任务,这样就完成了一次任务转换。
通过任务门进行切换时,将任务门的选择子指向某个任务的TSS描述符,用CALL指令指向任务门,执行CALL指令,则CPU就会进行任务切换,并且交换任务状态。
一般来说,分时操作系统会在定时器中断时判断是否要进行任务切换,操作系统查看当前进程或线程的时间片是否已用完,若是,则进行任务切换。
例5.5 编写程序,用任务状态段进行任务切换。
.386p
JUMP16 MACRO selector,offsetv ;无条件转移宏指令
DB 0EAH
DW offsetv
DW selector
ENDM
;------------------------------------------
CALL16 MACRO selector ,offsetv ;过程调用宏指令
DB 09AH
DW offsetv
DW selector
ENDM
;------------------------------------------
Descriptor STRUC ;描述符结构体
limitl dw 0
basel dw 0
basem db 0
attributes dw 0
baseh db 0
Descriptor ENDS
;===============================
Data Segment use16 ;数据段
gdt0 Descriptor <>
DataSel = $-gdt0
DataDes Descriptor <0ffffh,,,92H,>
CodeSel = $-gdt0
CodeDes Descriptor <0ffffh,,,98H,>
Tss1Sel = $-gdt0 ;Tss1 Descriptor
Tss1Des Descriptor <Tss1Len-1,,,89H,>
Tss2Sel = $-gdt0 ;Tss2 Descriptor
Tss2Des Descriptor <Tss2Len-1,,,89H,>
Stack1Sel = $-gdt0 ;任务1堆栈段选择子
Stack1Des Descriptor <0,,,97H,> ;任务1堆栈段描述符
Stack2Sel = $-gdt0 3
Stack2Des Descriptor <0,,,0F7H,>
Tss2CodeSel = $-gdt0 3 ;任务2代码段选择子
Tss2CodeDes Descriptor <0ffffh,,,0F8H,> ;任务2代码段描述符
GdtLen = $-gdt0
GdtPtr dw GdtLen-1
dd 0
;
Tss1 label byte ;任务1的任务状态段
dd 0
dd 0 ;Stack Pointer for Ring 0
dw 0,0
dd 0 ;Stack Pointer for Ring 1
dw 0,0
dd 0 ;Stack Pointer for Ring 2
dw 0,0
dd 0 ;cr3
dd 0 ;eip
dd 0 ;eflags
dd ? ;eax
dd ? ;ecx
dd ? ;edx
dd ? ;ebx
dd ? ;esp
dd ? ;ebp
dd ? ;esi
dd ? ;edi
dw 0,0 ;es
dw 0,0 ;cs
dw 0,0 ;ss
dw 0,0 ;ds
dw 0,0 ;fs
dw 0,0 ;gs
dw 0,0 ;ldt
dw 0
dw $ 2
db 0ffh
Tss1Len = $-Tss1
Tss2 label byte ;任务2的任务状态段
dd 0 ;Link
dd 0 ;Stack Pointer for Ring 0
dw 0,0
dd 0 ;Stack Pointer for Ring 1
dw 0,0
dd 0 ;Stack Pointer for Ring 2
dw 0,0
dd 0 ;cr3
dw offset Tss2Begin,0 ;eip
dd 0 ;eflags
dd ? ;eax
dd ? ;ecx
dd ? ;edx
dd ? ;ebx
dd 1024 ;esp
dd ? ;ebp
dd ? ;esi
dd ? ;edi
dw 0,0 ;es
dw Tss2CodeSel,0 ;cs
dw Stack2Sel,0 ;ss
dw 0,0 ;ds
dw 0,0 ;fs
dw 0,0 ;gs
dw 0,0 ;ldt
dw 0
dw $ 2
db 0ffh
Tss2Len = $-Tss2
;
OldSPSS dw 0,0
Data ends
;==============================
Stack1 Segment use16 ;任务1的堆栈段
db 1024 dup (0)
Stack1 Ends
Stack2 Segment use16 ;任务2的堆栈段
db 1024 dup (0)
Stack2 Ends
;==============================
Tss2Code Segment use16 ;任务2的代码段
assume cs:Tss2Code
Tss2Begin:
iretd
Tss2Code Ends
;=============================
Code Segment use16 ;代码段
assume cs:Code,ds:Data
Start:
mov ax,Data
mov ds,ax
xor eax,eax ;初始化数据段描述符
mov ax,Data
shl eax,4
mov dword ptr [GdtPtr 2],eax ;GDT地址
xor eax,eax ;初始化代码段描述符
mov ax,Code
shl eax,4
mov CodeDes.basel,ax
shr eax,16
mov CodeDes.basem,al
mov CodeDes.baseh,ah
mov ax,Data ;Tss1,初始化任务1的状态段描述符
movzx eax,ax
shl eax,4
xor ebx,ebx
mov bx,offset Tss1
add eax,ebx
mov Tss1Des.basel,ax
shr eax,16
mov Tss1Des.basem,al
mov Tss1Des.baseh,ah
mov ax,Data ;Tss2,初始化任务2的状态段描述符
movzx eax,ax
shl eax,4
xor ebx,ebx
mov bx,offset Tss2
add eax,ebx
mov Tss2Des.basel,ax
shr eax,16
mov Tss2Des.basem,al
mov Tss2Des.baseh,ah
mov ax,Stack1 ;Stack
movzx eax,ax
shl eax,4
mov Stack1Des.basel,ax
shr eax,16
mov Stack1Des.basem,al
mov Stack1Des.baseh,ah
mov ax,Stack2
movzx eax,ax
shl eax,4
mov Stack2Des.basel,ax
shr eax,16
mov Stack2Des.basem,al
mov Stack2Des.baseh,ah
mov ax,Tss2Code ;Tss2Code
movzx eax,ax
shl eax,4
mov Tss2CodeDes.basel,ax
shr eax,16
mov Tss2CodeDes.basem,al
mov Tss2CodeDes.baseh,ah
lgdt fword ptr GdtPtr ;Load GDT
cli
mov OldSPSS,sp
mov [OldSPSS 2],ss
mov eax,cr0 ;转到保护模式
or eax,1
mov cr0,eax
JUMP16 CodeSEL,<offset Protect>
Protect:
mov ax,DataSel
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ax,Stack1Sel
mov ss,ax
mov sp,1024
mov ax,Tss1Sel
ltr ax ;装入任务状态寄存器
CALL16 Tss2Sel,0 ;任务切换
mov eax,cr0
and eax,0fffffffeH
mov cr0,eax
JUMP16 Code,<offset Real>
Real:
mov ax,data
mov ds,ax
lss sp,dword ptr OldSPSS
sti
mov ax,4C00H
int 21H
Code ends
end Start