大多数有用的程序都需要处理用户的输入,键盘输入是最基本的输入
程序和数据通常需要长期存储,磁盘是最常用的存储设备
BIOS为这两种外设的I/O提供了最基本的中断例程
int 9中断例程对键盘输入的处理
键盘输入将引发9号中断,BIOS提供了int 9中断例程。CPU在9号中断发生后,执行int 9中断例程,从60h读出扫描码,并将其转化为相应的ASCII码或状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中
一般的键盘输入,在CPU执行完int 9中断例程后,都放到了键盘缓冲区中。键盘缓冲区中有16个字单元,可以存储15个按键的扫描码和对应的ASCII码
按照键盘缓冲区的逻辑结构,看一下键盘输入的扫描码和对应的ASCII码是如何写入键盘缓冲区的?
BIOS键盘缓冲区是用环形队列结构管理的内存区
- 初始状态,无键盘输入,键盘缓冲区为空
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
按下
A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,查看是否有Shift、Ctrl等切换键按下;发现无切换键按下,则将A的扫描码1eh和对应字母a的ASCII码61h,写入键盘缓冲区。缓冲区的字单元中,高位字节存储扫描码,低位字节存储
ASCII码
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1E61 |
- 按下
B键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出B键的通码;然后检测状态字节,查看是否有Shift、Ctrl等切换键按下;发现无切换键按下,则将B的扫描码30h和对应字母b的ASCII码62h,写入键盘缓冲区。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1E61 | 3062 |
- 按下
C、D、E键后,缓冲区内容如下:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1E61 | 3062 | 2E63 | 2064 | 1265 |
按下左
Shift键,引发键盘中断;int 9中断例程接收左Shift键的通码,设置0040:17处的状态字节的第1位为1,表示左Shift键按下(0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。第1位置1表示左shift按下)按下
A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,发现左Shift键按下;则将A的扫描码1eh和Shift_A对应ASCII码,即字母A的ASCII码41h,写入键盘缓冲区。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1E61 | 3062 | 2E63 | 2064 | 1265 | 1E41 |
松开左
Shift键,引发键盘中断;int 9中断例程接收左Shift键的断码,设置0040:17处的状态字节的第1位为0,表示左Shift键松开按下
A键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出A键的通码;然后检测状态字节,查看是否有Shift、Ctrl等切换键按下;发现无切换键按下,则将A的扫描码1eh和对应字母a的ASCII码61h,写入键盘缓冲区。
| 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中断例程读取键盘缓冲区
BIOS的int 16h中断例程中包含的一个最重要的功能是从键盘缓冲区读取一个键盘输入,该功能的编号为0
从键盘缓冲区中读取一个键盘输入,并将其从缓冲区中删除:
1 | mov ah,0 |
结果:(ah)=扫描码,(al)=ASCII码
执行
1
2mov ah,0
int 16h后,
ah中内容为30h,al中的内容为62h缓冲区的内容如下
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 3062 | 2E63 | 2064 | 1265 | 1E41 | 1E61 |
执行6次
1
2mov ah,0
int 16h后,
ah中内容为1Eh,al中的内容为61h缓冲区空
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
执行
1
2mov ah,0
int 16hint 16h中断例程检测键盘缓冲区,发现缓冲区空,则循环等待,直到缓冲区中有数据按下
A键后,缓冲区内容如下
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1E61 |
循环等待的
int 16h中断例程检测到键盘缓冲区中有数据,将其读出,缓冲区又为空ah中内容为1Eh,al中的内容为61h
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
int 16h中断例程的0号功能工作如下:
检测键盘缓冲区中是否有数据
没有则循环进行第1步
读取缓冲区第一个字单元中的键盘输入
将读取的扫描码送入
ah,ASCII码送入al将已读取的键盘的输入从缓冲区中删除
BIOS的int 9中断例程和int 16h中断例程是一对相互配合的程序,int 9中断例程向键盘缓冲区中写入,int 16h中断例程从缓冲区中读出
int 9中断例程是在有键按下的时候向键盘缓冲区中写入数据;int 16h中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出
编程,接收用户的键盘输入,更改字符颜色
输入”r”,将屏幕中的字符设为红色;输入”g”,将屏幕中的字符设为绿色;输入”b”,将屏幕中字符设为蓝色
1 | assume cs:code |
字符串的输入
用户通过键盘输入的通常不仅仅是单个字符而是字符串
最基本的字符串输入程序,具备以下功能:
在输入的同时需要显示这个字符串
一般在输入回车符后,字符串输入结束
能够删除已经输入的字符
编写一个接收字符串输入的子程序
子程序的参数如下:
(dh)、(dl)=字符串在屏幕上显示的行、列位置
ds:si指向字符串的存储空间,字符串以0为结尾符
分析:
字符的输入和删除
字符的输入和输出是按照栈的访问规则进行的,即后进先出
字符串的存储空间为一个字符栈。字符栈中的所有字符,从栈底到栈顶,组成一个字符串
在输入回车符后,字符串输入结束
输入回车符后,可以在字符串中加入0,表示字符串结束
在输入的同时需要显示这个字符串
每次字符输入和删除时,都应重新显示字符串,即从字符栈的栈底到栈顶,显示所有字符
程序的处理过程
调用
int 16h读取键盘输入若是字符,进入字符栈,显示栈中的所有字符;继续执行第1步
若是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行第1步
若是回车键,向字符栈中压入0,返回
子程序:字符栈的入栈、出栈和显示
参数说明:
(ah)=功能号,0表示入栈、1表示出栈、2表示显示
ds:si指向字符栈空间
对于0号功能:(al)=入栈字符
对于1号功能:(al)=返回的字符
对于2号功能:(dh)、(dl)=字符串在屏幕上显示的行、列位置
在显示栈中字符时,要清除屏幕中上一次显示的内容,再从栈中取字符串重新显示
1 | charstack:jmp short charstart |
完整的接收字符串输入的子程序
1 | getstr: push ax |
应用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
入口参数:
(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)=出错代码
1 | mov ax,0 |
将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指向将写入磁盘的内存单元
返回参数:
操作成功:
(ah)=0,(al)=写入的扇区数操作失败:
(ah)=出错代码
1 | mov ax,0 |
编程:将当前屏幕的内容保存到磁盘上
分析:
一屏的内容占4000个字节,需要8个扇区,用0面0道的1–8扇区存储显存中的内容
1 | assume cs:code |
逻辑扇区编号
使用面号、磁道号、扇区号来范问磁盘不太方便。可以对位于不同的磁道、面上的所有扇区进行统一编址。编号从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