CPU不论处理内中断还是外中断都是通过中断类型码来选择要执行的中断例程。外中断可分为可屏蔽和不可屏蔽,当IF=0时,会屏蔽掉可屏蔽中断。
以键盘输入为例。当按下一个键时,就会产生一个扫描码,并传到端口60h,此时相关芯片会向CPU发送中断类型码为9的可屏蔽中断。
端口的读写指令是 in/out。这里不做具体展开,假设要读取端口60h的数据,可用 in al,60h。
假设现在要在int 9中断例程的基础上加点东西,比如希望按下esc键后改变屏幕输出。
可行的思路是,修改中断向量表中9号中断例程的入口地址为子程序的地址,
当IF = 1且有键盘输入时,进入到子程序,子程序要先执行原先的9号中断例程(先pushf,再call)。因此要先将原先的9号中断例程的地址记录下来。注意每个子程序/中断例程内部结束处都会有ret/iret,所以这里执行了pushf和call,中断例程内部结束处会有iret来执行ret和popf。
执行完中断例程后再执行添加的指令。完整程序如下
assume cs:code
stack segment
db 128 dup(0) stack ends data segment dw 0,0 data ends code segment start: mov ax,stack mov ss,ax mov sp,128 mov ax,data mov ds,ax mov ax,0 mov es,ax push es:[9*4] pop ds:[0] push es:[9*4+2] pop ds:[2] pushf pop ax mov word ptr es:[9*4],offset int9 mov es:[9*4+2],cs mov ax,0b800h mov es,ax mov ah,'a' s: mov es:[160*12+40*2],ah call delay inc ah cmp ah,'z' jna s mov ax,4c00h int 21h delay: push ax push dx mov dx,10 mov ax,0 s1: sub ax,1 sbb dx,0 cmp ax,0 jne s1 cmp dx,0 jne s1 pop dx pop ax ret int9: push ax push bx push es in al,60h pushf call dword ptr ds:[0] cmp al,1 jne int9ret mov ax,0b800h mov es,ax inc byte ptr es:[160*12+40*2+1] int9ret: pop es pop bx pop ax iret code ends end start
int 9h中断例程是在有键按下的时候向键盘缓冲区写入数据,这里的键盘缓冲区是双向队列,可存放15个键盘输入,每个键盘输入包含扫描码和ASCII码(这里会检查状态字节,结合原字符得到ASCII码)。对应的,如果要从键盘缓冲区读取数据,可用int 16h,其功能号为0(有的中断例程需要功能号或一些其他的参数),指令为
mov ah,0
int 16h
通过该语句,ax被赋值,其中(ah) = 扫描码,(al) = ASCII码。
假设现在想通过按下r/g/b键令显示区的字符全为红/绿/蓝,
assume cs:code code segment start: mov ah,0 int 16h mov ah,1 cmp al,'r' je red cmp al,'g' je green cmp al,'b' je blue jmp short sret red: shl ah,1 green: shl ah,1 blue: mov bx,0b800h mov es,bx mov bx,1 mov cx,2000 s: and byte ptr es:[bx],11111000b or es:[bx],ah add bx,2 loop s sret: mov ax,4c00h int 21h code ends end start