《80X86汇编语言程序设计教程》十五 任务切换实例

时间:2022-09-01 07:54:36

1、  理论知识参考"《80X86汇编语言程序设计教程》十二 任务状态段、控制门和控制转移",演示内容:直接通过TSS段的任务切换、通过任务门的任务切换、任务内特权级的变换及参数传递。实现的逻辑功能是:从Temp任务切换到Demo任务以后显示原任务(Temp)的挂起点EIP的值。

 

 

2、  源代码

  “386scd.asm”不再贴上来。参考"《80X86汇编语言程序设计教程》十三 任务内无特权级变换转移实例",演示代码如下:

 

  1 ;DosTest.Asm
2 ;任务切换实例
3 ;逻辑功能:从Temp任务切换到Demo任务以后显示原任务(Temp)的挂起点EIP的值
4
5
6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义
7 .386p
8
9 ;------------------------------------------
10 ;全局描述符表GDT
11 GDTSeg segment para use16
12 GDT label byte
13 ;空描述符
14 dummy DESCRIPTOR<>
15
16 ;规范数据段描述符,存在的可读写数据段
17 Normal DESCRIPTOR<0ffffh,0,0,ATDW,0>
18 Normal_Sel = Normal - GDT
19 EFFGDT label byte
20
21 ;演示任务状态段TSS描述符(16位段,DPL = 0,RPL = 0)
22 DemoTSS DESCRIPTOR<DemoTSSLen -1,DemoTSSSeg,,AT386TSS,>
23 DemoTSS_Sel = DemoTSS - GDT
24
25 ;演示任务LDT段描述符(16位段,DPL = 0,RPL = 0)
26 DemoLDTab DESCRIPTOR<DemoLDTLen-1,DemoLDTSeg,,ATLDT,>
27 DemoLDT_Sel = DemoLDTab - GDT
28
29 ;临时任务任务状态TSS描述符(16位段,DPL = 2,RPL = 0)
30 TempTSS DESCRIPTOR<TempTSSLen - 1,TempTSSSeg,,AT386TSS + DPL2,>
31 TempTSS_Sel = TempTSS - GDT
32
33 ;临时任务代码段描述符(16位段,DPL = 0,RPL = 0)
34 TempCode DESCRIPTOR<0ffffh,TempCodeSeg,,ATCE,>
35 TempCode_Sel = TempCode - GDT
36
37 ;子程序代码段描述符(32位段,DPL = 0,RPL = 3)
38 SubR DESCRIPTOR<SubRLen - 1,SubRSeg,,ATCE + D32,>
39 SubR_Sel = SubR - GDT + RPL3
40
41 ;视频缓冲区段描述符,段界限0fffffh(16位段,DPL = 3,RPL = 0)
42 VideoBuff DESCRIPTOR<0ffffh,0,0,0f00h + ATDW + DPL3,0>
43 Video_Sel = VideoBuff - GDT
44
45 GDTNum = ($ - EFFGDT)/(size DESCRIPTOR)
46
47 ;指向演示任务Demo的任务门(DPL = 3,RPL = 0)
48 ;ToDemoT GATE<0,DemoTSS_Sel,0,ATTASKGAT + DPL3,0>
49 ;ToDemoT_Sel = (ToDemoT - GDT) + TIL
50
51 GDTLen = $ - GDT
52 GDTSeg ends
53
54 ;------------------------------------------
55 ;演示任务局部描述符表LDT
56 DemoLDTSeg segment para use16
57 DemoLDT label byte
58
59 ;0级堆栈段描述符(32位段,DPL = 0,RPL = 0)
60 DemoStack0 DESCRIPTOR<DemoStack0Len - 1,DemoStack0Seg,,ATDW + D32,>
61 DemoStack0_Sel = (DemoStack0 - DemoLDT) + TIL
62
63 ;2级堆栈段描述符(32位段,DPL = 2,RPL = 2)
64 DemoStack2 DESCRIPTOR<DemoStack2Len - 1,DemoStack2Seg,,ATDW + D32 + DPL2,>
65 DemoStack2_Sel = (DemoStack2 - DemoLDT) + TIL + RPL2
66
67 ;演示任务代码段描述符(32位段,DPL = 2,RPL = 2)
68 DemoCode DESCRIPTOR<DemoCodeLen - 1,DemoCodeSeg,,ATCE + D32 + DPL2,>
69 DemoCode_Sel = (DemoCode - DemoLDT) + TIL + RPL2
70
71 ;演示任务数据段描述符(32位段,DPL = 3,RPL = 0)
72 DemoData DESCRIPTOR<DemoDataLen - 1,DemoDataSeg,,ATDW + D32 + DPL3,>
73 DemoData_Sel = (DemoData - DemoLDT) + TIL
74
75 ;把演示任务LDT作为普通数据段描述的描述符(16位段,DPL = 2,RPL = 0)
76 ToDLDT DESCRIPTOR<DemoLDTLen - 1,DemoLDTSeg,,ATDW + DPL2,>
77 ToDLDT_Sel = (ToDLDT - DemoLDT) + TIL
78
79 ;把临时任务TSS作为普通数据段描述的描述符(16位段,DPL = 2,RPL = 0)
80 ToTTSS DESCRIPTOR<TempTSSLen - 1,TempTSSSeg,,ATDW + DPL2,>
81 ToTTSS_Sel = (ToTTSS - DemoLDT) + TIL
82
83 ;LDT含非门描述符个数
84 DemoLDTNum = ($ - DemoLDT)/(size DESCRIPTOR)
85
86 ;指向子程序SubRB的调用门(DPL = 3,RPL = 2)
87 ToSubR GATE<low offset SubRB,SubR_Sel,0,AT386CGAT + DPL3,0>
88 ToSubR_Sel = (ToSubR - DemoLDT) + TIL + RPL2
89
90 ;指向临时任务Temp的任务门(DPL = 3,RPL = 0)
91 ToTempT GATE<0,TempTSS_Sel,0,ATTASKGAT + DPL3,0>
92 ToTempT_Sel = (ToTempT - DemoLDT) + TIL
93
94 ;LDT字节长度
95 DemoLDTLen = $ - DemoLDT
96 DemoLDTSeg ends
97
98
99 ;------------------------------------------
100 ;演示任务任务状态段(TSS)
101 DemoTSSSeg segment para use16
102 DTSS TASKSS<>
103 db 0ffh ;IO许可位结束标志
104 DemoTSSLen = $ - DemoTSSSeg
105 DemoTSSSeg ends
106
107
108 ;------------------------------------------
109 ;临时任务的任务状态段(TSS)
110 TempTSSSeg segment para use16
111 TempTask TASKSS<>
112 db 0ffh
113 TempTSSLen = $ - TempTSSSeg
114 TempTSSSeg ends
115
116 ;------------------------------------------
117 ;演示任务0级堆栈段(32位段)
118 DemoStack0Seg segment para use32
119 DemoStack0Len = 1024
120 db DemoStack0Len dup(0)
121 DemoStack0Seg ends
122
123
124 ;------------------------------------------
125 ;演示任务2级堆栈段(32位段)
126 DemoStack2Seg segment para use32
127 DemoStack2Len = 512
128 db DemoStack2Len dup(0)
129 DemoStack2Seg ends
130
131 ;------------------------------------------
132 ;演示任务数据段(32位段)
133 DemoDataSeg segment para use32
134 message db 'Value = ',0
135 DemoDataLen = $ - DemoDataSeg
136 DemoDataSeg ends
137
138 ;------------------------------------------
139 ;子程序代码段(32位段)
140 SubRSeg segment para use32
141 assume cs:SubRSeg
142 ;从堆栈中取出参数显示
143 SubRB proc far
144 push ebp
145 mov ebp,esp
146 pushad
147 ;从堆栈(0级)中取提示信息串偏移
148 mov eax,[ebp + 12]
149 mov esi,eax
150 mov ah,7
151 jmp short SubR2
152 SubR1:
153 stosw
154 SubR2:
155 lodsb
156 or al,al
157 jnz SubR1
158 ;从堆栈(0级)中取显示值
159 mov edx,[ebp + 16]
160 mov ecx,8
161 SubR3:
162 rol edx,4
163 mov al,dl
164 call HTOASC
165 stosw
166 loop SubR3
167 popad
168 pop ebp
169 ret 8
170 SubRB endp
171
172 HTOASC proc
173 and al,0fh
174 add al,90h
175 daa
176 adc al,40h
177 daa
178 ret
179 HTOASC endp
180
181 SubRLen = $ - SubRSeg
182
183 SubRSeg ends
184
185 ;------------------------------------------
186 ;演示任务的代码段(32位段)
187 DemoCodeSeg segment para use32
188 assume cs:DemoCodeSeg
189 DemoBegin:
190 ;把要复制的参数个数置入调用门
191 mov fs:ToSubR.Dcount,2
192 ;向堆栈(2级)中压入参数
193 mov dword ptr gs:TempTask.TREIP,offset ToReal
194 push dword ptr gs:TempTask.TREIP
195 push offset message
196 ;通过调用门调用子程序SubRB
197 CALL32 ToSubR_Sel,0
198 ;把指向规范数据段描述符的选择子填入临时任务TSS
199 assume ds:TempTSSSeg
200 push gs
201 pop ds
202 mov ax,Normal_Sel
203 mov TempTask.TRDS,ax
204 mov TempTask.TRES,ax
205 mov TempTask.TRFS,ax
206 mov TempTask.TRGS,ax
207 mov TempTask.TRSS,ax
208 ;通过任务切换到临时任务
209 JUMP32 ToTempT_Sel,0
210 DemoCodeLen = $ - DemoCodeSeg
211 DemoCodeSeg ends
212
213 ;------------------------------------------
214 ;临时代码段(16位段,0级)
215 TempCodeSeg segment para use16
216 assume cs:TempCodeSeg
217 Virtual:
218 ;装载TR
219 mov bx,TempTSS_Sel
220 ltr bx
221 ;直接切换到演示任务
222 ;JUMP16 ToDemoT_Sel,0
223 JUMP16 DemoTSS_Sel,0
224 ToReal:
225 ;准备切换回实模式
226 clts ;清任务切换标志
227 ;
228 mov eax,cr0
229 and ax,0fffeh
230 mov cr0,eax
231 JUMP16 <seg Real>,<offset Real>
232 TempCodeLen = $ - TempCodeSeg
233 TempCodeSeg ends
234
235
236 ;------------------------------------------
237 ;实模式下的数据段
238 RDataSeg segment para use16
239 VGDTR PDESC<GDTLen - 1,>
240 SPVAR dw ?
241 SSVAR dw ?
242 RDataSeg ends
243 ;------------------------------------------
244 ;实模式下的代码段
245 RCodeSeg segment para use16
246 assume cs:RCodeSeg,ds:RDataSeg,es:RDataSeg
247 start:
248 mov ax,RDataSeg
249 mov ds,ax
250 cld
251 ;初始化GDT
252 call INIT_GDT
253 ;初始化演示任务LDT
254 mov ax,DemoLDTSeg
255 mov fs,ax
256 mov si,offset DemoLDT
257 mov cx,DemoLDTNum
258 call INIT_LDT
259 ;初始化TSS
260 call INIT_TSS
261 ;实模式堆栈保护
262 mov SSVAR,ss
263 mov SPVAR,sp
264 ;装载GDTR和切换到保护模式
265 lgdt fword ptr VGDTR
266 cli
267 mov eax,cr0
268 or eax,1
269 mov cr0,eax
270 JUMP16 TempCode_Sel,<offset Virtual>
271 Real:
272 ;又回到实模式
273 mov ax,RDataSeg
274 mov ds,ax
275 lss sp,dword ptr SPVAR
276 sti
277 mov ax,0700h
278 int 21h
279 mov ax,4c00h
280 int 21h
281 ;------------------------------------------
282 ;初始化全局描述符表的子程序
283 ;(1)把定义时预置的段值转换成32位段基地址并置入描述符内相应字段
284 ;(2)初始化为GDTR准备的伪描述符
285 INIT_GDT proc near
286 push ds
287 mov ax,GDTSeg
288 mov ds,ax
289 mov cx,GDTNum ;初始化描述符的个数
290 mov si,offset EFFGDT ;开始偏移
291 assume si:ptr DESCRIPTOR
292 INITG:
293 mov ax,[si].BaseL ;取出预置的段值
294 movzx eax,ax ;扩展到32位
295 shl eax,4
296 shld edx,eax,16 ;分解到2个16位寄存器
297 mov [si].BaseL,ax ;置入描述符相应字段
298 mov [si].BaseM,dl
299 mov [si].BaseH,dh
300 add si,size DESCRIPTOR ;调整到下一个描述符
301 loop INITG
302 assume si:nothing
303 pop ds
304 ;
305 mov bx,16 ;初始化为GDTR准备的伪描述符
306 mov ax,GDTSeg
307 mul bx
308 mov word ptr VGDTR.Base,ax
309 mov word ptr VGDTR.Base + 2,dx
310 ret
311 INIT_GDT endp
312 ;------------------------------------------
313 ;初始化演示任务局部描述符表的子程序
314 ;把定义时预置的段值转换成32位段基地址并置入描述符内的相应字段
315 ;入口参数:FS:SI = 第一个要初始化的描述符
316 ; CX = 要初始化的描述符个数
317 INIT_LDT proc
318 assume si:ptr DESCRIPTOR
319 ILDT:
320 mov ax,fs:[si].BaseL
321 movzx eax,ax
322 shl eax,4
323 shld edx,eax,16
324 mov fs:[si].BaseL,ax
325 mov fs:[si].BaseM,dl
326 mov fs:[si].BaseH,dh
327 add si,size DESCRIPTOR
328 loop ILDT
329 assume si:nothing
330 ret
331 INIT_LDT endp
332 ;------------------------------------------
333 ;初始化TSS段子程序
334 INIT_TSS proc
335 mov ax,DemoTSSSeg
336 mov fs,ax
337 mov si,offset DTSS
338 assume si:ptr TASKSS
339 mov fs:[si].TRLink,0 ;链接字 = 0
340 mov fs:[si].TRESP0,DemoStack0Len ;0级堆栈指针
341 mov fs:[si].TRSS0,DemoStack0_Sel
342 mov fs:[si].TRESP2,DemoStack2Len ;2级堆栈指针
343 mov fs:[si].TRSS2,DemoStack2_Sel
344 mov fs:[si].TRESP,DemoStack2Len ;当前使用2级堆栈
345 mov fs:[si].TRSS,DemoStack2_Sel
346 mov fs:[si].TRCR3,0 ;CR3 = 0
347 mov fs:[si].TREDI,0b8000h ;ES:EDI指向视频输出缓冲区
348 mov fs:[si].TRES,Video_Sel
349 mov fs:[si].TREIP,offset DemoBegin ;CS:EIP指向演示代码段起始位置
350 mov fs:[si].TRCS,DemoCode_Sel
351 mov fs:[si].TRDS,DemoData_Sel ;DS指向演示任务数据段
352 mov fs:[si].TRFS,ToDLDT_Sel ;FS指向演示任务LDT(被当成数据段)
353 mov fs:[si].TRGS,ToTTSS_Sel ;GS指向临时任务TSS(被当成数据段)
354 mov fs:[si].TRLDT,DemoLDT_Sel ;演示任务LDTR
355 assume si:nothing
356 ret
357 INIT_TSS endp
358 RCodeSeg ends
359 end start

 

 

3、  源代码说明

  依然存在的一些类似问题,要么直接copy这里的代码,要么对比原书去找它们了,不重要。须注意的是在引用结构的时候这里采用了一种新的形式。

 

 

4、  输出

《80X86汇编语言程序设计教程》十五 任务切换实例 

 

 

5、  测试说明

  1)  从Temp任务直接通过TSS切换到Dome任务

    LTR指令装载Temp任务的TSS并刷新高速缓冲区,以指示当前任务。Temp任务采用段间JMP指令进行任务切换:CPL = DPL = 0且目标TSS指示的是一个可用任务。这里有个很纠结的地方,切换任务以后,Temp任务的现场的确被保存了,只不过EIP寄存器都被初始化为0,导致系统奔溃:

《80X86汇编语言程序设计教程》十五 任务切换实例 

    而且输出如下:

 《80X86汇编语言程序设计教程》十五 任务切换实例

    从输出来看,EIP的确被初始化为0了,为什么是没有保存现场而是被初始化为0呢?因为我在JMP前强行给EIP赋值过,测试发现输出还是0。我测试自己电脑的CPU是686,不知道是不是CPU对JMP切换任务已经更换了策略,还是说有什么潜在的问题没有发现,暂时不得而知,总之,我尝试了各种方法,包括使用任务门、修改Temp的TSS任务为忙的TSS,这些尝试都是导致的系统直接奔溃。

  2)  从Dome任务通过任务门切换到Temp任务

    采用段间JMP,通过任务门切换回Temp任务:source.CPL(=2)<=gate.DPL(=3)且source.RPL(=0)<=gate.RPL(=0)所以成功进入任务门。进入门以后不再对权限等级进行保护检测(可以切换到目标任务的任何权限等级),由于目标任务可用,则直接实行了切换。由于上述毛病的存在,我这这里强势设置了一下EIP,使用的语句是“mov  dword ptr gs:TempTask.TREIP,offset ToReal”。

  3)  演示任务内特权级变换和堆栈传递参数

    采用段间CALL,通过调用门“ToSunR”调用子程序SubR。由于有权限等级的切换,栈也进行了切换。首先压入参数,须注意,压入时须保证CPL<=DPL且RPL<=DPL,栈是一种存储描述符。同时,注意调用门中Dcount字段,这个字段是压入参数的双字数目,这里压入了2个双字,所以设置为2。进入门的保护检测不再赘述。执行子程序时注意堆栈平衡。其它的没什么好说的。

  4)  别名技术

    在为了把调用门“ToSunR”中的Dcount字段设置为2时,使用了数据段描述符“ToDLDT”描述门所在演示任务的LDT段;此外在修改Temp任务的TSS时,也将其视普通的数据段。对同一内存段使用不同的解释方式,就是别名技术,这个不见得多么高深。