第17章 本书最后将学习什么呢(需要回头学习)

17.1 int 9中断例程对键盘输入的处理

大多数有用的程序都需要处理用户的输入,键盘输入是最基本的输入。

程序和数据通常需要长期存储,磁盘是最常用的存储设备。

BIOS 为这两种外设的I/O提供了最基本的中断例程,在本章中,我们对它们的应用和相关的问题进行讨论。

我们已经讲过,键盘输入将引发9 号中断,BIOS 提供了int 9 中断例程。

CPU 在9 号中断发生后,执行int 9中断例程,从60h 端口读出扫描码,并将其转化为相应的ASCII 码或状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中。

所以,一般的键盘输入,在CPU 执行完int 9 中断例程后,都放到了键盘缓冲区中。
键盘缓冲区中有16 个字单元,可以存储15个按键的扫描码和对应的入ASCII 码。

下面我们按照键盘缓冲区的逻辑结构,来看一下键盘输入的扫描码和对应 ASCII 码是如何写入键盘缓冲区的。

下面,我们通过下面几个键:
A、B、C、D、E、shift_A、A


17.2 使用int 16h中断例程读取键盘缓冲区

BIOS提供了int 16h 中断例程供程序员调用。
int 16h 中断例程中包含的一个最重要的功能是从键盘缓冲区中读取一个键盘输入,该功能的编号为0。
下面的指令从键盘缓冲区中读取一个键盘输入,并且将其从缓冲区中删除:
mov ah,0 int 16h
结果:(ah)=扫描码,(al)=ASCII码。

下面,我们接着上一节中的键盘输入过程,看一下 int 16h 如何读取键盘缓冲区。
从上面我们可以看出,int 16h 中断例程的 0 号功能,进行如下的工作:

(1)检测键盘缓冲区中是否有数据;
(2)没有则继续做第1 步;
(3)读取缓冲区第一个字单元中的键盘输入;
(4)将读取的扫描码送入ah,ASCII 码送入al;
(5)将己读取的键盘输入从缓冲区中删除。

可见,B1OS 的int 9 中断例程和int 16h 中断例程是一对相互配合的程序,int 9 中断例程向键盘缓冲区中写入,int 16h 中断例程从缓冲区中读出。

它们写入和读出的时机不同,int 9 中断例程在有键按下的时候向键盘缓冲区中写入数据;

而int 16h 中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。

我们在编写一般的处理键盘输入的程序的时候,可以调用int 16h 从键盘缓冲区中读取键盘的输入。

编程,接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色:输入“g”, 将屏幕上的字符设置为绿色;输入“b ”,将屏幕上的字符设置为蓝色。

;编程:
;接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色:输入“g”,
;将屏幕上的字符设置为绿色;输入“b ”,将屏幕上的字符设置为蓝色。

;A、B、C处的程序指令比较有技巧,请读者自行分析

assume cs:code

code segment
start:
mov ah,0
int 16h

mov ah,1			;A
cmp al,'r'
je red
cmp al,'g'
je green
cmp al,'b'
je blue
jmp short sret

red:
shl ah,1 ;B

green:
shl ah,1 ;C

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

17.3 字符串的输入

用户通过键盘输入的通常不仅仅是单个字符而是字符串。下面我们讨论字符串输入中的问题和简单的解决方法。
最基本的字符串输入程序,需要具备下面的功能:
(1) 在输入的同时需要显示这个字符串;
(2)一般在输入回车符后,字符串输入结束;
(3)能够删除已经输入的字符。

对于这三个功能,我们可以想象在DOS 中,输入命令行时的情况。

编写一个接收字符串的输入子程序,实现上面三个基本功能。因为在输入的过程中需要显示,子程序的参数如下:
(dh)、(dl)=字符串在屏幕上显示的行、列位置;
ds:si 指向字符串的存储空间,字符串以0为结尾符。

下面我们将进行仔细分析!
(1)字符的输入和删除。 每个新输入的字符都存储在前一个输入的字符之后,而删除是从最后面的字符进行的。
我们看下面的过程:
空字符串:
输入“a”:a
输入“b”:ab
输入“c”:abc
输入“d”:abcd

删除一个字符:abc
删除一个字符:ab
删除一个字符:a
删除一个字符:

可以看出在字符串输入的过程中,字符的输入和输出是按照栈的访问规则进行的,即后进先出。

这样,我们就可以用栈的方式来管理字符串的存储空间,也就是说,字符串的存储空间实际上是一个字符栈。

字符栈中的所有字符,从栈底到栈顶,组成一个字符串。

(2)在输入回车符后,字符串输入结束。
输入回车符后 ,我们可以在字符串中加入0,表示字符串结束。
(3) 在输入的同时需要显示这个字符串。
每次有新的字符输入和删除一个字符的时候,都应该重新显示字符串,即从字符栈的栈底到栈顶,显示所有的字符。

(4)程序的处理过程。现在我们可以简单地确定程序的处理过程如下:
① 调用int 16h读取键盘输入;
② 如果是字符,进入字符栈,显示字符栈中的所有字符;继续执行① ;
③ 如果是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行① ;
④ 如果是Enter 键,向字符栈中压入0,返回。

从程序的处理过程中可以看出,字符栈的入栈、出栈和显示栈中的内容,是需要在多处使用的功能,我们应该将它们写为子程序。

子程序:字符栈的入栈、出栈和显示。
参数说明:
(ah)=功能号,0表示入栈,1表示出栈,2表示显示;
ds : si 指向字符栈空间;
对于0 号功能:(al)=入栈字符;
对于1 号功能:(al)=返回的字符;
对于2 号功能:(dh)、(dl) =字符串在屏幕上显示的行、列位置。

;编程:
;接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色:输入“g”,
;将屏幕上的字符设置为绿色;输入“b ”,将屏幕上的字符设置为蓝色。

;A、B、C处的程序指令比较有技巧,请读者自行分析

assume cs:code

code segment
start:
mov ah,0
int 16h ;int 16h 0号功能实现从键盘缓冲区读取一个键盘输入

mov ah,1			;A
cmp al,'r'
je red
cmp al,'g'
je green
cmp al,'b'
je blue
jmp short sret

red:
shl ah,1 ;B
green:
shl ah,1 ;C

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
另外一个要注意的问题是:
显示栈中字符的时候,要注意清除屏幕上上一次显示的内容。

我们现在写出完整的接收字符串输入的子程序。

;最基本的字符串输入程序,需要具备下面的功能:
;(1) 在输入的同时需要显示这个字符串;
;(2)一般在输入回车符后,字符串输入结束;
;(3)能够删除已经输入的字符。

;编写一个接收字符串的输入子程序,实现上面三个基本功能。
;因为在输入的过程中需要显示,子程序的参数如下:
; (dh)、(dl)=字符串在屏幕上显示的行、列位置;
; ds:si 指向字符串的存储空间,字符串以O 为结尾符。

assume cs:code

code segment
start:
call getstr

return:
mov ax,4c00h
int 21h

;完整的接收字符串输入的子程序

getstr:
push ax

getstrs:
mov ah,0
int 16h

cmp al,20h
jb nochar   		;判断的是ASCII码小于0,说明不是字符
mov ah,0;
call charstack		;字符入栈
mov ah,2
call charstack		;显示栈中的字符
jmp getstrs

nochar:
cmp ah,0eh ;退格键的扫描码
je backspace
cmp ah,1ch ;回车键的扫描码
je enter
jmp getstrs

backspace: ;退格
mov ah,1
call charstack ;字符出栈
mov ah,2
call charstack ;显示栈中的字符
jmp getstrs

enter: ;回车
mov al,0
mov ah,0
call charstack ;0入栈
mov ah,2
call charstack ;显示栈中的字符

pop ax
ret ;getstr ends

;功能子程序实现

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
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
mov di,ax
add dl,dl
mov dh,0
add di,dx

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

code ends

end start

17.4 应用int13h中断例程对磁盘进行读写

这本书毕竟是经典,经典的东西嘛……就是古老的东西,所以这里主要以 3.5 英寸软盘为例,进行讲解,但原理依旧不变。

常用的3.5英寸软盘的结构:
分为上下两面,每面有80个磁道,每个磁道又分为18个扇区,每个扇区的大小为512B。
总容量为:2面×80磁道×18扇区×512B=1440KB≈1.44MB

磁盘的实际访问由磁盘控制器进行,我们可以通过控制磁盘控制器来访问磁盘。

注意,我们只能以扇区为单位对磁盘进行读写。
在读写扇区的时候,要给出面号、磁道号和扇区号。面号和磁道号从0开始,而扇区号从1开始。

如果我们通过直接控制磁盘控制器来访问磁盘,则需要涉及许多硬件细节。

BIOS提供了对扇区进行读写的中断例程,这些中断例程完成了许多复杂的和硬件相关的工作。

我们可以通过调用BIOS中断例程来访问磁盘。

BIOS 提供的访问磁盘的中断例程为int 13h 。

如下,读取0面0道1扇区的内容到0:200:

(ah)=int 13h的功能号(2表示读扇区)
(al)=读取的扇区数
(ch)=磁道号
(cl)=扇区号
(dh)=磁头号(对于软驱即面号,因为一个面用一个磁头来读写)
(dl)=驱动器号
软驱从0开始,0:软驱A,1:软驱B;
硬盘从80h开始,80h:硬盘C,81h:硬盘D。
es:bx指向接收此扇区读入数据的内存区

mov ax,0
mov es,ax
mov bx,200h
mov al,1
mov ch,0
mov cl,1
mov dl,0
mov dh,0
mov ah,2
int 13h

返回参数:
操作成功: (ah)=0,(al)=读入的扇区数
操作失败: (ah)=出错代码

接下来,我们看下将0:200中的内容写入0面0道1扇区示例!

(ah)=int 13h的功能号(3表示写扇区)
(al)=写入的扇区数
(ch)=磁道号
(cl)=扇区号
(dh)=磁头号(面)
(dl)=驱动器号
软驱从0开始,0:软驱A,1:软驱B;
硬盘从80h开始,80h:硬盘C,81h:硬盘D。
es:bx指向将写入磁盘的数据

mov ax,0
mov es,ax
mov bx,200h
mov al,1
mov ch,0
mov cl,1
mov dl,0
mov dh,0
mov ah,3
int 13h

返回参数:
操作成功: (ah)=0,(al)=写入的扇区数
操作失败: (ah)=出错代码

注意:
下面我们要使用int 13h 中断例程对软盘进行读写。直接向磁盘扇区写入数据是很危险的,很可能覆盖掉重要的数据。
如果向软盘的0 面0 道1 扇区中写入了数据,要使软盘在现有的操作系统下可以使用,必须要重新格式化。

在编写相关的程序之前,必须要找一张空闲的软盘。在使用int 13h中断例程时一定要注意驱动器号是否正确,千万不要随便对硬盘中的扇区进行写入。
编程练习:将当前屏幕的内容保存在磁盘上。
分析:1 屏的内容占4000个字节,需要8 个扇区,我们用0面0道的1~8扇区存储显存中的内容。

assume cs:code

code segment

start:
mov ax,0b800h
mov es,ax
mov bx,0 ;es:bx 指向将写入磁盘的数据的内存区

mov al,8 	;写入的扇区数
mov ch,0 	;磁道号,从0开始
mov cl,1 	;扇区号 从1开始
mov dl,0 	;驱动器号0:软驱A,  1:软驱B,硬盘从80h开始, 80h:硬盘C,81h:硬盘D
mov dh,0 	;磁头号,(对于软盘即面号,因为一个面用一个磁头来读写)
mov ah,3	;传递 int 13h 写入数据的功能号
int 13h

		;返回参数
		;操作成功:(ah) = 0,(al) = 写入的扇区数
		;操作失败:(ah) = 出错代码

return:
mov ax,4c00h
int 21h

code ends
end start

posted on 2015-08-15 00:22  木屐  阅读(235)  评论(0编辑  收藏  举报

导航