第十七章
17.1 int 9中斷例程對鍵盤輸入的處理
一般的鍵盤輸入,在CPU執行完int 9中斷例程後,都放到了鍵盤緩衝區中。鍵盤緩衝區共有16個字單元。可存儲15個按鍵的掃描碼和對應的ASCII碼
那麼它們是如何寫入的:
初始狀態下,沒有鍵盤輸入,緩衝區爲空(緩衝區是用環形隊列結構管理的內存區)
按下A,引發鍵盤中斷,CPU執行int 9中斷例程,從60h端口讀出A鍵的通碼,然後檢測狀態字節,看是否有按下Shift、Ctrl等鍵,若沒有,則將A的掃描碼1eh和“a”的ASCII碼61h寫入緩衝區,字單元中,高位字節存儲掃描碼,低位字節存儲ASCII碼
按下B、C、D、E鍵,同理
Shift_A:按下做Shift,引發鍵盤中斷,int 9中斷例程接收左Shift鍵的通碼,設置0040:17處的狀態字節的第1位爲1,表示左Shift鍵按下
按下A,引發鍵盤中斷,……,發現左Shift鍵被按下,則將A的掃描碼和Shift_A對應的ASCII碼,即“A”的ASCII碼41h寫入緩衝區
鬆開左Shift,引發鍵盤中斷,int 9中斷例程接收左Shift的斷碼,設置0040:17處的狀態字節第1位爲0,表示左Shift鬆開
按下A鍵……
17.2 使用int 16h中斷例程讀取鍵盤緩衝區
BIOS 提供int 16h中斷例程供程序員調用,int 16h中斷例程中包含的一個重要的功能是從鍵盤緩衝區中讀取一個鍵盤輸入,該功能編號爲0
; 從鍵盤緩衝區中讀取一個鍵盤輸入
; 並將其從緩衝區中刪除
mov ah,0
int 16h
; 結果 (ah)=掃描碼 (al)=ASCII碼
那麼int 16h是如何讀取的,接着上一節
執行
mov ah,0
int 16h
ah中的內容爲1Eh,al中的內容爲61h
執行
mov ah,0
int 16h
執行多次以後
此時再執行
mov ah,0
int 16h
int 16h中斷例程檢測鍵盤緩衝區,發現緩衝區空,則循環等待,直到緩衝區中有數據
按下A鍵後
循環等待的中斷例程檢測到緩衝區中有數據,將其讀出
可見BIOS的int 9和int 16是一對互相配合的程序,但是寫入和讀出的時機不同一個是有鍵按下時執行,一個是應用程序對其進行調用時執行
在編寫處理鍵盤輸入的程序的時候,可以調用int 16h從鍵盤緩衝區中讀取鍵盤的輸入
編程,接收用戶的鍵盤輸入,輸入“r”,將屏幕上的字符設置爲紅色,“g”綠色,“b”藍色
assume cs:code
code segment
start: mov ah,0
int 16h
mov ah,1 ; 1表示藍色
cmp al,'r'
je red
cmp al,'g'
je green
cmp al,'b'
je blue
jmp short sret
red: shl ah,1 ; 4表示紅色
green: shl ah,1 ; 2表示綠色
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的指令,因爲當鍵盤緩衝區爲空時,如果設置IF=0,int 9中斷無法執行,循環等待會死鎖
17.3 字符串的輸入
編寫一個接收字符串輸入的子程序,實現3個基本功能:輸入的同時顯示這個字符串,在輸入回車後輸入結束,刪除已經輸入的字符
參數:(dh)、(dl)=字符串在屏幕上顯示的行、列位置,ds:si指向字符串的存儲空間
因爲刪除是從尾開始刪,即後進先出,所以字符串的存儲空間實際是一個字符棧
輸入回車後,在字符串中加0,表示結束
每次有輸入或刪除時,都應該從棧底到棧頂的字符都顯示一遍
這些功能可以編寫爲子程序
(ah)=功能號 0表示入棧,1表示出棧,2表示顯示,(al)=入棧/出棧的字符
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 ; 把0入棧
call charstack
mov ah,2
call charstack ; 顯示棧中的字符
pop ax
ret
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 ; 如果ah>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
mov bx,top
mov al,[si][bx]
jmp sret
charshow: mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh ; (dh)、(dl)分別表示顯示的行、列
mov di,ax
add dl,dl
mov dh,0
add di,dx ; di表示要顯示的偏移地址
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
17.4 應用int 13h中斷例程對磁盤進行讀寫
以3.5英寸軟盤爲例,分爲上下面,每面80個磁道,每個磁道18個扇區,每個扇區爲512字節
磁盤的訪問由磁盤控制器進行,以扇區爲單位進行磁盤讀寫,讀寫扇區時要給出面號、磁道號和扇區號,面號、磁道號從0開始,扇區號從1開始
我們可以通過調用BIOS提供的中斷例程(int 13h)來訪問磁盤
; 讀取0面0道1扇區的內容到0:200
; es:bx指向接收從扇區讀入數據的內存區
mov ax,0
mov es,ax
mov bx,200h
; (ah)=int 13h的功能號(3表示寫扇區)
; (al)=寫入的扇區數
; (ch)=磁道號
; (cl)=扇區號
; (dh)=磁頭號(面號)
; (dl)=驅動器號
; 軟驅從0開始 0:軟驅A 1:軟驅B
; 硬盤從80h開始 80h:硬盤C 81h:硬盤D
mov al,1
mov ch,0
mov cl,1
mod dl,0
mov dh,0
mov ah,2
int 13h
; 返回參數:
; 操作成功:(ah)=0 (al)=寫入的扇區數
; 操作失敗:(ah)=出錯的代碼
但是直接向磁盤扇區寫數據是很危險的,必須找一張空閒的軟盤,同時注意驅動器號是否正確
編程:將當前屏幕的內容保存到磁盤中
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov bx,0
mov al,8 ; 一屏4000字節,需要8個扇區
mov ch,0
mov cl,1
mov dl,0
mov dh,0
mov ah,3
int 13h