实验11
保护模式支持多任务,能够快速地进行任务切换和保护任务环境。在Windows操作系统中,它能同时运行若干个应用程序(独立运行的程序又称之为进程),每一个进程作为一个任务;在一个进程中,它又可以分成若干个独立的执行流,称之为线程。
11.1 多任务及其调度
下面给出一个用于演示任务切换的实例。该程序在保护模式下建立2个任务,分别在屏幕上显示单个字符和寄存器的值。涉及的内容包括:直接通过TSS段的任务切换,通过任务门的任务切换,任务内特权级的变换及参数传递。
1. 实现步骤
第一个任务称为临时任务,另一个任务称为演示任务。临时任务的代码位于TempCodeSeg段,从标号Virtual处开始执行;演示任务的代码位于DemoCodeSeg段,从标号DemoBegin处开始执行。演示任务还调用了SubRSeg段中的SubRB子程序。
程序执行过程如下:
(1) 实模式下初始化(Start);
(2) 切换到保护模式(JUMP16 <TempCode_Sel>,<OFFSET Virtual>指令);
(3) 设置TR对应临时任务,特权级为0(Virtual);
(4) 直接切换到演示任务,演示任务的特权级为2(JUMP16 DemoTSS_Sel,0);
(5) 把入口参数压入堆栈,经调用门进入显示信息子程序,显示信息子程序的特权级为0(DemoBegin);
(6) 从堆栈中取出入口参数并处理(SubRB);
(7) 从显示信息子程序返回特权级为2的演示代码段(ret 8);
(8) 经任务门切换到特权级为0的临时任务(JUMP32 ToTempT_Sel,0);
(9) 循环执行(4)~(8)任务切换5次(loop指令)。
(10) 切换到实模式(JUMP16 <SEG Real>,<OFFSET Real>);
(11) 实模式下的恢复工作(Real)。
程序中包括以下内容:
(1) 实模式下的数据段(RDataSeg)。其中包括全局描述符表GDT。
(2) 演示任务的LDT段(DemoLDTSeg)。
(3) 演示任务的TSS(DemoTSSSeg)。
(4) 演示任务的0级和2级堆栈段(DemoStack0Seg、DemoStack2Seg)。
(5) 演示任务数据段(DemoDataSeg)。
(6) 子程序代码段(SubRSeg)。
(7) 演示任务代码段(DemoCodeSeg)。
(8) 临时任务的TSS段(TempTSSSeg)。
(9) 临时任务的代码段(TempCodeSeg)。
(10) 实模式下的堆栈段(RStackSeg)。
(11) 实模式下的代码段(RCodeSeg)。
在DemoLDT中,含有以下局部描述符,这些描述符只能被临时任务所使用:
(1) 演示任务的0级和2级堆栈段描述符(DemoStack0、DemoStack2)
(2) 代码段描述符(DemoCode)
(3) 数据段描述符(DemoData)
(4) LDT所在数据段的描述符(ToDLDT)
(5) 临时任务TSS所在数据段的描述符(ToTTSS)
(6) 指向子程序的调用门(ToSubR)
(7) 指向临时任务的任务门(ToTempT)。
其中,在定义前5项描述符时,其基地址为各个段的实模式段基值(16位)。因此,调用InitLDT子程序,将段基值乘以16以后,得到这些段的物理地址(20位),再存放到BaseH、BaseM、BaseL字段中。
GDT中的DemoLDTab、DemoTSS、TempTSS、TempCode、SubR也由InitGDT子程序进行了同样处理。
2. 从临时任务切换到演示任务
TR未在实模式下设置,切换到保护模式后,要把指向临时任务TSS描述符的选择符(TempTSS_Sel)装入TR。之后,才能执行任务切换。利用LTR指令为TR赋值时,并不引用TSS的内容,所以临时任务的TSS不需要被初始化。
在任务切换时,把原任务的现场保存到TR所指示的TSS内,然后再把指向目标任务的TSS描述符的选择符装入TR。从临时任务切换到演示任务时,执行“JUMP16 DemoTSS_Sel, 0 ” 指令,切换过程包括:把临时任务的执行现场保存到临时任务的TSS中;把演示任务的LDT描述符选择符(DemoLDT_Sel)装载到LDTR;从演示任务的TSS中恢复演示任务的现场等。
在TempTSSSeg中,演示任务TSS的CS字段存放的是DemoCode_Sel,对应的描述符是DemoLDT中的DemoCode,其DPL=2;EIP字段存放的是DemoBegin,所以在切换到演示任务后从DemoBegin开始执行,并且CPL=2。由于使用JMP指令进行任务切换,所以不实施任务链接。
3. 演示任务内的特权级变换和
演示任务采用段间调用指令CALL,通过调用门ToSubR调用子程序SubRB。调用门内的它所指示的子程序代码段描述符(SubR_Sel)的DPL=0,所以在调用过程中就发生了从特权级2到特权级0的变换,同时堆栈也被切换到DemoStack0Seg。
通过堆栈传递了两个参数给子程序SubRB。在把参数压入堆栈时,CPL=2,使用的也是对应特权级2 的堆栈(DemoStack2Seg)。通过调用门进入子程序后,CPL=0,使用0级堆栈。为此,把调用门ToSubR中的DCount字段设置为2,提升到特权级0时,CPU从DemoStack2Seg复制了2个双字参数到DemoStack0Seg。
从子程序SubRB返回时,CPL=0变换为CPL=2,堆栈也回到2级堆栈(DemoStack2Seg)。
4. 从演示任务切换到临时任务
演示任务采用段间转移指令JMP,通过任务门ToTempT切换到临时任务。演示任务的现场被保存到演示任务的TSS;临时任务的现场从临时任务的TSS恢复。
临时任务的挂起点是TempCodeSeg的“inc byte ptr es:[0h]”指令,所以恢复后的临时任务从该指令处开始执行。在恢复到临时任务时,SS、DS、FS、GS被恢复为Normal_Sel,ES被恢复为Video_Sel。
5. 任务现场的保存、恢复
程序在临时任务和演示任务之间进行了多次任务切换。临时任务在进入时,清除屏幕内容,将屏幕左上角(0,0)处的字符设为’ 0’ 。
演示任务执行一次,显示EDI的当前值。第1次执行时,EDI的值等于(80*4+50)*2,即000002E4h,在屏幕上位置(4,50)处显示“EDI=000002E 4 ” ,之后,EDI增加160,执行下一行。在切换到临时任务时,EDI的值被保存到DemoTSSSeg中。下一次从临时任务切换到演示任务时,EDI从DemoTSSSeg中恢复,所以在屏幕上位置(5,50)处显示“EDI=00000384”。
从临时任务切换到演示任务时,临时任务的ECX值也被保存到TempTSSSeg中,切换回临时任务时,从TempTSSSeg恢复ECX值,所以,循环一共执行5次,每次将屏幕左上角(0,0)处的字符加1。执行结束时,该字符变为’ 5’ 。
;程序清单: task.asm(任务切换)
INCLUDE 386SCD.INC
RDataSeg SEGMENT PARA USE16 ;实方式数据段
;全局描述符表
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;规范段描述符
Normal Desc <0ffffh,,,ATDW,,>
;视频缓冲区段描述符(DPL=3)
VideoBuf Desc <0ffffh,8000h,0bh,ATDW+DPL3,,>
EFFGDT LABEL BYTE
;演示任务的局部描述符表段的描述符
DemoLDTab Desc <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
;演示任务的任务状态段描述符
DemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
;临时任务的任务状态段描述符
TempTSS Desc <TempTSSLen-1,TempTSSSeg,,AT386TSS+DPL2,,>
;临时代码段描述符
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
;子程序代码段描述符
SubR Desc <SubRLen-1,SubRSeg,,ATCE,D32,>
GDNum = ($-EFFGDT)/8 ;需处理基地址的描述符个数
GDTLen = $-GDT ;全局描述符表长度
VGDTR PDesc <GDTLen-1,> ;GDT伪描述符
SPVar DW ? ;用于保存实方式下的SP
<