第15章 外中断


    CPU在计算机系统中,除了能够执行指令, 进行运算以外,还应该能够对外部设备进行控制,接收它们的输入,向它们进行输出。也就是说CPU除了有运算能力外,还要有I/O能力。

要及时处理外设的输入,显然需要解决两个问题:
(1) 外设的输入随时可能发生,CPU如何得知?
(2)CPU从何处得到外设的输入?


15.1 接口芯片和端口

CPU通过端口和外部设备进行联系:
(1)外设的输入不直接送入内存和CPU,而是 送入相关的接口芯片的端口中
(2)CPU向外设的输出也不是直接送入外设,而是 先送入端口中,再由相关的芯片送到外设
(3) CPU还可以向外设输出控制命令,也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。



15.2 外中断信息

在PC系统中,外中断源一共有以下两类: 可屏蔽中断和不可屏蔽中断
(1)可屏蔽中断是CPU可以不响应的外中断。 CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置
(2)不可屏蔽中断是CPU必须响应的外中断。 不受IF位影响。当CPU检测到不可屏蔽中断信息时,在执行完当前指令后,立即响应。

    可屏蔽中断所引发的中断过程,除在第1步上的实现有所不同外,基本上和内中断的中断过程相同。因为可屏蔽中断信息来自于CPU外部中断类型码是通过数据线送入CPU的;而内中断的中断类型码是在CPU内部产生的。

8086CPU提供的设置IF的指令如下:
(1)sti,设置IF=1
(2)cli,设置IF=0

    对于8086CPU,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码。

    几乎所有由外设引发的外中断,都是可屏蔽中断。当外设有需要处理的事件发生时,相关芯片向CPU发出可屏蔽中断信息。不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知CPU的中断信息。


    我需要修正自己的一个观点,以前在看《计算机组成原理》这本书中介绍I/O和中断的章节时,形成了一个观点,“外设发出中断请求”,看了这章之后,感觉更准确的说法是,“与外设对应的接口芯片,发出中断请求”
    记录一个模糊的观点,留着以后完善:“输入和输出的不同,输出是CPU通过指令执行主动发出的,输入是CPU被动的检测有没有外设的中断信息,比如下一小节介绍的键盘输入”


15.3 PC机键盘的处理过程

键盘输入:
(1) 按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。 扫描码被送入主板上相关接口芯片的寄存器中,该寄存器的端口地址为60h
(2)松开按下的键时,也产生一个扫描码。 松开按键时产生的扫描码也被送入60h端口中。
(3)一般按下一个键时产生的扫描码称为 通码,松开一个键产生的扫描码为 断码断码=通码+80h
BIOS提供了int 9中断例程,用来进行基本的键盘输入处理,主要工作如下:
(1)读出60h端口中的扫描码
(2)如果是字符键的扫描码, 将该扫描码和它对应的字符码送入内存中的BIOS键盘缓冲区;如果是控制键和切换键的扫描码,则 将其转变为状态字节写入内存中存储状态字节的单元
(3)对键盘系统进行相关控制。比如说,向相关芯片发出应答信息。

    BIOS键盘缓冲区 是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区。该内存区可以存储15个 键盘输入,因为int 9 中断例程除了接收扫描码外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码

0040:17单元 存储键盘状态字节,该字节记录了控制键和切换键的状态。键盘状态字节记录的各位信息如下:
0:右Shift状态,置1表示按下右shift键
1:左Shift状态,置1表示按下左shift键
2:Ctrl状态,置1表示按下Ctrl键
3:Alt状态,置1表示按下Alt键
4:ScrollLock状态,置1表示Scroll指示灯亮
5:NumLock状态,置1表示小键盘输入的是数字
6:CapsLock状态,置1表示输入大写字母
7:Insert状态,置1表示处于删除态

    该小节键盘输入的细节,对我有些启发。


15.4 编写int 9中断例程


    我一般都是看看例题的需求,不看书中源码,自己尝试写一下,写完之后再对比。本小节中用其它指令模拟int 9指令的中断过程要着重看一下理解透,还有后面的检测点,搞清楚为什么可以简化前面的模拟过程

水平有限,个人代码仅供参考:(书中有源码,更简洁,我的沿用了前面的方法安装到0000:0200处)

assume cs:code,ss:stack,ds:data

data segment
	dw 2 dup (0)
data ends
stack segment
	dw 16 dup(0)
stack ends

code segment
;把int 9中断例程的入口地址保存到中断例程oldInter处
start:
			mov ax,0
			mov ds,ax
			mov si,9*4			;ds:si指向原int 9中断例程的入口地址在向量表中的地址
			mov ax,cs
			mov es,ax
			mov di,offset oldInter		;es:di指向原int 9中断例程的入口地址要保存的位置
			cld
			mov cx,2
			rep movsw			;将原int 9中断例程备份到新位置

;将自己写的中断例程安装到0000:0200处
			mov ax,cs
			mov ds,ax
			mov si,offset newInter		;ds:di指向要安装的中断例程内存中的地址

			mov ax,0000h
			mov es,ax
			mov di,0200h			;es:si指向0000:0200处

			cld
			mov cx,offset newInterE-offset newInter
			rep movsb			;通过串传送指令进行安装

;修改中断类型码9在中断向量表对应的入口地址
			cli				;防止修改int 9中断例程入口地址时,被中断导致错误
			mov ax,0
			mov es,ax
			mov word ptr es:[9*4],0200h
			mov word ptr es:[9*4+2],0
			sti
;主程序,实现自动显示a~z,点击esc键变色
			mov ax,stack			;初始化栈段
			mov ss,ax
			mov sp,32

			mov ax,0b800h
			mov es,ax
			mov bp,160*12+40*2		;es:bp指向显存中显示字符的位置

			mov ah,'a'
c:			mov es:[bp],ah
			call delay			;调用延迟子程序,方便我们看清
			inc ah
			cmp ah,'z'
			jna c

			mov ax,cs
			mov ds,ax
			mov si,offset oldInter
			mov ax,0
			mov es,ax
			mov di,9*4
			mov cx,2
			cld
			rep movsw

			mov ax,4c00h
			int 21h
	
;功能:延迟一段时间。通过双字数10000000H减1实现
;参数:无
;返回:无
delay:			push dx
			push ax

			mov dx,10h
			mov ax,0000h
d:			sub ax,1
			sbb dx,0000h
			cmp ax,0
			jne d
			cmp dx,0
			jne d
			
			pop ax
			pop dx
			ret

;中断例程:自己写的一个代替原int 9的中断例程,此中断例程需要通过call指令调用原中断例程
;功能:点击键盘esc键后,字符变色
;参数:无
;返回:无
newInter:		jmp short chaCol
oldInter:		db 4 dup(0)		;保存原int 9中断例程入口地址的地方
chaCol:			push ax
			push es
			push bx
			push ds

			in al,60h				;获得键盘输入的扫描码

			pushf					;调用原来的int 9的中断例程
			mov bx,cs
			mov ds,bx
			call dword ptr ds:[202h]		;这里没搞清楚段地址偏移地址的关系,浪费半天时间,本来昨天就应该顺利验证通过
			
			cmp al,01h
			jne n

			mov ax,0b800h
			mov es,ax
			inc byte ptr es:[160*12+40*2+1]		;es:bx指向显存中字符的属性

n:			pop ds
			pop bx
			pop es
			pop ax
			iret
newInterE:		nop
code ends

end start
我自己写的代码中,比较满意和需要注意是:
(1)对offset这个指令的理解更好了。
(2)注意中断例程中要访问的数据保存的地方,这个地方在安装后中断例程要能访问到
(3)例程安装好后访问属于例程的数据和我写程序时访问数据的细节有些不同。 不同的原因就是段地址的改变,安装的地方和安装程序所在的地方,地址是不同的。

    我在写这个代码时就是没注意例程安装好后的访问细节浪费了半天时间,一度自我怀疑,该不该不看源码自己写、该不该自学编程。


15.5 安装新的int 9中断例程


我自己根据需求写了中断例程,大体结构都差不多,对比书中的中断例程,给我的启发:
(1)我忽略了push和pop指令可以直接对内存单元操作, 我一直误以为push和pop指令后面只能跟寄存器
(2)对自己写的程序的细节要注意,写这个程序时忘记了在修改中断向量表中表项时要关中断。就是 cli和sti两个指令的运用
(3)安装好的中断例程,安装程序结束不在内存中,中断例程还是能够触发的。

水平有限,个人代码仅供参考:(书中有完整代码,我与书中代码的区别,这是在保存原int 9中断例程的入口地址方法不同)

assume cs:code

code segment
;将原int9中断例程的入口地址传送到新int9中断例程中oldAdr处
start:				mov ax,0
				mov ds,ax
				mov si,9*4			;ds:si指向中断向量表中原int9中断例程入口地址

				mov ax,cs
				mov es,ax
				mov di,offset oldAdr		;es:di指向保存原中断例程入口地址处

				cld
				mov cx,4
				rep movsb

;安装int9中断例程到0000:0200处
				mov ax,cs
				mov ds,ax
				mov si,offset int9		;ds:si指向新的int9中断例程

				mov ax,0
				mov es,ax
				mov di,0200h			;es:di指向0000:0200处	

				cld
				mov cx,offset int9E-offset int9
				rep movsb

;更新中断向量表中int9对应的入口地址
				cli
				mov ax,0
				mov ds,ax
				mov word ptr ds:[9*4],0200h
				mov word ptr ds:[9*4+2],0
				sti

				mov ax,4c00h
				int 21h

;扩展后int9对应的中断例程
;功能:点击F1键,屏幕变色,其他键不变
;参数:无
;返回:无
int9:				jmp short i
	oldAdr:			dw 2 dup (0)
	i:			push ax
				push es
				push si
				push cx
				push ds

				in al,60h			;获取键盘输入的扫描码
				cmp al,3bh
				jne oldIt9			;如果按的不是F1键,调用原int9中断例程

				mov ax,0b800h
				mov es,ax
				mov si,1			;es:si指向显存第一个属性地址
				mov cx,2000

		chaScr:		inc byte ptr es:[si]
				add si,2
				loop chaScr

		oldIt9:		mov ax,cs
				mov ds,ax
				pushf
				call dword ptr ds:[202h]	;原int9中断例程的入口地址保存在0000:0202处

				pop ds
				pop cx
				pop si
				pop es
				pop ax
				iret
int9E:				nop
				
code ends

end start



实验 15 安装新的int 9中断例程

水平有限,个人代码仅供参考:

assume cs:code

code segment
;原中断例程的入口地址保存到新中断例程中oldAdr处
start:				mov ax,0
				mov ds,ax
				mov si,9*4					;ds:si指向源地址,即原int 9 中断例程的入口地址
				
				mov ax,cs
				mov es,ax
				mov di,offset oldAdr				;es:di指向目的地址,即新int 9中断例程的oldAdr处

				mov cx,4
				cld
				rep movsb					;串传送

;安装新中断例程到0000:0200处
				mov ax,cs
				mov ds,ax
				mov si,offset int9				;ds:si指向源地址,即新int9中断例程

				mov ax,0
				mov es,ax
				mov di,0200h					;es:di指向目的地址,即0000:0200处

				mov cx,offset int9E-offset int9
				cld
				rep movsb					;串传送指令

;修改中断向量表中原int9对应的入口地址
				cli						;防止int9中断的入口地址没设置号,响应中断导致错误
				mov ax,0
				mov es,ax
				mov word ptr es:[9*4],0200h
				mov word ptr es:[9*4+2],0
				sti

				mov ax,4c00h					;结束程序
				int 21h

;新int9中断例程
int9:				jmp short showA
		oldAdr:		dw 2 dup (0)
showA:				push ax
				push es
				push di
				push cx
				push ds

				in al,60h
				cmp al,9eh					;用A键的断码来判断
				jne oldIt9					;其它情况调用原int 9中断例程

				mov ax,0b800h					;准备输出全屏A
				mov es,ax
				mov di,0
				mov cx,2000

		cycA:		mov byte ptr es:[di],'A'			;循环输出全屏A
				add di,2
				loop cycA
				
		oldIt9:		mov ax,cs
				mov ds,ax
				pushf
				call dword ptr ds:[202h]

				pop ds
				pop cx
				pop di
				pop es
				pop ax
				iret
int9E:				nop

code ends

end start

    参考前面的例子很简单,前面的例子根据键的通码判断,这里的实验改成根据键的断码判断而已

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值