九.输入/输出保护

发布时间:2012-5-27 17:21
分类名称:80x86保护模式


为了支持多任务,80386不仅要有效地实现任务隔离,而且还要有效地控制各任务的输入/输出, 避免输入/输出冲突。本文将介绍输入输出保护。 这里下载本文源代码

<一>输入/输出保护

80386采用I/O特权级IPOL和I/O许可位图的方法来控制输入/输出,实现输入/输出保护。

1.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
M

R
F

0

N
T

IOPL

OF

D
F

I
F

T
F

S
F

Z
F

0

A
F

0

P
F

1

C
F

 

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敏感指令在实模式下总是可执行的。

2.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的固定格式,用于保存任务的状态。

1.I/O访问许可检查细节

保护模式下处理器在执行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<>

不变

不变

可变

CPL>IOPL

不变

不变

不变

 

从表中可见,只有在特权级0执行的程序才可以修改IOPL位及VM位;只能由相对于IOPL同级或更内 层特权级执行的程序才可以修改IF位。与CLI和STI指令不同,在特权级不满足上述条件的情况下, 当执行POPF指令和IRET指令时,如果试图修改这些字段中的任何一个字段,并不引起异常,但试图 要修改的字段也未被修改,也不给出任何特别的信息。此外,指令POPF总不能改变VM位,而PUSHF指 令所压入的标志中的VM位总为0。

<三>演示输入输出保护的实例(实例九)

下面给出一个用于演示输入输出保护的实例。演示内容包括:I/O许可位图的作用、I/O敏感指令 引起的异常和特权指令引起的异常;使用段间调用指令CALL通过任务门调用任务,实现任务嵌套。

1.演示步骤

实例演示的内容比较丰富,具体演示步骤如下:

(1)在实模式下做必要准备后,切换到保护模式;

(2)进入保护模式的临时代码段后,把演示任务的TSS段描述符装入TR,并设置演示任务 的堆栈;

(3)进入演示代码段,演示代码段的特权级是0;

(4)通过任务门调用测试任务1。测试任务1能够顺利进行;

(5)通过任务门调用测试任务2。测试任务2演示由于违反I/O许可位图规定而导致通用保 护异常;

(6)通过任务门调用测试任务3。测试任务3演示I/O敏感指令如何引起通用保护异常;

(7)通过任务门调用测试任务4。测试任务4演示特权指令如何引起通用保护异常;

(8)从演示代码转临时代码,准备返回实模式;

(9)返回实模式,并作结束处理。

2.源程序组织和清单

为了达到演示目的,实例除了演示任务外,还涉及四个测试任务和一个通用保护故障处理任务。 实例由如下几部分组成。

(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

3.关于实例九的说明

为了节省篇幅,同时也反映任务状态段的作用,实例通过任务门调用的四个测试任务合用一个 任务状态段。从源程序可见,这种合用,实际上是串行的。先把测试任务1的入口点填入测试任 务状态段,同时填入堆栈指针,然后调用测试任务1。在测试任务1完成后,再填入测试任务2的 入口点,也填入堆栈指针,然后调用测试任务2。依次类推。

通用保护故障处理程序作为一个独立的任务出现。再发生通用保护故障后,通用保护故障处理程 序在屏幕上显示故障点的选择子和偏移,然后通过调整存放在任务状态段内的任务连接指针的方 法,中止引起故障的测试任务。