第十七章
17.1 int 9中断例程对键盘输入的处理
一般的键盘输入,在CPU执行完int 9中断例程后,都放到了键盘缓冲区中。键盘缓冲区共有16个字单元。可存储15个按键的扫描码和对应的ASCII码
那么它们是如何写入的:
初始状态下,没有键盘输入,缓冲区为空(缓冲区是用环形队列结构管理的内存区)
按下A,引发键盘中断,CPU执行int 9中断例程,从60h端口读出A键的通码,然后检测状态字节,看是否有按下Shift、Ctrl等键,若没有,则将A的扫描码1eh和“a”的ASCII码61h写入缓冲区,字单元中,高位字节存储扫描码,低位字节存储ASCII码
按下B、C、D、E键,同理
Shift_A:按下做Shift,引发键盘中断,int 9中断例程接收左Shift键的通码,设置0040:17处的状态字节的第1位为1,表示左Shift键按下
按下A,引发键盘中断,……,发现左Shift键被按下,则将A的扫描码和Shift_A对应的ASCII码,即“A”的ASCII码41h写入缓冲区
松开左Shift,引发键盘中断,int 9中断例程接收左Shift的断码,设置0040:17处的状态字节第1位为0,表示左Shift松开
按下A键……
17.2 使用int 16h中断例程读取键盘缓冲区
BIOS 提供int 16h中断例程供程序员调用,int 16h中断例程中包含的一个重要的功能是从键盘缓冲区中读取一个键盘输入,该功能编号为0
; 从键盘缓冲区中读取一个键盘输入
; 并将其从缓冲区中删除
mov ah,0
int 16h
; 结果 (ah)=扫描码 (al)=ASCII码
那么int 16h是如何读取的,接着上一节
执行
mov ah,0
int 16h
ah中的内容为1Eh,al中的内容为61h
执行
mov ah,0
int 16h
执行多次以后
此时再执行
mov ah,0
int 16h
int 16h中断例程检测键盘缓冲区,发现缓冲区空,则循环等待,直到缓冲区中有数据
按下A键后
循环等待的中断例程检测到缓冲区中有数据,将其读出
可见BIOS的int 9和int 16是一对互相配合的程序,但是写入和读出的时机不同一个是有键按下时执行,一个是应用程序对其进行调用时执行
在编写处理键盘输入的程序的时候,可以调用int 16h从键盘缓冲区中读取键盘的输入
编程,接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色,“g”绿色,“b”蓝色
assume cs:code
code segment
start: mov ah,0
int 16h
mov ah,1 ; 1表示蓝色
cmp al,'r'
je red
cmp al,'g'
je green
cmp al,'b'
je blue
jmp short sret
red: shl ah,1 ; 4表示红色
green: shl ah,1 ; 2表示绿色
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
在int 16h中断例程中,一定有设置IF=1的指令,因为当键盘缓冲区为空时,如果设置IF=0,int 9中断无法执行,循环等待会死锁
17.3 字符串的输入
编写一个接收字符串输入的子程序,实现3个基本功能:输入的同时显示这个字符串,在输入回车后输入结束,删除已经输入的字符
参数:(dh)、(dl)=字符串在屏幕上显示的行、列位置,ds:si指向字符串的存储空间
因为删除是从尾开始删,即后进先出,所以字符串的存储空间实际是一个字符栈
输入回车后,在字符串中加0,表示结束
每次有输入或删除时,都应该从栈底到栈顶的字符都显示一遍
这些功能可以编写为子程序
(ah)=功能号 0表示入栈,1表示出栈,2表示显示,(al)=入栈/出栈的字符
getstr: push ax
getstrs: mov ah,0
int 16h
cmp al,20h
jb nochar ; ASCII码小于20h说明不是字符
mov ah,0
call charstack ; 字符入栈
mov ah,2
call charstack ; 显示栈中的字符
jmp getstrs
nochar: cmp ah,0eh ; 退格键的扫描码
je backspace
cmp ah,1ch ; Enter键的扫描码
je enter
jmp getstrs
backspace: mov ah,1
call charstack ; 字符出栈
mov ah,2
call charstack ; 显示栈中的字符
jmp getstrs
enter: mov al,0
mov ah,0 ; 把0入栈
call charstack
mov ah,2
call charstack ; 显示栈中的字符
pop ax
ret
charstack: jmp short charstart
table dw charpush,charpop,charshow
top dw 0 ; 栈顶
charstart: push bx
push dx
push di
push es
cmp ah,2
ja sret ; 如果ah>2 无效功能号,返回
mov bl,ah
mov bh,0
add bx,bx
jmp word ptr table[bx] ;通过表跳转到相应的子程序
charpush: mov bx,top
mov [si][bx],al
inc top
jmp sret
charpop: cmp top,0 ; 判断当前栈是否为空
je sret
dec top
mov bx,top
mov al,[si][bx]
jmp sret
charshow: mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh ; (dh)、(dl)分别表示显示的行、列
mov di,ax
add dl,dl
mov dh,0
add di,dx ; di表示要显示的偏移地址
mov bx,0 ; 从栈底开始显示
charshows: cmp bx,top ; 判断字符串是否已经全部显示
jne noempty
mov byte ptr es:[di],' '
jmp sret
noempty: mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' '
inc bx
add di,2
jmp charshows
sret: pop es
pop di
pop dx
pop bx
ret
17.4 应用int 13h中断例程对磁盘进行读写
以3.5英寸软盘为例,分为上下面,每面80个磁道,每个磁道18个扇区,每个扇区为512字节
磁盘的访问由磁盘控制器进行,以扇区为单位进行磁盘读写,读写扇区时要给出面号、磁道号和扇区号,面号、磁道号从0开始,扇区号从1开始
我们可以通过调用BIOS提供的中断例程(int 13h)来访问磁盘
; 读取0面0道1扇区的内容到0:200
; es:bx指向接收从扇区读入数据的内存区
mov ax,0
mov es,ax
mov bx,200h
; (ah)=int 13h的功能号(3表示写扇区)
; (al)=写入的扇区数
; (ch)=磁道号
; (cl)=扇区号
; (dh)=磁头号(面号)
; (dl)=驱动器号
; 软驱从0开始 0:软驱A 1:软驱B
; 硬盘从80h开始 80h:硬盘C 81h:硬盘D
mov al,1
mov ch,0
mov cl,1
mod dl,0
mov dh,0
mov ah,2
int 13h
; 返回参数:
; 操作成功:(ah)=0 (al)=写入的扇区数
; 操作失败:(ah)=出错的代码
但是直接向磁盘扇区写数据是很危险的,必须找一张空闲的软盘,同时注意驱动器号是否正确
编程:将当前屏幕的内容保存到磁盘中
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov bx,0
mov al,8 ; 一屏4000字节,需要8个扇区
mov ch,0
mov cl,1
mov dl,0
mov dh,0
mov ah,3
int 13h