[汇编]《汇编语言》第17章 使用BIOS进行键盘输入和磁盘读写

Posted 若水三千

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[汇编]《汇编语言》第17章 使用BIOS进行键盘输入和磁盘读写相关的知识,希望对你有一定的参考价值。

王爽《汇编语言》第四版 超级笔记

第17章 使用Bios进行键盘输入和磁盘读写

大多数有用的程序都需要处理用户的输入,键盘输入是最基本的输入。程序和数据通常需要长期存储,磁盘是最常用的存储设备。

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

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

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

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

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

注意:在我们的课程中,仅在逻辑结构的基础上,讨论BIOS键盘缓冲区的读写问题。其实键盘缓冲区是用环形队列结构管理的内存区,但我们不对队列和环形队列的实现进行讨论,因为那是另一门专业课《数据结构》的内容。

下面我们通过下面几个键:

A、B、C、D、E、Shift_A、A

的输入过程,简要地看一下int 9中断例程对键盘输入的处理方法。

(1)初始状态下,没有键盘输入,键盘缓冲区空,此时没有任何元素。


(2)按下A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,看看是否有Shift、Ctrl等切换键按下;发现没有切换键按下,则将A键的扫描码leh和对应的ASCII码,即字母"a”的ASCII码61h,写入键盘缓冲区。缓冲区的字单元中,高位字节存储扫描码,低位字节存储ASCII码。此时缓冲区中的内容如下。


(3)按下B键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出B键的通码;然后检测状态字节,看看是否有切换键按下;发现没有切换键按下,将B键的扫描码30h和对应的ASCII码,即字母"b”的ASCII码62h,写入键盘缓冲区。此时缓冲区中的内容如下。


(4)按下C、D、E键后,缓冲区中的内容如下。


(5)按下左Shift键,引发键盘中断;int 9中断例程接收左Shift键的通码,设置0040:17处的状态字节的第1位为1,表示左Shift键按下。

(6)按下A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;检测状态字节,看看是否有切换键按下;发现左Shift键被按下,则将A键的扫描码1Eh和Shift_A对应的ASCII码,即字母"A”的ASCII码41h,写入键盘缓冲区。此时缓冲区中的内容如下。


(7)松开左Shift键,引发键盘中断;int 9中断例程接收左Shift键的断码,设置 0040:17处的状态字节的第1位为0,表示左Shift键松开。

(8)按下A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,看看是否有切换键按下;发现没有切换键按下,则将A键的扫描码1Eh和A对应的ASCII码,即字母"a”的ASCII码61h,写入键盘缓冲区。此时缓冲区中的内容如下。

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

BIOS提供了int 16h中断例程供程序员调用。int 16h中断例程中包含的一个最重要的功能是从键盘缓冲区中读取一个键盘输入,该功能的编号为0。

下面的指令从键盘缓冲区中读取一个键盘输入,并且将其从缓冲区中删除:

mov ah,0
int 16h

结果:(ah)=扫描码,(al)=ASCII码。

下面我们接着上一节中的键盘输入过程,看一下int 16h如何读取键盘缓冲区。

(1)执行

mov ah,0
int 16h

后,缓冲区中的内容如下。


ah中的内容为1Eh,al中的内容为61h。

(2)执行

mov ah,0
int 16h

后,缓冲区中的内容如下。


ah中的内容为30h,al中的内容为62h。

(3)执行

mov ah,0
int 16h

后,缓冲区中的内容如下。


ah中的内容为2Eh,al中的内容为63h。

(4)执行4次

mov ah,0
int 16h

后,缓冲区空。


ah中的内容为lEh,al中的内容为61h。

(5)执行

mov ah,0
int 16h

int 16h中断例程检测键盘缓冲区,发现缓冲区空则循环等待,直到缓冲区中有数据。

(6)按下A键后,缓冲区中的内容如下。


(7)循环等待的int 16h中断例程检测到键盘缓冲区中有数据,将其读出缓冲区又 为空。


ah中的内容为1Eh,al中的内容为61h。

从上面我们可以看出,int 16h中断例程的0号功能,进行如下的工作。

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

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

它们写入和读出的时机不同,int 9中断例程是在有键按下的时候向键盘缓冲区中写入数据;而int 16h中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。

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

编程,接收用户的键盘输入,输入“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

17.3 字符串的输入

用户通过键盘输入的通常不仅仅是单个字符而是字符串。下面我们讨论字符串输入中的问题和简单的解决方法。

最基本的字符串输入程序,需要具备下面的功能。

(1)在输入的同时需要显示这个字符串;
(2)一般在输入回车符后,字符串输入结束;
(3)能够删除己经输入的字符。

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

编写一个接收字符串输入的子程序,实现上面3个基本功能。因为在输入的过程中需要显示,子程序的参数如下:

  • (dh)、(dl)=字符串在屏幕上显示的行、列位置;
  • ds:si指向字符串的存储空间,字符串以0为结尾符。

下面我们进行分析。

(1)字符的输入和删除。

每个新输入的字符都存储在前一个输入的字符之后,而删除是从最后面的字符进行的,我们看下面的过程。

空字符串:

输入"a" : a
输入“b” : ab
输入“c” : abc
输入“d” : abed
删除一个字符:abc
删除一个字符:ab
删除一个字符:a
删除一个字符:

可以看出在字符串输入的过程中,字符的输入和输出是按照栈的访问规则进行的,即后进先出。这样,我们就可以用栈的方式来管理字符串的存储空间,也就是说字符串的存储空间实际上是一个字符栈。字符栈中的所有字符,从栈底到栈顶,组成一个字符串。

(2)在输入回车符后,字符串输入结束。

输入回车符后,可以在字符串中加入0,表示字符串结束。

(3)在输入的同时需要显示这个字符串。

每次有新的字符输入和删除一个字符的时候,都应该重新显示字符串,即从字符栈的栈底到栈顶,显示所有的字符。

(4)程序的处理过程。

现在我们可以简单地确定程序的处理过程如下。

1、调用int 16h读取键盘输入;
2、如果是字符,进入字符栈,显示字符栈中的所有字符;继续执行1;
3、如果是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行1;
4、如果是Enter键,向字符栈中压入0,返回。

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

子程序:字符栈的入栈、出栈和显示。

参数说明:

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



上面的子程序中,字符栈的访问规则如下所示。

另外一个要注意的问题是,显示栈中字符的时候,要注意清除屏幕上上一次显示的内容。

我们现在写岀完整的接收字符串输入的子程序,如下所示。

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
        call charstack      ;0入栈
        mov ah,2
        call charstack      ;显示栈中的字符
        pop ax
        ret

17.4 应用int 13h中断例程对磁盘进行读写

我们主要以3.5英寸软盘为例,进行讲解。

3.5英寸软盘分为上下两面,每面有80个磁道,每个磁道又分为18个扇区,每个扇区的大小为512个字节。

则:2面x80磁道x18扇区x512字节=1440KB≈1.44MB

磁盘的实际访问由磁盘控制器进行,我们可以通过控制磁盘控制器来访问磁盘。只能以扇区为单位对磁盘进行读写。在读写扇区的时候,要给出面号、磁道号和扇区号。面号和磁道号从0开始,而扇区号从1开始。

如果我们通过直接控制磁盘控制器来访问磁盘,则需要涉及许多硬件细节。BIOS提供了对扇区进行读写的中断例程,这些中断例程完成了许多复杂的和硬件相关的工作。我们可以通过调用BIOS中断例程来访问磁盘。

BIOS提供的访问磁盘的中断例程为int 13h。读取0面0道1扇区的内容到0:200的程序如下所示。

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

返回参数:

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

将0:200中的内容写入0面0道1扇区。

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

返回参数:

操作成功:(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

        mov al,8
        mov ch,0
        mov cl,1
        mov dl,0
        mov dh,0
        mov ah,3
        int 13h

        mov ax,4c00h
        int 21h

code ends

end start

汇编语言-使用BIOS进行键盘输入和磁盘读写

int9中断例程对键盘输入的处理

??键盘输入将引发9号中断,BIOS提供了int9中断例程。CPU在9号中断发生后,执行int 9中断例程,从60h端口读出扫描码,并将其转化为相应的ASCII码或状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中。
??一般的键盘输入,在CPU执行完int 9中断例程后,都放到了键盘缓冲区中。键盘缓冲区中有16个字单元,可以存储15个按键的扫描码和对应的ASCII码。

使用int16h中断例程读取键盘缓冲区

??BIOS提供了int 16h中断例程供程序员调用。int 16h中断例程中包含的一个最重要的功能是从键盘缓冲区中读取一个键盘输入,该功能的编号为0。下面的指令从键盘缓冲区中读取一个键盘输入,并且将其从缓冲区中删除。

mov ah,0
int 16h
结果: (ah)=扫描码,(al)=ASCII码

int 16h中断例程检测键盘缓冲区,发现缓冲区空,则循环等待,直到缓冲区中有数据。

  • nt 16h中断例程的0号功能,进行如下的工作:
  • 检测键盘缓冲区中是否有数据;
  • 没有则继续做第1步;
  • 读取缓冲区第一个字节单元中的键盘输入;
  • 将读取的扫描码送入ah,ASCII码送入al;
  • 将已读取的键盘输入从缓冲区中删除。

??BIOS的int 9中断例程和int 16h中断例程是一对相互配合的程序,int 9中断例程向键盘缓冲区中写入,int 16h中断例程从缓冲区中读出。它们写入和读出的时机不同,int 9中断例程是在有键按下的时候向键盘缓冲区中写入数据;而int 16h中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。

字符串的输入

最简单的字符串输入程序,需要具备下面的功能:

  • 在输入的同时需要显示这个字符串;
  • 一般在输入回车符后,字符串输入结束;
  • 能够删除已经输入的字符。

以栈的方式处理字符串的输入,需要的功能有入栈、出栈、显示。

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

??3.5英寸软盘分为上下两面,每面有80个磁道,每个磁道又分为18个扇区,每个扇区的大小为512个字节。

??则:2面80磁道18扇区*512字节=1440KB~=1.44MB

??磁盘的实际访问由磁盘控制器进行,我们可以通过控制磁盘控制器来访问磁盘。只能以扇区为单位对磁盘进行读写。在读写扇区的时候,要给出面号、磁道号和扇区号。面号和磁道号从0开始,而扇区号从1开始。

??如果我们通过直接控制磁盘控制器来访问磁盘,则需要涉及许多硬件细节。BIOS提供了对扇区进行读写的中断例程,这些中断例程完成了许多复杂的和硬件相关的工作。我们可以通过调用BIOS中断例程来访问磁盘。

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

以上是关于[汇编]《汇编语言》第17章 使用BIOS进行键盘输入和磁盘读写的主要内容,如果未能解决你的问题,请参考以下文章

第17章 使用BIOS进行键盘输入和磁盘读写

汇编语言-使用BIOS进行键盘输入和磁盘读写

汇编语言-18使用BIOS进行键盘输入和磁盘读写

第28章:使用汇编语言进行代码注入

汇编语言第一章总结

第4章 x86反汇编速成班