《80X86汇编语言程序设计教程》十七 中断处理实例

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

1、  理论知识参考"《80X86汇编语言程序设计教程》十六 80386的中断和异常",这里演示中断处理,包括外部中断处理程序与陷阱处理程序。实现的逻辑功能是:在屏幕左上角以倒计时(8秒)方式显示秒数,倒计时结束退出程序。

 

 

2、  源代码

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

 

  1 ;DosTest.Asm
2 ;演示任中断处理
3 ;逻辑功能:倒计时一定秒速,几时到0时自动终止程序
4
5
6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义
7 .386p
8
9 ;------------------------------------------
10 ;部分常量定义
11 EOICOM = 20H ;外部中断处理结束命令
12 ICREGP = 20H ;中断控制寄存器端口地址
13 IMREGP = 21H ;中断屏蔽寄存器端口地址
14
15 ;------------------------------------------
16 ;全局描述符表GDT
17 GDTSeg segment para use16
18 GDT label byte
19 ;空描述符
20 dummy DESCRIPTOR<>
21
22 ;规范数据段描述符,存在的可读写数据段
23 Normal DESCRIPTOR<0ffffh,0,0,ATDW,0>
24 Normal_Sel = Normal - GDT
25 EFFGDT label byte
26
27 ;临时任务代码段描述符(16位段,DPL = 0,RPL = 0)
28 TempCode DESCRIPTOR<0ffffh,TempCodeSeg,,ATCE,>
29 TempCode_Sel = TempCode - GDT
30
31 ;演示任务代码段描述符(16位段,DPL = 0,RPL = 0)
32 DemoCode DESCRIPTOR<DemoCodeLen,DemoCodeSeg,,ATCE,>
33 DemoCode_Sel = DemoCode - GDT
34
35 ;演示任务数据段描述符(16位段,DPL = 0,RPL = 0)
36 DemoData DESCRIPTOR<DemoDataLen - 1,DemoDataSeg,,ATDW,>
37 DemoData_Sel = DemoData - GDT
38
39 ;演示任务堆栈段描述符(16位段,DPL = 0,RPL = 0)
40 DemoStack DESCRIPTOR<DemoStackLen - 1,DemoStackSeg,,ATDWA,>
41 DemoStack_Sel = DemoStack - GDT
42
43 ;FEH号中断处理程序(显示程序)代码段描述符
44 EchoCode DESCRIPTOR<EchoCodeLen - 1,EchoCodeSeg,,ATCE,>
45 EchoCode_Sel = EchoCode - GDT
46
47 ;FEH号中断处理程序(显示程序)数据段描述符
48 EchoData DESCRIPTOR<EchoDataLen - 1,EchoDataSeg,,ATDW,>
49 EchoData_Sel = EchoData - GDT
50
51 ;视频缓冲区段描述符(16位段,DPL = 0,RPL = 0)
52 VideoBuff DESCRIPTOR<80 * 25 * 2 - 1,0b800h,,ATDW,0>
53 VideoBuff_Sel = VideoBuff - GDT
54
55 ;8H号中断处理程序代码段描述符
56 TICode DESCRIPTOR<TICodeLen - 1,TICodeSeg,,ATCE,>
57 TICode_Sel = TICode - GDT
58
59 ;8H号中断处理程序数据段描述符
60 TIData DESCRIPTOR<TIDataLen - 1,TIDataSeg,,ATDW,>
61 TIData_Sel = TIData - GDT
62
63 ;其它中断或异常处理程序代码段描述符
64 OtherCode DESCRIPTOR<OtherCodeLen - 1,OtherCodeSeg,,ATCE,>
65 OtherCode_Sel = OtherCode - GDT
66
67 ;GDT中需要初始化基地址的描述符个数
68 GDTNum = ($ - EFFGDT)/(size DESCRIPTOR)
69
70 ;GDT段长度
71 GDTLen = $ - GDT
72 GDTSeg ends
73
74 ;------------------------------------------
75 ;中断描述符表IDT
76 IDTSeg segment para use16
77 IDT label byte
78
79 ;从00H~07H的8个陷阱门描述符
80 rept 8
81 GATE<OtherBegin,OtherCode_Sel,0,AT386TGAT,0>
82 endm
83
84 ;对应08H号(时钟)中断处理程序的中断门描述符
85 INT08 GATE<TIBegin,TICode_Sel,0,AT386TGAT,0>
86
87 ;从09H~FDH的245个陷阱门描述符
88 rept 254 - 9
89 GATE<OtherBegin,OtherCode_Sel,0,AT386TGAT,0>
90 endm
91
92 ;对应0FEH号中断处理程程序的陷阱门描述符
93 INTFE GATE<EchoBegin,EchoCode_Sel,0,AT386TGAT,0>
94
95 ;对应0FFH号中断处理程序的陷阱门描述符
96 GATE<OtherBegin,OtherCode_Sel,0,AT386TGAT,0>
97
98 ;中断描述符表长度
99 IDTLen = $ - IDT
100 IDTSeg ends
101
102 ;------------------------------------------
103 ;其它中断或异常处理程序代码段
104 OtherCodeSeg segment para use16
105 assume cs:OtherCodeSeg
106 OtherBegin:
107 mov ax,VideoBuff_Sel
108 mov es,ax
109 mov ah,17h
110 mov al,'!'
111 mov es:[0],ax ;在屏幕左上角显示蓝底白色符号"!"
112 jmp $ ;无限循环
113 OtherCodeLen = $ - OtherCodeSeg
114 OtherCodeSeg ends
115
116 ;------------------------------------------
117 ;8H号(时钟)中断处理程序的数据段
118 TIDataSeg segment para use16
119 Count db 0 ;中断发生的计数器
120 TIDataLen = $ - TIDataSeg
121 TIDataSeg ends
122
123 ;------------------------------------------
124 ;8H号(时钟)中断处理程序的代码段
125 TICodeSeg segment para use16
126 assume cs:TICodeSeg,ds:TIDataSeg
127 TIBegin:
128 push eax
129 push ds
130 push fs
131 push gs ;保护现场
132 mov ax,TIData_Sel
133 mov ds,ax ;置中断处理程序数据段
134 mov ax,EchoData_Sel
135 mov fs,ax ;置显示过程数据段
136 mov ax,DemoData_Sel
137 mov gs,ax ;置演示程序数据段
138 ;
139 cmp Count,0
140 jnz TI2 ;计数非0表示未到1秒
141 mov Count,18 ;每秒约18次
142 int 0feh ;调用0feh号中断处理程序显示
143 cmp fs:Mess,'0'
144 jnz TI1
145 mov gs:Flag,1 ;显示符号'0'时置标记
146 TI1:
147 dec fs:Mess ;调整显示符号
148 TI2:
149 dec Count ;调整计数
150 pop gs
151 pop fs
152 pop ds ;恢复现场
153 mov al,EOICOM
154 out ICREGP,al ;通知中断处理器处理结束
155 pop eax
156 iretd
157 TICodeLen = $ - TICodeSeg
158 TICodeSeg ends
159
160 ;------------------------------------------
161 ;0FEH号中断处理程序的数据段
162 EchoDataSeg segment para use16
163 Mess db '8',07h
164 EchoDataLen = $ - EchoDataSeg
165 EchoDataSeg ends
166
167 ;------------------------------------------
168 ;0FEH号中断处理程序的代码段
169 EchoCodeSeg segment para use16
170 assume cs:EchoCodeSeg,ds:EchoDataSeg
171 EchoBegin:
172 push ax
173 push ds
174 push es ;保护现场
175 mov ax,EchoData_Sel
176 mov ds,ax ;置显示过程数据段
177 mov ax,VideoBuff_Sel
178 mov es,ax ;置视频数据段
179 mov ax,word ptr Mess
180 mov es:[0],ax ;显示符号
181 pop es
182 pop ds
183 pop ax ;恢复现场
184 iretd
185 EchoCodeLen = $ - EchoCodeSeg
186 EchoCodeSeg ends
187
188 ;------------------------------------------
189 ;演示任务堆栈段
190 DemoStackSeg segment para use16
191 DemoStackLen = 1024
192 db DemoStackLen dup(0)
193 DemoStackSeg ends
194
195 ;------------------------------------------
196 ;演示任务数据段
197 DemoDataSeg segment para use16
198 Flag db 0
199 DemoDataLen = $ - DemoDataSeg
200 DemoDataSeg ends
201
202 ;------------------------------------------
203 ;演示任务的代码段
204 DemoCodeSeg segment para use16
205 assume cs:DemoCodeSeg,ds:DemoDataSeg
206 DemoBegin:
207 mov ax,DemoStack_Sel
208 mov ss,ax ;置堆栈指针
209 mov sp,DemoStackLen
210 mov ax,DemoData_Sel
211 mov ds,ax ;置数据段
212 mov es,ax
213 mov fs,ax
214 mov gs,ax
215 ;
216 mov al,1111110b ;置中断屏蔽寄存器
217 out IMREGP,al ;仅开放时钟中断
218 sti
219 DemoConti:
220 cmp Flag,0 ;判标志
221 jz DemoConti ;为0继续
222 ;
223 cli ;关中断
224 ;转回临时代码段,准备回实模式
225 Over:
226 JUMP16 TempCode_Sel,<offset ToDOS>
227 DemoCodeLen = $ - DemoCodeSeg
228 DemoCodeSeg ends
229
230 ;------------------------------------------
231 ;临时代码段
232 TempCodeSeg segment para use16
233 assume cs:TempCodeSeg
234 Virtual:
235 ;转演示代码段
236 JUMP16 DemoCode_Sel,DemoBegin
237 ToDOS:
238 ;演示返回实模式
239 mov ax,Normal_Sel
240 mov ds,ax
241 mov es,ax
242 mov fs,ax
243 mov gs,ax
244 mov ss,ax
245 mov eax,cr0
246 and eax,0fffffffeh
247 mov cr0,eax
248 JUMP16 <seg Real>,<offset Real>
249 TempCodeSeg ends
250
251
252 ;------------------------------------------
253 ;实模式下的数据段
254 RDataSeg segment para use16
255 VGDTR PDESC<GDTLen - 1,> ;GDT伪描述符
256 VIDTR PDESC<IDTLen - 1,> ;IDT伪描述符
257 NORVIDTR PDESC<3ffh,0> ;保存IDTR原值
258 SPVar dw ? ;保存原堆栈
259 SSVar dw ?
260 IMASKREGV db ? ;保存原中断屏蔽寄存器值
261 RDataSeg ends
262 ;------------------------------------------
263 ;实模式下的代码段
264 RCodeSeg segment para use16
265 assume cs:RCodeSeg,ds:RDataSeg
266 start:
267 mov ax,RDataSeg
268 mov ds,ax
269 cld
270 ;初始化GDT
271 call INIT_GDT
272 ;初始化IDT
273 call INIT_IDT
274 ;实模式堆栈保护
275 mov SSVar,ss
276 mov SPVar,sp
277 ;保存IDTR值
278 sidt NORVIDTR
279 ;保存中断屏蔽字节
280 ;IMREGP = 21H,中断屏蔽寄存器端口地址
281 in al,IMREGP
282 mov IMASKREGV,al
283 ;装载GDTR和IDTR,切换到保护模式
284 lgdt fword ptr VGDTR
285 cli
286 lidt fword ptr VIDTR
287 mov eax,cr0
288 or eax,1
289 mov cr0,eax
290 JUMP16 <TempCode_Sel>,<offset Virtual>
291 Real:
292 ;又回到实模式
293 mov ax,RDataSeg
294 mov ds,ax ;置实模式数据段
295 lss sp,dword ptr SPVar ;恢复堆栈指针
296 lidt NORVIDTR ;恢复IDTR
297 mov al,IMASKREGV ;恢复中断屏蔽字节
298 out IMREGP,al
299 sti ;开中断
300 mov ax,4c00h
301 int 21h
302 ;------------------------------------------
303 ;初始化全局描述符表的子程序
304 ;(1)把定义时预置的段值转换成32位段基地址并置入描述符内相应字段
305 ;(2)初始化为GDTR准备的伪描述符
306 INIT_GDT proc near
307 push ds
308 mov ax,GDTSeg
309 mov ds,ax
310 mov cx,GDTNum ;初始化描述符的个数
311 mov si,offset EFFGDT ;开始偏移
312 assume si:ptr DESCRIPTOR
313 INITG:
314 mov ax,[si].BaseL ;取出预置的段值
315 movzx eax,ax ;扩展到32位
316 shl eax,4
317 shld edx,eax,16 ;分解到2个16位寄存器
318 mov [si].BaseL,ax ;置入描述符相应字段
319 mov [si].BaseM,dl
320 mov [si].BaseH,dh
321 add si,size DESCRIPTOR ;调整到下一个描述符
322 loop INITG
323 assume si:nothing
324 pop ds
325 ;
326 mov bx,16 ;初始化为GDTR准备的伪描述符
327 mov ax,GDTSeg
328 mul bx
329 mov word ptr VGDTR.Base,ax
330 mov word ptr VGDTR.Base + 2,dx
331 ret
332 INIT_GDT endp
333
334 ;------------------------------------------
335 ;初始化IDTR伪描述符子程序
336 INIT_IDT proc
337 mov bx,16
338 mov ax,IDTSeg
339 mul bx
340 mov word ptr VIDTR.Base,ax
341 mov word ptr VIDTR.Base + 2,dx
342 ret
343 INIT_IDT endp
344 RCodeSeg ends
345 end start

 

 

3、  源代码需要说明的地方

  1)  几个老毛病,一直存在

  2)  在设置视频缓冲区段的时候,与之前不同,这里设置了基地址为0b8000h,由于GDT在初始化的时候会将段描述符中基地址全部左移4位,所以填写的时候是0b800h

 

 

4、  测试效果

《80X86汇编语言程序设计教程》十七 中断处理实例 

 

 

5、  测试说明:

  1)  关于时钟中断8H号中断向量的说明

    8H号与保护模式下的双重故障异常发生冲突,本来需要重新设置时间中断向量号,这里为了简便,仍然保留了这个向量号(但是需要保证不发生双重故障异常,否则将导致异常无法得到处理)。8259A控制外部中断请求,屏蔽字端口号为21H,只开启时钟中断时屏蔽字为11111110B。定时器在系统加电时初始化为每约55微秒发出一次中断,所以每秒大约中断18次(55 * 18 = 990),有关8259A与定时器的详细内容参考"《80X86汇编语言程序设计教程》四 输入输出与中断"。

  2)  时钟中断处理程序

    中断门并不切换任务,由于时钟中断为外部中断,发出中断的时机是处理器本身不可控的,因此需在中断处理程序中保护现场。在代码内使用了3个数据段:中断程序数据段(Count用于计数18次达到一秒)、显示过程数据段(Mess实时显示的ASCII码秒数)和演示程序数据段(Flag标志判断演示任务是否已经结束)。其工作原理是每秒钟减少一次Mess,当Mess为字符‘0’时调整Flag为1,控制主程序退出。这里须注意一点,每次中断处理完毕须通知8259A处理完毕。

  3)  利用“软中断”(陷阱处理)程序实现显示

    在时钟中断处理程序中,每1秒钟“软中断”一次来实时显示数值。

  4)  没有权限等级的切换

    为了简化,全都描述符都是R0级,没有权限等级的切换,也没有堆栈切换。

  5)  IDT初始化

    IDT中初始化了256个中断门描述符,真正使用的只有2个,一个08H时钟中断处理程序的中断描述符与0FEH号用于显示用的陷阱处理程序描述符。由于IDT表中全部使用的是门描述符,并没有32位段基地址,且入口点偏移较小,所以无需向GDT那样进行初始化。INIT_IDT只不过是设置了IDT伪描述符。

  6)  保存和装载IDTR寄存器

    保持IDTR使用指令SIDT,要保存到的缓冲区地址必须是一个IDTR的伪描述符类型(同GDTR一样使用结构PDESC,它低字为界限,高双字为基地址)。装载IDTR寄存器时,由于要访问GDT,所以必须先装载GDTR。此外,必须在关闭中断的情况下进行。