王爽 《汇编语言》 读书笔记 十七
第十七章 使用BIOS进行键盘输入和磁盘读写
17.1 int9中断例程对键盘输入的处理
一般的键盘输入,在cpu执行完int9中断例程以后,欧放到了键盘缓冲区中。 键盘缓冲区有16个字单元,可以存储15个按键的扫描码和对应的ASCII码。
参照书P300页 关于键盘缓冲区的存储利用了队列或者环形队列数据结构。
17.2 使用int16h中断例程读取键盘缓冲区
int16h 能够从键盘缓冲区读取一个键盘输入, 功能标号为0.
mov ah, 0
int 16h
结果 ah = 扫描码 bl = ascii码
然后该字符从键盘缓冲区被删除
int 16h中断例程检测键盘缓冲区,发现缓冲区为空,则循环等待,直到缓冲区中有数据。
int 16h的中断例程0号功能,进行如下工作
1)检测键盘缓冲区是否有数据
2)没有则继续第一步
3)读取缓冲区第一个字单元的键盘输入
4)将读取的扫描码送入ah, ASCII码送入al
5)将已读取的键盘输入从缓冲区中删除。
编程,接受用户的键盘输入,输入“r” ,将屏幕上的字符设置为红色,输入“g”, 将屏幕上的字符设置为绿色
输入“b“ 将屏幕上的字符设置为蓝色。
assume cs:code
code segment
start: mov ah, 0
int 16h
mov ah, 1 ; used to generate the color code
cmp al, 'r'
je red
cmp al, 'g'
je green
cmp al, 'b'
je blue
jmp short sret
red: shl ah, 1 ; left shift once
green: shl ah, 1 ; left shift once . if go through red it will generate 100B
blue: mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000
s: and byte ptr es:[bx], 11111000b ; reset color
or es:[bx], ah ; set new color
add bx, 2
loop s
sret: mov ax, 4c00h
int 21h
code ends
end start

监测点17.1
"在int 16h中断例程中,一定有设置IF=1的指令." 这种说法对吗?
不对.
8086CPU的中断系统具有256个中断(0-255),其中分为
(1)外部中断:又分为 1. 可屏蔽中断,可以由标志寄存器中的IF位控制是否屏蔽(IF=0则屏蔽);
2. 不可屏蔽中断, 中断向量号为02;
(2)内部中断: 包括除法溢出中断,int指令中断,溢出中断,单步中断, 不受标志寄存器的IF位控制;
题意中设置为1是为了保证在读取键盘缓冲区的第一个字节内容时,键盘中断可以响应, 以便将相应按键加入键盘缓冲区中,
但是按下键即调用 int 9h, 属于内部中断, 不受IF位的控制, 故不需要将IF设置为1。
故这种说法错误。
17.3 字符串的输入
基本的字符串输入程序,需要具备下面的功能。
1)在输入的同时需要显示这个字符串:
2)一般在输入回车符后,字符串输入结束;
3)能够删除已经输入的字符。
assume cs:code
data segment
db 256 dup(' ')
data ends
stack segment
db 64 dup(0)
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 64
mov ax, data
mov ds, ax
mov ax, 0
mov si, 0
mov dh, 5
mov dl, 0
call getstr
mov ax, 4c00h
int 21h
;getstr:
getstr: push ax
getstrs: mov ah, 0
int 16h
cmp al, 20h
jb nochar ; ascii code < 20h ,it is not a char
mov ah, 0
call charstack ; push char
mov ah, 2
call charstack ; charshow
jmp getstrs
nochar: cmp ah, 0eh ; backspace
je backspace
cmp ah, 1ch ; return key
je return
jmp getstrs
backspace: mov ah, 1
call charstack ; pop char
mov ah, 2
call charstack ; charshow
jmp getstrs
return: mov al, 0
mov ah, 0
call charstack ; push char '\0'
mov ah, 2
call charstack ; showchar
pop ax
ret
; charstack
; push char into stack, pop up char from stack. and show the current char
; ah= 0 push, 1 pop 2 show
; ds:si point to the char stack.
; for 0 push al
; for 1 pop char and store to al
; for 2 dh, dl point to the position. Show the full string.
charstack: jmp short charstart
table dw charpush, charpop, charshow
top dw 0 ; point to the top of the stack
charstart: push bx
push dx
push di
push es
cmp ah, 2 ; invalid command
ja charret
mov bl, ah
mov bh, 0
add bx, bx
jmp word ptr table[bx] ; call the actual function.
charpush: mov bx, top
mov [si][bx], al
inc top
jmp charret
charpop: cmp top, 0
je charret
dec top
mov bx, top
mov al, [si][bx]
jmp charret
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, ax ; calc the offset
mov bx, 0
charshows: cmp bx, top
jne noempty
mov byte ptr es:[di], ' '
jmp charret
noempty: mov al, [si][bx]
mov es:[di], al
;and es:[di + 1], 11111000b
;or es:[di + 1], 10b ; set color
mov byte ptr es:[di+2], ' '
inc bx
add di, 2
jmp charshows
charret: pop es
pop di
pop dx
pop bx
ret
code ends
end start

17.4 应用int 13h中断例程对磁盘进行读写
需要先在虚拟机中创建虚拟软盘
参考这篇blog: <>
3.5 英寸软盘分为上下两面,每个面80个磁道,每个磁道有18个扇区,每个扇区512个字节。
只能以扇区为单位对磁盘进行读写。
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 = 13h 的功能号(2表示读扇区)
al = 读取扇区数
ch = 磁道号
cl = 扇区号
dh = 磁头号(也就是软盘的面号)
dl=驱动器号(软驱从0开始代码 A, 1 代表B)
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 = int13h 功能号(3表示写扇区)
al = 写入扇区数
ch = 磁道号
cl = 扇区号
dh = 磁头号
dl = 驱动器号
es:bx指向写入磁盘的数据
将当前屏幕的内容保存在磁盘上。
注意写入软盘的0扇区会破坏文件系统导致软盘在当前操作系统下不可用。
assume cs:code
code segment
start: mov ax, 0b800h
mov es, ax
mov bx, 0
mov al, 8 ; write 8sectors
mov ch, 0 ; #of the track
mov cl, 1 ; #of the first sector
mov dl, 0 ; !!!!VERY IMPORTANT Drive A:
mov dh, 0 ; #of disk face
mov ah, 3 ; write floppy
int 13h ; call int 13h
mov ax, 4c00h
int 21h
code ends
end start
写入以后发现windows会提示磁盘未格式化。(文件系统被破坏了)

实验17 编写包含多个功能子程序的中断例程。
安装一个新的int 7ch中断例程,实现通过逻辑扇区号对软盘进行读写。
参数说明
1)用ah 寄存器传递功能号: 0表示读,1表示写
2)用dx寄存器传递要读写的扇区的逻辑扇区号;
3)用es:bx指向存储读初数据或写入数据的内存区。
提示,用逻辑扇区号计算出面号,磁道号,扇区号后,调用int 13h 中断例程进行实际的读写。
安装int7ch的磁盘读写例程
assume cs:code
stack segment
db 64 dup(0)
stack ends
code segment
start: ; set es:di as target address 0000:0200h
mov ax, 0
mov es, ax
mov di, 200h
; set ds:si as source address cs:sqr
mov ax, cs
mov ds, ax
mov si, offset int7ch
; set cx as the data length
mov cx, offset int7chend - offset int7ch
; set the transport directive DF = 0 cld
cld
rep movsb
; set the IRQ table 1F0 = 200h(IP) 1F2 = 0(CS)
mov ax, 0
mov es, ax
mov word ptr es:[7ch * 4], 200h ; install IRQ7ch
mov word ptr es:[7ch * 4 + 2], 0
mov ax, 4c00h
int 21h
org 200h
;write/read floppy from logic sectors
;in ah 0 read, 1 write/read ,al number sectors to write
;dx store the logic sectors.
;es:bx point to the buffer
int7ch: push cx
push ax
mov ax, dx
call getsectors ; get pyhsical address of floppy
cmp ah, 1
jne int7ret
pop ax
cmp ah, 0
je read
cmp ah, 1
je write
jmp int7ret ; return
read: mov ah, 2 ; call #2 function of int13h
int 13h
jmp int7ret
write: mov ah, 3 ; call #3 function of int13h
int 13h
int7ret: pop cx
iret
; get the pyhsical face track and sector from logic #sectors
; in ax (store the logic #sectors from (0~1443))
; out
; ah = 1 success, ah = 0 failed
; dh #of face
; dl drive: default = 0
; ch #of track
; cl #of sector
getsectors: push bx
cmp ax, 2879
ja errors
mov dx, 0
mov bx, 1440
div bx ; al = #face ah = 0
push dx ; store mode
mov dh, al ; store face
mov dl, 0 ; store drive label 0
pop ax
mov bl, 18
div bl ; al = #track , ah = #sector - 1
mov ch, al ; store # track
mov cl, ah
inc cl ; store # sector
mov ah, 1
jmp short retsectors ; return true
errors: mov ah, 0 ; return false
retsectors: pop bx
ret
int7chend: nop
code ends
end start
执行代码:
assume cs:code
code segment
start: mov ax, 0b800h
mov es, ax
mov bx, 0
mov al, 8 ; write 8sectors
mov ah, 0 ; read floppy of int7ch
mov dx, 0 ; write logic sectors 1
int 7ch
mov ax, 4c00h
int 21h
code ends
end start
例如可以将当前屏幕内容写入磁盘。然后再读取出来。
实现在软盘中写入了512x8的屏幕内容
接着读取


载入了之前写入磁盘的内容。说明IO功能有效。
