下面给出一个用于演示中断处理的实例。该实例的逻辑功能是,在屏幕的左上角以倒计时方式显
示秒为单位的时间,在时间用完后结束。该实例演示内容包括:外部中断处理程序和陷阱处理程序。
1.源程序组织和清单
本实例由如下几部分组成:
(1)全局描述符表GDT。GDT中除了含有常见的几个描述符外,还含有描述时钟中断处理程序所使用
的代码段和数据段描述符,以及描述显示程序所使用的代码段和数据段描述符。
(2)中断描述符表IDT。为了在保护模式下响应中断和处理异常,必须有IDT。IDT含有256个门描述
符。8号安排的是一个通向时钟中断处理程序的中断门,0FEH号安排的是通向显示处理程序的陷阱
门,其它均安排成通向其它中断或异常处理程序的陷阱门。
(3)时钟中断处理程序的代码段和数据段。
(4)实现直接写显示缓冲区进行显示的程序代码段和数据段。
(5)处理其它中断或异常的处理程序的代码段。
(6)演示程序的代码段、数据段和堆栈段。
(7)实模式下执行的启动和结束程序代码段和数据段。
源程序清单如下:
;名称:ASM6.ASM
;功能:演示中断处理的实现
;编译:TASM ASM6.ASM
;连接:TLINK ASM6.OBJ
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
;部分常量定义
;----------------------------------------------------------------------------
EOICOM = 20h ;外部中断处理结束命令
ICREGP = 20h ;中断控制寄存器端口地址
IMREGP = 21h ;中断屏蔽寄存器端口地址
;----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 ;全局描述符表数据段(16位)
;----------------------------------------------------------------------------
;全局描述符表GDT
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;规范段描述符
Normal Desc <0ffffh,,,ATDW,,>
;视频缓冲区段描述符(DPL=3)
VideoBuf Desc <0ffffh,8000h,0bh,ATDW,,>
;----------------------------------------------------------------------------
EFFGDT LABEL BYTE
;临时代码段描述符
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
;演示代码段描述符
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE,,>
;演示数据段描述符
DemoData Desc <DemoDataLen-1,DemoDataSeg,,ATDW,,>
;演示堆栈段描述符
DemoStack Desc <DemoStackLen-1,DemoStackSeg,,ATDWA,,>
;0feh号中断处理程序(显示程序)代码段描述符
EchoCode Desc <EchoCodeLen-1,EchoCodeSeg,,ATCE,,>
;0feh号中断处理程序(显示程序)数据段描述符
EchoData Desc <EchoDataLen-1,EchoDataSeg,,ATDW,,>
;8号中断处理程序代码段描述符
TICode Desc <TICodeLen-1,TICodeSeg,,ATCE,,>
;8号中断处理程序数据段描述符
TIData Desc <TIDataLen-1,TIDataSeg,,ATDW,,>
;其它中断或异常处理程序代码段描述符
Other Desc <OtherCodeLen-1,OtherCodeSeg,,ATCE,,>
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全局描述符表长度
GDNum = ($-EFFGDT)/(SIZE Desc) ;需特殊处理的描述符数
;----------------------------------------------------------------------------
Normal_Sel = Normal-GDT ;规范段描述符选择子
Video_Sel = VideoBuf-GDT ;视频缓冲区段描述符选择子
;----------------------------------------------------------------------------
TempCode_Sel = TempCode-GDT ;临时代码段的选择子
DemoCode_Sel = DemoCode-GDT ;演示代码段的选择子
DemoData_Sel = DemoData-GDT ;演示数据段的选择子
DemoStack_Sel = DemoStack-GDT ;演示堆栈段的选择子
EchoCode_Sel = EchoCode-GDT ;0feh号中断程序代码段选择子
EchoData_Sel = EchoData-GDT ;0feh号中断程序数据段选择子
TICode_Sel = TICode-GDT ;8号中断程序代码段选择子
TIData_Sel = TIData-GDT ;8号中断程序数据段选择子
Other_Sel = Other-GDT ;其它中断或异常代码段选择子
;----------------------------------------------------------------------------
GDTSeg ENDS ;全局描述符表段定义结束
;----------------------------------------------------------------------------
IDTSeg SEGMENT PARA USE16 ;中断描述符表数据段(16位)
;----------------------------------------------------------------------------
IDT LABEL BYTE ;中断描述符表
;0--7的8个陷阱门描述符
REPT 8
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
;对应8号(时钟)中断处理程序的门描述符
Gate <TIBegin,TICode_Sel,,AT386IGate,>
;从9--0fdh的245个陷阱门描述符
REPT 245
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
;对应0feh号中断处理程序的陷阱门描述符
Gate <EchoBegin,EchoCode_Sel,,AT386TGate,>
;对应0ffh号中断处理程序的陷阱门描述符
Gate <OtherBegin,Other_Sel,,AT386TGate,>
;----------------------------------------------------------------------------
IDTLen = $-IDT
;----------------------------------------------------------------------------
IDTSeg ENDS ;中断描述符表段定义结束
;----------------------------------------------------------------------------
;其它中断或异常处理程序的代码段
;----------------------------------------------------------------------------
OtherCodeSeg SEGMENT PARA USE16
ASSUME CS:OtherCodeSeg
;----------------------------------------------------------------------------
OtherBegin PROC FAR
mov ax,Video_Sel
mov es,ax
mov ah,17h ;在屏幕左上角显示兰底白字
mov al,'!' ;符号"!"
mov WORD PTR es:[0],ax
jmp $ ;无限循环
OtherBegin ENDP
;----------------------------------------------------------------------------
OtherCodeLen = $
OtherCodeSeg ENDS
;----------------------------------------------------------------------------
;8号中断处理程序的数据段
;----------------------------------------------------------------------------
TIDataSeg SEGMENT PARA USE16
Count DB 0 ;中断发生的计数器
TIDataLen = $
TIDataSeg ENDS
;----------------------------------------------------------------------------
;8号中断处理程序的代码段
;----------------------------------------------------------------------------
TICodeSeg SEGMENT PARA USE16
ASSUME CS:TICodeSeg,DS:TIDataSeg
;----------------------------------------------------------------------------
TIBegin PROC FAR
push eax ;保护现场
push ds
push fs
push gs
mov ax,TIData_Sel ;置中断处理程序数据段
mov ds,ax
mov ax,EchoData_Sel ;置显示过程数据段
mov fs,ax
mov ax,DemoData_Sel ;置演示程序数据段
mov gs,ax
cmp Count,0
jnz TI2 ;计数非0表示未到1秒
mov Count,18 ;每秒约18次
int 0feh ;调用0FEH号中断处理程序显示
cmp BYTE PTR fs:Mess,'0'
jnz TI1
mov BYTE PTR gs:Flag,1 ;显示符号'0'时置标记
TI1: dec BYTE PTR fs:Mess ;调整显示符号
TI2: dec Count ;调整计数
pop gs ;恢复现场
pop fs
pop ds
mov al,EOICOM ;通知中断控制器中断处理结束
out ICREGP,al
pop eax
iretd ;中断返回
TIBegin ENDP
;----------------------------------------------------------------------------
TICodeLen = $
TICodeSeg ENDS
;----------------------------------------------------------------------------
;0FEH号中断处理程序数据段
;----------------------------------------------------------------------------
EchoDataSeg SEGMENT PARA USE16
Mess DB '8',4eh
EchoDataLen = $
EchoDataSeg ENDS
;----------------------------------------------------------------------------
;0FEH号中断处理程序(显示程序)的代码段
;----------------------------------------------------------------------------
EchoCodeSeg SEGMENT PARA USE16
ASSUME CS:EchoCodeSeg,DS:EchoDataSeg
;----------------------------------------------------------------------------
EchoBegin PROC FAR
push ax ;保护现场
push ds
push es
mov ax,EchoData_Sel ;置显示过程数据段
mov ds,ax
mov ax,Video_Sel ;置视频缓冲区数据段
mov es,ax
mov ax,WORD PTR Mess
mov WORD PTR es:[0],ax
pop es
pop ds
pop ax
iretd
EchoBegin ENDP
;----------------------------------------------------------------------------
EchoCodeLen = $
EchoCodeSeg ENDS
;----------------------------------------------------------------------------
;演示任务的堆栈段
;----------------------------------------------------------------------------
DemoStackSeg SEGMENT PARA USE16
DemoStackLen = 1024
DB DemoStackLen DUP(0)
DemoStackSeg ENDS
;----------------------------------------------------------------------------
;演示任务的数据段
;----------------------------------------------------------------------------
DemoDataSeg SEGMENT PARA USE16
Flag DB 0
DemoDataLen = $
DemoDataSeg ENDS
;----------------------------------------------------------------------------
;演示任务的代码段
;----------------------------------------------------------------------------
DemoCodeSeg SEGMENT PARA USE16
ASSUME CS:DemoCodeSeg,DS:DemoDataSeg
;----------------------------------------------------------------------------
DemoBegin PROC FAR
mov ax,DemoStack_Sel ;置堆栈
mov ss,ax
mov sp,DemoStackLen ;置数据段
mov ax,DemoData_Sel
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov al,11111110b ;置中断屏蔽字
out IMREGP,al ;只开发时钟中断
sti ;开中断
DemoConti: cmp BYTE PTR Flag,0 ;判标志
jz DemoConti ;直到不为0
cli ;关中断
;转回临时代码段,准备回实方式
JUMP16 TempCode_Sel,<OFFSET ToDos>
DemoBegin ENDP
;----------------------------------------------------------------------------
DemoCodeLen = $
DemoCodeSeg ENDS
;----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 ;临时任务的代码段
ASSUME CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual PROC FAR
JUMP16 DemoCode_Sel,DemoBegin ;转演示任务
ToDos: 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
IMaskRegV DB ? ;用于保存原中断屏蔽寄存器值
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
mov SSVar,ss ;保存堆栈指针
mov SPVar,sp
sidt QWORD PTR NORVIDTR ;保存IDTR
in al,IMREGP
mov BYTE PTR IMaskRegV,al
lgdt QWORD PTR VGDTR ;装载GDTR
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
mov al,IMaskRegV
out IMREGP,al
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
2.关于实例六的说明
(1)时钟中断仍使用8H号中断向量
为了即简单又清楚地演示在保护模式下响应外部中断并进行处理,实例使用了时钟中断源,
但没有通过重新设置中断控制器的方法改变对应的中断向量。所以,时钟中断使用的8H号中
断向量号就与双重故障异常使用的中断向量号发生冲突。但实例仅是演示程序,所以只要保
证不发生双重故障异常,就可避免冲突,从而不会影响演示。
设置中断屏蔽寄存器,仅开放时钟中断。所以,在开中断状态下,也只可能发生时钟中断,
而不会发生其它外部中断。
(2)时钟中断处理程序的设计
由于通过中断门转时钟中断处理程序,所以在控制转移时不发生任务切换。但外部中断随时
可能发生,因此中断处理程序必须采取保护现场等措施。作为演示程序,该中断处理程序检
查和调整在其数据段中的计数器,满18次后,就认为已满一秒,再调整用于显示的倒计数信
息。如果倒计数信息为0,那么就设置演示程序数据段中的时间为0标志。该中断处理程序通
过约定的数据区与显示程序及演示程序交换信息。
(3)利用一个软中断(陷阱处理)程序实现显示
为了演示陷阱及其处理,把显示过程安排成陷阱处理程序。上述时钟中断处理程序通过软中
断调用指令INT调用该显示程序,以显示倒计数。在控制转移时,也没有任务切换。该陷阱
处理程序相当于一个“软中断”处理程序,类似实模式下的BIOS中断INT 10H。
(4)对其它中断或异常的响应
为了简单,除了8H号和0FEH号外,IDT中其它的门均通向一个处理程序。该处理程序用于处
理其它中断或异常。处理过程也极其简单,在屏幕左上角显示蓝底白字的符号“!”,然后
进入无限循环。实际上,按演示程序现在的安排,不可能发生这种情况。
(5)没有特权级变换
为了简单,实例涉及的中断处理程序和异常处理程序都保持特权级0。所以,控制转移时不
发生特权级变换。因此,没有使用其它堆栈。
(6)对IDT的初始化
由于IDT中门描述符没有32位段基地址,并且入口点偏移较小,所以就直接填写门描述符结
构变量,没有额外再初始化。过程InitIDT只是设置IDT伪描述符。
(7)装载和保存IDTR寄存器
再使IDT发挥作用之前,还要装载中断描述符表寄存器IDTR;但为了回到实模式后,恢复原
来的IDTR之内容,所以先保存IDTR的内容。实例使用如下指令保存IDTR:
sidt QWORD PTR NORVIDTR
该指令的功能是把IDTR的内容保存到存储器中的伪描述符NORVIDTR中。该伪描述符的结构如
前文所述的结构类型PDESC所示,低字是以字节为单位的界限,高双字是基地址。在后面的文
章中将对SIDT指令作详细说明。
本实例使用如下指令装载IDTR寄存器:
lidt QWORD PTR VIDTR
lidt QWORD PTR NORVIDTR
LIDT指令类似于LGDT指令,在后面的文章中将对LIDT指令作详细说明。