第十五章
在计算机系统中,CPU除了能执行指令,进行运算外,还能对外部设备进行控制,接收它们的输入,向它们进行输出,即I/O能力,比如在文本编辑器中,我们按键盘中的一个键,可以看到屏幕上出现该字母,是CPU将从键盘上输入的键对应的字符送到显示器上
及时处理外设的输入,有两个问题,即外设的输入随时可能发生,CPU如何得知,以及CPU从何处得到外设的输入
以键盘为例进行讨论
15.1 接口芯片和端口
外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中,CPU向外设的输出也是先送入端口中,再由相关的芯片送到外设,CPU还可以向外设输出控制命令,控制命令也是先送到相关芯片的端口中
CPU通过端口和外部设备进行练习册
15.2 外中断信息
由于外设随时都可能发生需要CPU及时处理的事件,因此CPU提供中断机制,比如外设的输入到达,相关芯片将向CPU发出相应的中断信息吗,CPU在执行完当前指令后,检测到中断信息,引发中断过程,处理外设的输入
PC系统中,外中断源有两类
1、可屏蔽中断
CPU可以不响应,是否响应取决于标志寄存器IF位的设置,当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,IF=0则可以屏蔽
可屏蔽中断信息来自于CPU外部,中断类型码通过数据总线送入CPU,而内中断的中断类型码是在CPU内部产生的,可屏蔽中断引发的中断过程除了中断类型码来源不同,其他步骤基本和内中断相同
因此中断过程中将IF设置为0是为了在进入中断处理程序后,禁止其他的可屏蔽中断
如果中断处理程序中需要处理可屏蔽程序,将IF置1
sti ; IF=1
cli ; IF=0
2、不可屏蔽中断
是CPU必须响应的中断,CPU检测到不可屏蔽中断信息时,执行完当前指令后立即响应
8086CPU不可屏蔽中断的中断类型码固定为2,所以中断过程不需要中断类型码
并且最后设置(IP)=(8) (CS)=(0AH)
几乎所有外设引发的外中断都是可屏蔽的(如键盘输入)
不可屏蔽中断是在系统中有必须处理的紧急情况时通知CPU,我们主要讨论可屏蔽中断
15.3 PC机键盘的处理过程
1、键盘输入
键盘上每个键相当于一个开关,键盘中有一个芯片对每个键的开关状态进行扫描
按下一个键时,开关接通,芯片产生一个扫描码,扫描码说明了按下的键在键盘上的位置,扫描码被送入主板上相关接口芯片的寄存器中,该寄存器的端口地址为60h
松开按下的键,也产生一个扫描码,说明松开键的位置,该扫描码也被送到60h端口中
按下键时产生的扫描码成为通码,按下时产生的称为断码,扫描码长度为一个字节
断码=通码+80h(通码第7位为0,断码第7位为1)
如:g的通码为22h,断码为a2h
2、引发9号中断
键盘的输入到达60h端口时,相关芯片就会向CPU发出中断类型码为9的可屏蔽中断信息,CPU根据IF的值决定是否响应中断
3、执行int 9中断例程
BIOS提供int 9中断例程,进行基本的键盘输入处理
执行过程:首先读出60h端口中的扫描码,如果时字符键的扫描码,将该扫描码和它对应的ASCII码送入内存中的BIOS键盘缓冲区;如果是控制键(如Ctrl)和切换键(CapsLock)的扫描码,将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元,最后对键盘系统进行相关的控制
BIOS键盘缓冲区是系统启动后BIOS用于存放int 9中断例程所接收的键盘输入的内存区
该内存区可以存储15个键盘输入,一个键盘输入占一个字单元,高位存放扫描码,低位存放字符码
0040:17这个内存单元存储键盘状态字节,该字节记录了控制键和切换键的状态,具体各个位记录的信息如下
15.4 编写int9中断例程
编程:在屏幕中间依次显示“a”~“z”,并可以让人看得清在显示过程中,按下Esc键后,改变显示的颜色
如果用普通的循环向一个内存单元依次传送一个字母,由于CPU执行速度很快,所以字母切换太快,无法看清
所以每显示一个字母,让CPU执行一段时间的空循环,用两个16位寄存器存放32位的循环次数
现在需要改变显示的颜色,我们编写的int 9中断例程功能:
首先从60端口读键盘的输出
调用BIOS的int 9中断例程处理其他硬件细节
判断是否为Esc的扫描码
有一个问题在于新的int 9中断例程会改变中断向量表中相应的入口地址,那么我们想调用原来的int 9中断例程就会有麻烦,因此在更改中断向量表中的入口地址前,先保存原有的地址,然后对int指令进行模拟,从而实现对中断例程的调用
int n指令执行时,中断类型码n的作用就是从中断向量表中获取中断例程的入口地址,而这里我们已经提前保存了入口地址,因此只需要标志寄存器入栈,设置IF=0、TF=0,随后CS、IP入栈,最后修改CS、IP的值
我们发现call dword ptr ds:[0]即可完成后两步
最后一个问题是,在程序返回前,需要将中断向量表中的int 9中断例程的入口地址恢复为原来的地址,否则程序返回后,别的程序将无法使用键盘
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] ; 将原来的int 9中断例程的入口地址
pop ds:[0] ; 保存到ds:0 ds:2单元中
push es:[9*4+2]
pop ds:[2]
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs ; 设置新的int 9中断例程的入口地址
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,0
mov es,ax
push ds:[0] ; 该程序不安装
push ds:[2] ; 只是临时更改int 9中断例程的入口
pop es:[9*4] ; 在显示字母的过程中只要按下Esc就可以变色
pop es:[9*4+2] ; 将int 9中断例程的入口恢复为原来的地址
mov ax,4c00h
int 21h
delay: push ax
push dx
mov dx,1000h ; 循环10000000h次
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0 ; 只有ax dx都不等于0
jne s1 ; 循环才结束
pop dx
pop ax
ret
; -------------以下为新的int 9中断例程------------
int9: push ax
push bx
push es
in al,60h ; 从端口取出扫描码
; 调用系统提供的int 9中断例程
pushf ; 标志寄存器入栈
pushf
pop bx
and bh,11111100b ; 设置IF=0 TF=0
push bx
popf
call dword ptr ds:[0] ;相当于CS IP入栈并修改CS IP的值
cmp al,1 ; 判断是否为Esc键
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
在模拟int9中断例程时,我门可以精简程序,因为在进入我们写的中断例程中时,IF呵TF都已经置为0,因此在进入BIOS的int 9中断例程时,没必要再设置一次IF和TF
pushf ; 标志寄存器入栈
pushf
pop bx
and bh,11111100b ; 设置IF=0 TF=0
push bx
popf
call dword ptr ds:[0] ;相当于CS IP入栈并修改CS IP的值
; 精简为
pushf ; 标志寄存器入栈
call dword ptr ds:[0] ;相当于CS IP入栈并修改CS IP的值
还有一个问题,如果在主程序重新设置的int 9中断例程的段地址和偏移地址的指令之间发生了键盘中断,则CPU将转去一个错误的地址执行(CS寄存器中的内容可能会改变)
cli ; 设置IF为0.不响应可屏蔽中断
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs ; 设置新的int 9中断例程的入口地址
sti
15.5 安装新的int 9中断例程
安装一个新的int 9中断例程,在DOS下,按F1键后改变当前屏幕的颜色
assume cs:code
stack segment
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
push cs
pop ds
mov ax,0
mov es,ax
; 安装
mov si,offset int9
mov di,204h
mov cx,offset int9end-offset int9
cls
rep movsb
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[20h]
cli
mov word ptr es:[9*4],204h
moc word ptr es:[9*4+2],0
mov ax,4c00h
int 21h
int9: push ax
push bx
push cx
push es
in al,60h
pushf
call dowrd ptr cs:[200h]
cmp al,3bh ; F1扫描码为3bh
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s: inc byte ptr es:[bx]
add bx,2
loop s
int9ret: pop es
pop cx
pop ax
pop ax
iret
int9end: nop
code ends
end start
端口和中断机制是CPU进行I/O的基础