为了支持多任务,80386不仅要有效地实现任务隔离,而且还要有效地控制各任务的输入/输出,避免输入/输出冲突。本文将介绍输入输出保护。 这里下载本文源代码。
80386采用I/O特权级IPOL和I/O许可位图的方法来控制输入/输出,实现输入/输出保护。
输入输出特权级(I/O Privilege Level)规定了可以执行所有与I/O相关的指令和访问I/O空间中所有地址的最外层特权级。IOPL的值在如下图所示的标志寄存器中。
标 志 |
BIT31—BIT18 |
BIT17 |
BIT16 |
BIT15 |
BIT14 |
BIT13—BIT12 |
BIT11 |
BIT10 |
BIT9 |
BIT8 |
BIT7 |
BIT6 |
BIT5 |
BIT4 |
BIT3 |
BIT2 |
BIT1 |
BIT0 |
00000000000000 |
V |
R |
0 |
N |
IOPL |
OF |
D |
I |
T |
S |
Z |
0 |
A |
0 |
P |
1 |
C |
I/O许可位图规定了I/O空间中的哪些地址可以由在任何特权级执行的程序所访问。I/O许可位图在任务状态段TSS中。
I/O敏感指令 |
指令 |
功能 |
保护方式下的执行条件 |
CLI |
清除EFLAGS中的IF位 |
CPL<=IOPL |
|
STI |
设置EFLAGS中的IF位 |
CPL<=IOPL |
|
IN |
从I/O地址读出数据 |
CPL<=IOPL或I/O位图许可 |
|
INS |
从I/O地址读出字符串 |
CPL<=IOPL或I/O位图许可 |
|
OUT |
向I/O地址写数据 |
CPL<=IOPL或I/O位图许可 |
|
OUTS |
向I/O地址写字符串 |
CPL<=IOPL或I/O位图许可 |
上表所列指令称为I/O敏感指令,由于这些指令与I/O有关,并且只有在满足所列条件时才可以执行,所以把它们称为I/O敏感指令。从表中可见,当前特权级不在I/O特权级外层时,可以正常执行所列的全部I/O敏感指令;当特权级在I/O特权级外层时,执行CLI和STI指令将引起通用保护异常,而其它四条指令是否能够被执行要根据访问的I/O地址及I/O许可位图情况而定(在下面论述),如果条件不满足而执行,那么将引起出错码为0的通用保护异常。
由于每个任务使用各自的EFLAGS值和拥有自己的TSS,所以每个任务可以有不同的IOPL,并且可以定义不同的I/O许可位图。注意,这些I/O敏感指令在实模式下总是可执行的。
如果只用IOPL限制I/O指令的执行是很不方便的,不能满足实际要求需要。因为这样做会使得在特权级3执行的应用程序要么可访问所有I/O地址,要么不可访问所有I/O地址。实际需要与此刚好相反,只允许任务甲的应用程序访问部分I/O地址,只允许任务乙的应用程序访问另一部分I/O地址,以避免任务甲和任务乙在访问I/O地址时发生冲突,从而避免任务甲和任务乙使用使用独享设备时发生冲突。
因此,在IOPL的基础上又采用了I/O许可位图。I/O许可位图由二进制位串组成。位串中的每一位依次对应一个I/O地址,位串的第0位对应I/O地址0,位串的第n位对应I/O地址n。如果位串中的第位为0,那么对应的I/O地址m可以由在任何特权级执行的程序访问;否则对应的I/O地址m只能由在IOPL特权级或更内层特权级执行的程序访问。如果在I/O外层特权级执行的程序访问位串中位值为1的位所对应的I/O地址,那么将引起通用保护异常。
I/O地址空间按字节进行编址。一条I/O指令最多可涉及四个I/O地址。在需要根据I/O位图决定是否可访问I/O地址的情况下,当一条I/O指令涉及多个I/O地址时,只有这多个I/O地址所对应的I/O许可位图中的位都为0时,该I/O指令才能被正常执行,如果对应位中任一位为1,就会引起通用保护异常。
80386支持的I/O地址空间大小是64K,所以构成I/O许可位图的二进制位串最大长度是64K个位,即位图的有效部分最大为8K字节。一个任务实际需要使用的I/O许可位图大小通常要远小于这个数目。
当前任务使用的I/O许可位图存储在当前任务TSS中低端的64K字节内。I/O许可位图总以字节为单位存储,所以位串所含的位数总被认为是8的倍数。从前文中所述的TSS格式可见,TSS内偏移66H的字确定I/O许可位图的开始偏移。由于I/O许可位图最长可达8K字节,所以开始偏移应小于56K,但必须大于等于104,因为TSS中前104字节为TSS的固定格式,用于保存任务的状态。
保护模式下处理器在执行I/O指令时进行许可检查的细节如下所示。
(1)若CPL<=IOPL,则直接转步骤(8);
(2)取得I/O位图开始偏移;
(3)计算I/O地址对应位所在字节在I/O许可位图内的偏移;
(4)计算位偏移以形成屏蔽码值,即计算I/O地址对应位在字节中的第几位;
(5)把字节偏移加上位图开始偏移,再加1,所得值与TSS界限比较,若越界,则产生出错码为0的通用保护故障;
(6)若不越界,则从位图中读对应字节及下一个字节;
(7)把读出的两个字节与屏蔽码进行与运算,若结果不为0表示检查未通过,则产生出错码为0的通用保护故障;
(8)进行I/O访问。
设某一任务的TSS段如下:
TSSSEG SEGMENT PARA USE16
TSS <> ;TSS低端固定格式部分
DB 8 DUP(0) ;对应I/O端口00H—3FH
DB 10000000B ;对应I/O端口40H—47H
DB 01100000B ;对用I/O端口48H—4FH
DB 8182 DUP(0ffH) ;对应I/O端口50H—0FFFFH
DB 0FFH ;位图结束字节
TSSLen = $
TSSSEG ENDS
再假设IOPL=1,CPL=3。那么如下I/O指令有些能正常执行,有些会引起通用保护异常:
in al,21h ;(1)正常执行
in al,47h ;(2)引起异常
out 20h,al ;(3)正常实行
out 4eh,al ;(4)引起异常
in al,20h ;(5)正常执行
out 20h,eax ;(6)正常执行
out 4ch,ax ;(7)引起异常
in ax,46h ;(8)引起异常
in eax,42h ;(9)正常执行
由上述I/O许可检查的细节可见,不论是否必要,当进行许可位检查时,80386总是从I/O许可位图中读取两个字节。目的是为了尽快地执行I/O许可检查。一方面,常常要读取I/O许可位图的两个字节。例如,上面的第(8)条指令要对I/O位图中的两个位进行检查,其低位是某个字节的最高位,高位是下一个字节的最低位。可见即使只要检查两个位,也可能需要读取两个字节。另一方面,最多检查四个连续的位,即最多也只需读取两个字节。所以每次要读取两个字节。这也是在判别是否越界时再加1的原因。为此,为了避免在读取I/O许可位图的最高字节时产生越界,必须在I/O许可位图的最后填加一个全1的字节,即0FFH。此全1的字节应填加在最后一个位图字节之后,TSS界限范围之前,即让填加的全1字节在TSS界限之内。
I/O许可位图开始偏移加8K所得的值与TSS界限值二者中较小的值决定I/O许可位图的末端。当TSS的界限大于I/O许可位图开始偏移加8K时,I/O许可位图的有效部分就有8K字节,I/O许可检查全部根据全部根据该位图进行。当TSS的界限不大于I/O许可位图开始偏移加8K时,I/O许可位图有效部分就不到8K字节,于是对较小I/O地址访问的许可检查根据位图进行,而对较大I/O地址访问的许可检查总被认为不可访问而引起通用保护故障。因为这时会发生字节越界而引起通用保护异常,所以在这种情况下,可认为不足的I/O许可位图的高端部分全为1。利用这个特点,可大大节约TSS中I/O许可位图占用的存储单元,也就大大减小了TSS段的长度。
输入输出的保护与存储在标志寄存器EFLAGS中的IOPL密切相关,显然不能允许随便地改变IOPL,否则就不能有效地实现输入输出保护。类似地,对EFLAGS中的IF位也必须加以保护,否则CLI和STI作为敏感指令对待是无意义的。此外,EFLAGS中的VM位决定着处理器是否按虚拟8086方式工作。
80386对EFLAGS中的这三个字段的处理比较特殊,只有在较高特权级执行的程序才能执行IRET、POPF、CLI和STI等指令改变它们。下表列出了不同特权级下对这三个字段的处理情况。
不同特权级对 |
特权级 |
VM标志字段 |
IOPL标志字段 |
IF标志字段 |
CPL=0 |
可变(初POPF指令外) |
可变 |
可变 |
|
0<CPL<=IOPL< TD> |
不变 |
不变 |
可变 |
|
CPL>IOPL |
不变 |
不变 |
不变 |
从表中可见,只有在特权级0执行的程序才可以修改IOPL位及VM位;只能由相对于IOPL同级或更内层特权级执行的程序才可以修改IF位。与CLI和STI指令不同,在特权级不满足上述条件的情况下,当执行POPF指令和IRET指令时,如果试图修改这些字段中的任何一个字段,并不引起异常,但试图要修改的字段也未被修改,也不给出任何特别的信息。此外,指令POPF总不能改变VM位,而PUSHF指令所压入的标志中的VM位总为0。
下面给出一个用于演示输入输出保护的实例。演示内容包括:I/O许可位图的作用、I/O敏感指令引起的异常和特权指令引起的异常;使用段间调用指令CALL通过任务门调用任务,实现任务嵌套。
实例演示的内容比较丰富,具体演示步骤如下:
(1)在实模式下做必要准备后,切换到保护模式;
(2)进入保护模式的临时代码段后,把演示任务的TSS段描述符装入TR,并设置演示任务的堆栈;
(3)进入演示代码段,演示代码段的特权级是0;
(4)通过任务门调用测试任务1。测试任务1能够顺利进行;
(5)通过任务门调用测试任务2。测试任务2演示由于违反I/O许可位图规定而导致通用保护异常;
(6)通过任务门调用测试任务3。测试任务3演示I/O敏感指令如何引起通用保护异常;
(7)通过任务门调用测试任务4。测试任务4演示特权指令如何引起通用保护异常;
(8)从演示代码转临时代码,准备返回实模式;
(9)返回实模式,并作结束处理。
为了达到演示目的,实例除了演示任务外,还涉及四个测试任务和一个通用保护故障处理任务。实例由如下几部分组成。
(1)全局描述符表GDT和中断描述符表IDT;
(2)其它中断/异常处理程序代码段;
(3)通用保护故障处理任务的任务状态段、堆栈段和代码段;
(4)四个测试任务合用的任务状态段、堆栈段和代码段;
(5)演示任务的任务状态段、堆栈段和代码段;
(6)演示任务的临时代码段;
(7)实模式下执行的启动与结束程序代码段和数据段。
实例九源程序清单如下:
;名称:ASM9.ASM
;功能:演示I/O保护及I/O敏感指令的作用
;编译:TASM ASM9.ASM
;连接:TLINK /32 ASM9.OBJ
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 ;全局描述符表段(16位)
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;规范段描述符及选择子
Normal Desc <0ffffh,,,ATDW,,>
Normal_Sel = Normal-GDT
;视频缓冲区段描述符(DPL=3)及选择子(任何特权级可写)
VideoBuf Desc <07fffh,8000h,0bh,ATDW,,>
VideoBuf_Sel = VideoBuf-GDT
;----------------------------------------------------------------------------
EFFGDT LABEL BYTE
;演示任务TSS段描述符及选择子
DemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
DemoTSS_Sel = DemoTSS-GDT
;演示任务堆栈段描述符及选择子
DemoStack Desc <DemoStackLen-1,DemoStackSeg,,ATDW,D32,>
DemoStack_Sel = DemoStack-GDT
;演示代码段描述符及选择子
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE,D32,>
DemoCode_Sel = DemoCode-GDT
;属于演示任务的临时代码段描述符及选择子
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel = TempCode-GDT
;指向GDT的存储段描述符及选择子
ToGDT Desc <GDTLen-1,GDTSeg,,ATDW,,>
ToGDT_Sel = ToGDT-GDT
;指向通用保护故障处理任务TSS的存储段描述符及选择子
ToGPTSS Desc <GPTSSLen-1,GPTSSSeg,,ATDW,,>
ToGPTSS_Sel = ToGPTSS-GDT
;指向测试任务TSS的存储段描述符及选择子
ToTestTSS Desc <TestTSSLen-1,TestTSSSeg,,ATDW,,>
ToTestTSS_Sel = ToTestTSS-GDT
;测试任务TSS段描述符及选择子
TestTSS Desc <TestTSSLen-1,TestTSSSeg,,AT386TSS,,>
TestTSS_Sel = TestTSS-GDT
;测试任务1堆栈段描述符(DPL=1)及选择子
Test1Stack Desc <TestStackLen-1,TestStackSeg,,ATDW+DPL1,D32,>
Test1Stack_Sel = Test1Stack-GDT+RPL1
;测试任务1代码段描述符(DPL=1)及选择子
Test1Code Desc <TestCodeLen-1,TestCodeSeg,,ATCE+DPL1,D32,>
Test1Code_Sel = Test1Code-GDT+RPL1
;测试任务2堆栈段描述符(DPL=2)及选择子
Test2Stack Desc <TestStackLen-1,TestStackSeg,,ATDW+DPL2,D32,>
Test2Stack_Sel = Test2Stack-GDT+RPL2
;测试任务2代码段描述符(DPL=2)及选择子
Test2Code Desc <TestCodeLen-1,TestCodeSeg,,ATCE+DPL2,D32,>
Test2Code_Sel = Test2Code-GDT+RPL2
;测试任务3堆栈段描述符(DPL=3)及选择子
Test3Stack Desc <TestStackLen-1,TestStackSeg,,ATDW+DPL3,D32,>
Test3Stack_Sel = Test3Stack-GDT+RPL3
;测试任务3代码段描述符(DPL=3)及选择子
Test3Code Desc <TestCodeLen-1,TestCodeSeg,,ATCE+DPL3,D32,>
Test3Code_Sel = Test3Code-GDT+RPL3
;通用保护故障处理任务的TSS段描述符及选择子
GPTSS Desc <GPTSSLen-1,GPTSSSeg,,AT386TSS,,>
GPTSS_Sel = GPTSS-GDT
;通用保护故障处理任务的堆栈段描述符及选择子
GPStack Desc <GPStackLen-1,GPStackSeg,,ATDW,D32,>
GPStack_Sel = GPStack-GDT
;通用保护故障处理任务的代码段描述符及选择子
GPCode Desc <GPCodeLen-1,GPCodeSeg,,ATCE,D32,>
GPCode_Sel = GPCode-GDT
;其它中断或异常处理程序代码段(一致可读)描述符及选择子
ErrCode Desc <ErrCodeLen-1,ErrCodeSeg,,ATCCOR,D32,>
ErrCode_Sel = ErrCode-GDT
;----------------------------------------------------------------------------
GDNum = ($-EFFGDT)/(SIZE Desc) ;需处理基地址的描述符个数
;----------------------------------------------------------------------------
;指向测试任务的任务门
TestTask Gate <,TestTSS_Sel,,ATTaskGate,>
Test_Sel = TestTask-GDT
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全局描述符表长度
GDTSeg ENDS ;全局描述符表段定义结束
;----------------------------------------------------------------------------
IDTSeg SEGMENT PARA USE16 ;中断描述符表段(16位)
IDT LABEL BYTE ;中断描述符表
REPT 13
Gate <ErrBegin,ErrCode_Sel,,AT386TGate,>
ENDM
Gate <,GPTSS_Sel,,ATTaskGate,> ;通用故障处理程序门描述符
REPT 242
Gate <ErrBegin,ErrCode_Sel,,AT386TGate,>
ENDM
;----------------------------------------------------------------------------
IDTLen = $-IDT
;----------------------------------------------------------------------------
IDTSeg ENDS ;中断描述符表段定义结束
;----------------------------------------------------------------------------
;其它中断或异常处理程序的代码段(一致可读)
;----------------------------------------------------------------------------
ErrCodeSeg SEGMENT PARA USE32
ASSUME CS:ErrCodeSeg
;----------------------------------------------------------------------------
ErrMess DB 'Error!!!'
ErrMessLen = $-ErrMess
;----------------------------------------------------------------------------
ErrBegin PROC FAR
cld
mov ax,ErrCode_Sel
mov ds,ax
lea esi,ErrMess
mov ax,VideoBuf_Sel
mov es,ax
mov edi,1992
mov ecx,ErrMessLen
mov ah,4eh
Err1: lodsb
stosw
loop Err1
jmp $
ErrBegin ENDP
;----------------------------------------------------------------------------
ErrCodeLen = $
ErrCodeSeg ENDS
;----------------------------------------------------------------------------
GPTSSSeg SEGMENT PARA USE16 ;通用保护故障处理任务的TSS
GPTaskSS LABEL BYTE
DD 0 ;任务嵌套时的链接指针
DD 0 ;0级堆栈偏移
DW 0,0 ;0级堆栈选择子
DD 0 ;1级堆栈偏移
DW 0,0 ;1级堆栈选择子
DD 0 ;2级堆栈偏移
DW 0,0 ;2级堆栈选择子
DD 0 ;CR3
DW GPBegin,0 ;EIP
DD 0 ;EFLAGS
DD 0 ;EAX
DD 0 ;ECX
DD 0 ;EDX
DD 0 ;EBX
DD GPStackLen ;ESP
DD 0 ;EBP
DD 0 ;ESI
DD 0 ;EDI
DW VideoBuf_Sel,0 ;ES
DW GPCode_Sel,0 ;CS
DW GPStack_Sel,0 ;SS
DW ToTestTSS_Sel,0 ;DS
DW ToGPTSS_Sel,0 ;FS
DW 0,0 ;GS
DW 0,0 ;LDTR
DW 0 ;调试陷阱标志
DW $+2 ;指向I/O许可位图的偏移
DB 0ffh ;I/O许可位图结束标志
GPTSSLen = $
GPTSSSeg ENDS
;----------------------------------------------------------------------------
GPStackSeg SEGMENT PARA USE32 ;通用保护故障处理任务堆栈段
GPStackLen = 512
DB GPStackLen DUP(0)
GPStackSeg ENDS
;----------------------------------------------------------------------------
;通用保护故障处理程序代码段
;----------------------------------------------------------------------------
GPCodeSeg SEGMENT PARA USE32
ASSUME CS:GPCodeSeg
;----------------------------------------------------------------------------
GPBegin PROC FAR
;在屏幕左上角显示故障点
xor edi,edi
mov ebx,OFFSET TestTaskSS
mov edx,DWORD PTR [ebx].TRCS
call EchoEDX
mov ax,(17h SHL 8)+':'
stosw
mov edx,[ebx].TREIP
call EchoEDX
;演示以便看清故障点
mov ecx,1234567h
loop $
;调整任务链接指针,中止故障任务
mov ebx,OFFSET GPTaskSS
mov ax,DemoTSS_Sel
mov fs:[ebx].TRLink,ax
add esp,4
iretd
jmp GPBegin
GPBegin ENDP
;----------------------------------------------------------------------------
;显示edx内容的子程序
;----------------------------------------------------------------------------
EchoEDX PROC
mov ah,17h
mov ecx,8
EchoEDX1: rol edx,4
mov al,dl
call HToASCII
stosw
loop EchoEDX1
ret
EchoEDX ENDP
;----------------------------------------------------------------------------
;把4位二进制数转换成对应的ASCII码
;----------------------------------------------------------------------------
HToASCII PROC
and al,0fh
add al,90h
daa
adc al,40h
daa
ret
HToASCII ENDP
;----------------------------------------------------------------------------
GPCodeLen = $
GPCodeSeg ENDS
;----------------------------------------------------------------------------
;测试任务的TSS段
TestTSSSeg SEGMENT PARA USE16
TestTaskSS TSS <> ;TSS的固定格式部分
IOMap LABEL BYTE ;I/O许可位图
DB 8 DUP(0ffh) ;端口00h--3fh
DB 11111011b ;端口40h--47h
DB 3 DUP(0ffh) ;端口48h--5fh
DB 11111101b ;端口60h--67h
DB 0 ;端口68h--6fh
DB 0ffh ;I/O许可位图结束标志
TestTSSLen = $
TestTSSSeg ENDS
;----------------------------------------------------------------------------
;测试任务的堆栈段
TestStackSeg SEGMENT PARA USE32
TestStackLen = 1024
DB TestStackLen DUP(0)
TestStackSeg ENDS
;----------------------------------------------------------------------------
;测试任务的代码段
TestCodeSeg SEGMENT PARA USE32
ASSUME CS:TestCodeSeg
;----------------------------------------------------------------------------
Test3Begin PROC FAR
cli ;I/O敏感指令
clts ;特权指令
iretd
jmp Test3Begin
Test3Begin ENDP
;----------------------------------------------------------------------------
TestBegin PROC FAR
mov al,0b6h ;使扬声器发出一长声
out 43h,al
mov al,2
out 42h,al
mov al,34h
out 42h,al
in al,61h
mov ah,al
or al,3
out 61h,al
mov ecx,1234567h
loop $
mov al,ah
out 61h,al
iretd
jmp TestBegin
TestBegin ENDP
;----------------------------------------------------------------------------
TestCodeLen = $
TestCodeSeg ENDS
;----------------------------------------------------------------------------
;演示任务TSS段
DemoTSSSeg SEGMENT PARA USE16
DemoTaskSS TSS <>
DB 0ffh ;I/O许可位图结束字节
DemoTSSLen = $
DemoTSSSeg ENDS
;----------------------------------------------------------------------------
;演示任务的堆栈段
DemoStackSeg SEGMENT PARA USE32
DemoStackLen = 1024
DB DemoStackLen DUP(0)
DemoStackSeg ENDS
;----------------------------------------------------------------------------
;演示任务的代码段
DemoCodeSeg SEGMENT PARA USE32
ASSUME CS:DemoCodeSeg
;----------------------------------------------------------------------------
DemoBegin PROC FAR
mov ax,ToTestTSS_Sel
mov ds,ax
mov ebx,OFFSET TestTaskSS
;把测试任务1的入口点,堆栈指针和标志值(含IOPL)填入测试任务TSS
mov WORD PTR [ebx].TRSS,Test1Stack_Sel
mov DWORD PTR [ebx].TRESP,TestStackLen
mov WORD PTR [ebx].TRCS,Test1Code_Sel
mov DWORD PTR [ebx].TREIP,OFFSET TestBegin
mov DWORD PTR [ebx].TREFLAG,IOPL1
;通过任务门调用测试任务
CALL32 Test_Sel,0
;把测试任务2的入口点,堆栈指针和标志值(含IOPL)填入测试任务TSS
mov WORD PTR [ebx].TRSS,Test2Stack_Sel
mov DWORD PTR [ebx].TRESP,TestStackLen
mov WORD PTR [ebx].TRCS,Test2Code_Sel
mov DWORD PTR [ebx].TREIP,OFFSET TestBegin
mov DWORD PTR [ebx].TREFLAG,IOPL1
;通过任务门调用测试任务
CALL32 Test_Sel,0
;把测试任务TSS描述符内的属性置为"可用"
mov ax,ToGDT_Sel
mov fs,ax
mov fs:TestTSS.Attributes,AT386TSS
;把测试任务3的入口点,堆栈指针和标志值(含IOPL)填入测试任务TSS
mov WORD PTR [ebx].TRSS,Test3Stack_Sel
mov DWORD PTR [ebx].TRESP,TestStackLen
mov WORD PTR [ebx].TRCS,Test3Code_Sel
mov DWORD PTR [ebx].TREIP,OFFSET Test3Begin
mov DWORD PTR [ebx].TREFLAG,IOPL2
;通过任务门调用测试任务
CALL32 Test_Sel,0
;把测试任务TSS描述符内的属性置为"可用"
mov ax,ToGDT_Sel
mov fs,ax
mov fs:TestTSS.Attributes,AT386TSS
;把测试任务4的入口点,堆栈指针和标志值(含IOPL)填入测试任务TSS
mov WORD PTR [ebx].TRSS,Test3Stack_Sel
mov DWORD PTR [ebx].TRESP,TestStackLen
mov WORD PTR [ebx].TRCS,Test3Code_Sel
mov DWORD PTR [ebx].TREIP,OFFSET Test3Begin
mov DWORD PTR [ebx].TREFLAG,IOPL3
;通过任务门调用测试任务
CALL32 Test_Sel,0
JUMP32 TempCode_Sel,<OFFSET ToDOS>
DemoBegin ENDP
;----------------------------------------------------------------------------
DemoCodeLen = $
DemoCodeSeg ENDS
;----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 ;演示任务的临时代码段
ASSUME CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual PROC FAR
;置数据段寄存器为空
mov ax,0
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
;置堆栈指针
mov ax,DemoStack_Sel
mov ss,ax
mov esp,DemoStackLen
;置任务寄存器
mov ax,DemoTSS_Sel
ltr ax
;转演示代码段
JUMP16 DemoCode_Sel,DemoBegin
ToDOS: clts
mov ax,Normal_Sel
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
Virtual ENDP
;----------------------------------------------------------------------------
TempCodeSeg ENDS
;============================================================================
RDataSeg SEGMENT PARA USE16 ;实方式数据段
VGDTR PDesc <GDTLen-1,> ;GDT伪描述符
VIDTR PDesc <IDTLen-1,> ;IDT伪描述符
NORVIDTR PDesc <3ffh,> ;用于保存原IDTR值
SPVar DW ? ;用于保存实方式下的SP
SSVar DW ? ;用于保存实方式下的SS
RDataSeg ENDS
;----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16 ;实方式代码段
ASSUME CS:RCodeSeg,DS:RDataSeg
;----------------------------------------------------------------------------
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
call InitGDT ;初始化全局描述符表GDT
call InitIDT ;初始化中断描述符表IDT
lgdt QWORD PTR VGDTR ;装载GDTR
mov SSVar,ss ;保存堆栈指针
mov SPVar,sp
sidt QWORD PTR NORVIDTR ;保存IDTR
cli ;关中断
lidt QWORD PTR VIDTR ;装载IDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <TempCode_Sel>,<OFFSET Virtual>
Real: mov ax,RDataSeg
mov ds,ax
lss sp,DWORD PTR SPVar ;又回到实方式
lidt QWORD PTR NORVIDTR
sti
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
InitGDT PROC
push ds
mov ax,GDTSeg
mov ds,ax
mov cx,GDNum
mov si,OFFSET EFFGDT
InitG: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR [si].BaseL,ax
mov BYTE PTR [si].BaseM,dl
mov BYTE PTR [si].BaseH,dh
add si,SIZE Desc
loop InitG
pop ds
mov bx,16
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
ret
InitGDT ENDP
;----------------------------------------------------------------------------
InitIDT PROC
mov bx,16
mov ax,IDTSeg
mul bx
mov WORD PTR VIDTR.Base,ax
mov WORD PTR VIDTR.Base+2,dx
ret
InitIDT ENDP
;----------------------------------------------------------------------------
RCodeSeg ENDS
END Start
为了节省篇幅,同时也反映任务状态段的作用,实例通过任务门调用的四个测试任务合用一个任务状态段。从源程序可见,这种合用,实际上是串行的。先把测试任务1的入口点填入测试任务状态段,同时填入堆栈指针,然后调用测试任务1。在测试任务1完成后,再填入测试任务2的入口点,也填入堆栈指针,然后调用测试任务2。依次类推。
通用保护故障处理程序作为一个独立的任务出现。再发生通用保护故障后,通用保护故障处理程序在屏幕上显示故障点的选择子和偏移,然后通过调整存放在任务状态段内的任务连接指针的方法,中止引起故障的测试任务。