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


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

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

BIOS为这两种外设的I/O提供了最基本的中断例程


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

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

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

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

BIOS键盘缓冲区是用环形队列结构管理的内存区

  1. 初始状态,无键盘输入,键盘缓冲区为空
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  1. 按下A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,查看是否有ShiftCtrl等切换键按下;发现无切换键按下,则将A的扫描码1eh和对应字母aASCII61h,写入键盘缓冲区。

    缓冲区的字单元中,高位字节存储扫描码,低位字节存储ASCII

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1E61
  1. 按下B键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出B键的通码;然后检测状态字节,查看是否有ShiftCtrl等切换键按下;发现无切换键按下,则将B的扫描码30h和对应字母bASCII62h,写入键盘缓冲区。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1E61 3062
  1. 按下CDE键后,缓冲区内容如下:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1E61 3062 2E63 2064 1265
  1. 按下左Shift键,引发键盘中断;int 9中断例程接收左Shift键的通码,设置0040:17处的状态字节的第1位为1,表示左Shift键按下(0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。第1位置1表示左shift按下)

  2. 按下A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,发现左Shift键按下;则将A的扫描码1ehShift_A对应ASCII码,即字母AASCII41h,写入键盘缓冲区。

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1E61 3062 2E63 2064 1265 1E41
  1. 松开左Shift键,引发键盘中断;int 9中断例程接收左Shift键的断码,设置0040:17处的状态字节的第1位为0,表示左Shift键松开

  2. 按下A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,查看是否有ShiftCtrl等切换键按下;发现无切换键按下,则将A的扫描码1eh和对应字母aASCII61h,写入键盘缓冲区。

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1E61 3062 2E63 2064 1265 1E41 1E61

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

BIOSint 16h中断例程中包含的一个最重要的功能是从键盘缓冲区读取一个键盘输入,该功能的编号为0

从键盘缓冲区中读取一个键盘输入,并将其从缓冲区中删除:

1
2
mov ah,0
int 16h

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

  1. 执行

    1
    2
    mov ah,0
    int 16h

    后,ah中内容为30hal中的内容为62h

    缓冲区的内容如下

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3062 2E63 2064 1265 1E41 1E61
  1. 执行6次

    1
    2
    mov ah,0
    int 16h

    后,ah中内容为1Ehal中的内容为61h

    缓冲区空

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  1. 执行

    1
    2
    mov ah,0
    int 16h

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

  2. 按下A键后,缓冲区内容如下

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1E61
  1. 循环等待的int 16h中断例程检测到键盘缓冲区中有数据,将其读出,缓冲区又为空

    ah中内容为1Ehal中的内容为61h

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

int 16h中断例程的0号功能工作如下:

  1. 检测键盘缓冲区中是否有数据

  2. 没有则循环进行第1步

  3. 读取缓冲区第一个字单元中的键盘输入

  4. 将读取的扫描码送入ahASCII码送入al

  5. 将已读取的键盘的输入从缓冲区中删除

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

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

编程,接收用户的键盘输入,更改字符颜色

输入”r”,将屏幕中的字符设为红色;输入”g”,将屏幕中的字符设为绿色;输入”b”,将屏幕中字符设为蓝色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

字符串的输入

用户通过键盘输入的通常不仅仅是单个字符而是字符串

最基本的字符串输入程序,具备以下功能:

  1. 在输入的同时需要显示这个字符串

  2. 一般在输入回车符后,字符串输入结束

  3. 能够删除已经输入的字符

编写一个接收字符串输入的子程序

子程序的参数如下:

(dh)(dl)=字符串在屏幕上显示的行、列位置

ds:si指向字符串的存储空间,字符串以0为结尾符

分析:

  1. 字符的输入和删除

    字符的输入和输出是按照栈的访问规则进行的,即后进先出

    字符串的存储空间为一个字符栈。字符栈中的所有字符,从栈底到栈顶,组成一个字符串

  2. 在输入回车符后,字符串输入结束

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

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

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

  4. 程序的处理过程

    1. 调用int 16h读取键盘输入

    2. 若是字符,进入字符栈,显示栈中的所有字符;继续执行第1步

    3. 若是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行第1步

    4. 若是回车键,向字符栈中压入0,返回

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

参数说明:

(ah)=功能号,0表示入栈、1表示出栈、2表示显示

ds:si指向字符栈空间

对于0号功能:(al)=入栈字符

对于1号功能:(al)=返回的字符

对于2号功能:(dh)(dl)=字符串在屏幕上显示的行、列位置

在显示栈中字符时,要清除屏幕中上一次显示的内容,再从栈中取字符串重新显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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 ;比较功能号,大于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 ;栈顶减1,字符出栈
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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

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

以3.5英寸软盘为例

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

2面*80磁道*18扇区*512字节=1440KB≈1.44MB

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

只能以扇区为单位对磁盘进行读写,在读写扇区过程中,要提供面号、磁道号、扇区号。面号和磁道号从0开始,扇区号从1开始

通过调用BIOS提供的访问磁盘的中断例程int 13h来访问磁盘

读取0面0道1扇面的内容到0:200

入口参数:

  1. (ah)=int 13h的功能号(2表示读扇区)

  2. (al)=读取的扇区数

  3. (ch)=磁道号

  4. (cl)=扇区号

  5. (dh)=磁头号(即面号)

  6. (dl)=驱动器号(软驱从0开始,0为软驱A,1为软驱B)(硬盘从80h开始,80h为硬盘C81h为硬盘D)

  7. es:bx指向接收从扇区读入数据的内存区

返回参数:

  1. 操作成功:(ah)=0(al)=读入的扇区数

  2. 操作失败:(ah)=出错代码

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,0
mov es,ax
mov bx,200h ;es:bx指向接收内存

mov al,1 ;读取的扇区数
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)

mov ah,2 ;int 13h的功能号2,读扇区
int 13h

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

入口参数:

  1. (ah)=int 13h的功能号(3表示读扇区)

  2. (al)=写入的扇区数

  3. (ch)=磁道号

  4. (cl)=扇区号

  5. (dh)=磁头号(即面号)

  6. (dl)=驱动器号(软驱从0开始,0为软驱A,1为软驱B)(硬盘从80h开始,80h为硬盘C81h为硬盘D)

  7. es:bx指向将写入磁盘的内存单元

返回参数:

  1. 操作成功:(ah)=0(al)=写入的扇区数

  2. 操作失败:(ah)=出错代码

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,0
mov es,ax
mov bx,200h ;es:bx指向要写入的内存单元

mov al,1 ;写入的扇区数
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)

mov ah,3 ;int 13h的功能号3,写扇区
int 13h

编程:将当前屏幕的内容保存到磁盘上

分析:

一屏的内容占4000个字节,需要8个扇区,用0面0道的1–8扇区存储显存中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:code
code segment

start: mov ax,0b800h
mov es,ax
mov bx,0 ;es:bx指向显存单元

mov al,8 ;写入的扇区数
mov ch,0 ;磁道号
mov cl,1 ;扇区号
mov dl,0 ;驱动器号
mov dh,0 ;磁头号(面号)

mov ah,3 ;int 13h的功能号3,写扇区
int 13h

mov ax,4c00h
int 21h

code ends
end start

逻辑扇区编号

使用面号、磁道号、扇区号来范问磁盘不太方便。可以对位于不同的磁道、面上的所有扇区进行统一编址。编号从0开始,一直到2879,称为逻辑扇区编号

编号方法:

物理扇区号 逻辑扇区号
0面0道1扇区 0
0面0道2扇区 1
0面0道3扇区 2
0面0道4扇区 3
0面0道18扇区 17
0面1道1扇区 18
0面1道2扇区 19
0面79道18扇区 1439
1面0道1扇区 1440

逻辑扇区号和物理扇区号关系:逻辑扇区号=(面号*80+磁道号)*18+扇区号-1

根据逻辑扇区号计算出物理编号:

int():描述性运算符,取商

rem():描述性运算符,取余数

逻辑扇区号=(面号*80+磁道号)*18+扇区号-1

面号=int(逻辑扇区号/1440)

磁道号=int(rem(逻辑扇区号/1440)/18)

扇区号=rem(rem(逻辑扇区号/1440)/18)+1


---------------The End---------------
0%