BIOS 为键盘和磁盘这两种外设的 I/O 提供了最基本的中断例程
int 9 中断例程对键盘输入的处理
键盘输入将引发9号中断,BIOS 提供了 int 9 中断例程。CPU 在9号中断发生后,执行 int 9 中断例程,从 60h 端口读出扫描码,并将其转化为相应的 ASCII码 或 状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中。
一般的键盘输入,在CPU执行完 int 9 中断例程后,都放到了键盘缓冲区中。键盘缓冲区中有16个字单元,可以存储15个按键的扫描码和对应的ASCII码。
**键盘缓冲区是用环形队列结构管理的内存区。
假设依次按下几个键:A、B、C、D、E、shift_A、A | ||||||||||||||||
1、初始状态下,没有键盘输入,键盘缓冲区空,此时没有任何元素; |
||||||||||||||||
2、按下 A 键,引发键盘中断;CPU执行int 9中断例程,从60h端口读出 A 键的通码;然后检测状态字节,看看是否有 shift、ctrl 等切换按键按下;发现没有切换键按下,则将 A 键的扫描码 leh 和对应的 ASCII 码,即字母 "a" 的 ASCII 码 61h,写入键盘缓冲区。缓冲区的字单元中,高位字节存储扫描码,低位字节存储ASCII码。
|
||||||||||||||||
3、按下 B 键,引发键盘中断;CPU 执行 int 9 中断例程,从60h端口读出B键的通码;然后检测状态字节,看看是否有 shift、ctrl 等切换按键按下;发现没有切换键按下,则将 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 键的通码;然后检测状态字节,看看是否有 shift、ctrl 等切换按键按下;发现没有切换键按下,则将 A 键的扫描码 leh 和对应的 ASCII 码,即字母 "a" 的 ASCII 码 61h,写入键盘缓冲区。缓冲区的字单元中;
|
使用 int 16h 中断例程读取键盘缓冲区
BIOS 提供了 int 16h 中断例程,int 16h 中断例程包含一个最重要的功能是从键盘缓冲区中读取一个键盘输入,该功能的编号为 0。读取完输入后会将这个键盘输入从缓冲区中删除。
mov ah , 0 int 16h |
(ah)=扫描码 (al)=ASCII码 |
接上面,键盘缓冲区:
|
||||||||||||||||||||||||||||||||
2、循环执行6次后键盘缓冲区为空。int 16h 中断例程检测键盘缓冲区,发现缓冲区空,则循环等待,直到缓冲区中有数据。 |
int 16h 中断例程的0号功能主要进行如下的工作:
① 检测键盘缓冲区中是否有数据;
② 没有则继续做第一步;
③ 读取缓冲区第一个字单元中的键盘输入;
④ 将读取的扫描码送入 ah,ASCII 码送入 al;
⑤ 将已读取的键盘输入从缓冲区中删除。
BIOS 的 int 9 中断例程和 int 16h 中断例程是一对相互配合的程序,int 9h 中断例程向键盘缓冲区中写入,int 16h 中断例程从缓冲区中读出。他们写入和读出的时机不同,int 9h 中断例程是在有键按下的时候向键盘缓冲区中写入数据;而 int 16h 中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。
编程,接收用户的键盘输入,输入"r",将屏幕上的字符设置为红色;输入"g",将屏幕上的字符设置为绿色;输入"b",将屏幕上的字符设置为蓝色,输入其他字符不做任何改变; |
assume cs:code
code segment
start: mov ah , 0
int 16h ; 从键盘缓存区读取键盘输入,空则循环等待
mov ah , 1 ; 设置(ah)=00000001b
cmp al , 'r' ; 待设置的颜色属性为红色,100
je red
cmp al , 'g' ; 待设置的颜色属性为绿色,010
je green
cmp al , 'b' ; 待设置的颜色属性为蓝色,001
je blue
jmp short sret ; 其他键不做特殊处理,直接退出
red: shl ah , 1 ; 属性红色就要把ah的1左移两位
green: shl ah , 1 ; 属性绿色就要把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
|
“在 int 16h 中断例程中,一定有设置 IF=1 的指令”这种说法是对的;假设int 16h中断去读键盘缓冲区,发现没有数据就会循环等待,直到缓冲区有数据。如果中断例程没有设置 IF=1 的指令,那么int 9h 号可屏蔽中断就无法执行,不能往缓冲区放入键盘输入数据,这个时候就产生死锁;
字符串的输入
用户通过键盘输入的通常不仅仅是单个字符而是字符串。
最基本的字符串输入程序,需要具备下面的功能:
① 在输入的同时需要显示这个字符串;
② 一般在输入回车符后,字符串输入结束;
③ 能够删除已经输入的字符。
编写一个接收字符串的输入子程序,实现上面三个基本功能。因为在输入的过程中的需要显示,子程序的参数如下: (dh)、(dl)=字符串在屏幕上显示的行、列位置; ds:si 指向字符串的存储空间,字符串以 0 为结尾负。 |
1、字符的输入和删除。 每个新输入的字符都存储在前一个输入的字符之后,而删除是从最后面的字符进行的。 eg: 空字符串: 输入"a": a 输入"b": ab 输入"c": abc 输入"d": abcd 删除一个字符:abc 删除一个字符:ab 字符串的存储空间实际上是一个字符栈,字符栈中的所有字符,从栈低到栈顶,组成一个字符串。 |
2、在输入回车符后,字符串输入结束。 输入回车符后,可以在字符串中加入0,表示字符串结束。 |
3、在输入的同时需要显示这个字符串。 每次有新的字符输入和删除一个字符的时候,都应该重新显示字符串,即从字符栈的栈低到栈顶,显示所有的字符。 |
4、程序的处理过程。 ① 调用 int 16h 读取键盘输入; ② 如果是字符,进入字符栈,显示字符栈中的所有字符;继续执行①; ③ 如果是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行①; ④ 如果是 Enter 键,向字符栈中压入0,返回; |
字符栈的入栈、出栈和显示栈中的内容,是需要在多处使用的功能,我们应该将它们写为子程序。 |
子程序:字符栈的入栈、出栈和显示。 参数说明: (ah)=功能号,0 表示入栈,1 表示出栈,2 表示显示; ds:si 指向字符栈空间; 对于 0 号功能:(al)=入栈字符; 对于 1 号功能:(al)=返回的字符; 对于 2 号功能:(dh)、(dl)=字符串在屏幕上显示的行、列位置。 |
显示栈中字符的时候,要注意清除屏幕上上一次显示的内容。 |
// 实现代码在programing
应用 int 13h 中断例程对磁盘进行读写
3.5寸软盘,分为上下两面,每面有80个磁道,每个磁道又分为18个扇区,每个扇区的大小为512B。
size = 2 * 80 * 18 * 512 = 1440 KB ≈ 1.44 MB
3.5 英寸软盘只能采用 CHS 方式寻址: 3.5 英寸软盘的 CHS 参数: 80个柱面,柱面编号 0 ~ 79; 2个磁头,磁头编号 0、1;
每个磁道有 18 个扇区,每个磁道上扇区编号 1 ~ 18;
每个扇区可存放 512 字节的数据。
CHS寻址方式的容量由CHS三个参数决定:
磁头(磁面)数最大为255 (用 8 个二进制位存储)。从0开始编号。
柱面(磁道)数最大为1023(用 10 个二进制位存储)。从0开始编号。
扇区数最大数 63(用 6个二进制位存储)。从1始编号。
所以CHS寻址方式的最大寻址范围为:
255 * 1023 * 63 * 512 / 1048576 = 7.837 GB ( 1M =1048576 Bytes )
或硬盘厂商常用的单位:
255 * 1023 * 63 * 512 / 1000000 = 8.414 GB ( 1M =1000000 Bytes )
|
CHS寻址模式将硬盘划分为磁头(Heads)、柱面(Cylinder)、扇区(Sector)。
磁头(Heads):每张磁片的正反两面各有一个磁头,一个磁头对应一张磁片的一个面。因此,用第几磁头就可以表示数据在哪个磁面。
柱面(Cylinder):所有磁片中半径相同的同心磁道构成“柱面",意思是这一系列的磁道垂直叠在一起,就形成一个柱面的形状。简单地理解,柱面数=磁道数。
扇区(Sector):将磁道划分为若干个小的区段,就是扇区。虽然很小,但实际是一个扇子的形状,故称为扇区。每个扇区的容量为512字节。
CHS寻址的缺点:
显然,由于要求每个磁道的扇区数相等,而外道的周长要大于内道,所以外道的记录密度要远低于内道,不仅造成了硬盘空间的浪费,也限制了硬盘的容量。为了解决这一问题,进一步提高硬盘容量,人们改用等密度结构生产硬盘。也就是说,外圈磁道的扇区比内圈磁道多,采用这种结构后,硬盘不再具有实际的CHS参数,寻址方式也改为线性寻址,即以扇区为单位进行寻址。
但一些古老的软件仍然使用CHS寻址方式(如使用BIOSInt13H接口的软件),为了兼容这样的程序,在硬盘控制器内部安装了一个地址翻译器,可以通过它将老式CHS参数翻译成新的线性参数。
|
磁盘的实际访问是由磁盘控制器进行,我们可以通过控制磁盘控制器来访问磁盘。只能以扇区为单位对磁盘进行读写。在读写扇区的时候,要给出面号、磁道号和扇区号。面号和磁道号从0开始,而扇区号从1开始。
BIOS 提供的访问磁盘的中断例程为 int 13h 。eg:读取 0 面 0 道 1 扇区的内容到 0:200
读取 0 面 0 道 1 扇区的内容到 0:200
mov ax , 0
mov es , ax
mov bx , 200h
mov al , 1 ; 读取一个扇区
mov ch , 0 ; 0号磁道
mov cl , 1 ; 1号扇区
mov dh , 0 ; 0面
mov dl , 0 ; 驱动器号0,软驱A
mov ah , 2 ; 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 ; 0号磁道
mov cl , 1 ; 1号扇区
mov dh , 0 ; 0面
mov dl , 0 ; 驱动器号0,软驱A
mov ah , 3 ; 3号功能,写扇区
int 13h
|
入口参数: (ah)=int 13h 功能号 (3表示写扇区) (al)=写入的扇区数 (ch)=磁道号 (cl)=扇区号 (dh)=磁头号(面) (dl)=驱动器号 es:bx 指向将写入磁盘的数据 返回参数: 操作成功:(ah)=0 , (al)=写入的扇区数
操作失败:(ah)=出错代码
|
// 我用自己的硬盘测试13h号中断,dl=81h,驱动器号设置为D盘,但是最后AH=FF—检测操作失败(硬盘)可能我的是固态硬盘,或者没有格式化或者磁头号不对。
硬盘C的0道0面1扇区存储有引导操作系统的程序