[匯編]《匯編語言》第17章 使用BIOS進行鍵盤輸入和磁盤讀寫


王爽《匯編語言》第四版 超級筆記

第17章 使用BIOS進行鍵盤輸入和磁盤讀寫

大多數有用的程序都需要處理用戶的輸入,鍵盤輸入是最基本的輸入。程序和數據通常需要長期存儲,磁盤是最常用的存儲設備。

BIOS為這兩種外設的I/O提供了最基本的中斷例程,在本章中我們對它們的應用和相關的問題進行討論。

17.1 int 9中斷例程對鍵盤輸入的處理

鍵盤輸入將引發9號中斷,BIOS提供了int 9中斷例程。CPU在9號中斷發生后,執行int 9中斷例程,從60h端口讀出掃描碼,並將其轉化為相應的ASCII碼或狀態信息,存儲在內存的指定空間(鍵盤緩沖區或狀態字節)中。

一般的鍵盤輸入,在CPU執行完int 9中斷例程后,都放到了鍵盤緩沖區中。鍵盤緩沖區中有16個字單元,可以存儲15個按鍵的掃描碼和對應的ASCII碼。

下面我們按照鍵盤緩沖區的邏輯結構,來看一下鍵盤輸入的掃描碼和對應的ASCII碼是如何寫入鍵盤緩沖區的。

注意:在我們的課程中,僅在邏輯結構的基礎上,討論BIOS鍵盤緩沖區的讀寫問題。其實鍵盤緩沖區是用環形隊列結構管理的內存區,但我們不對隊列和環形隊列的實現進行討論,因為那是另一門專業課《數據結構》的內容。

下面我們通過下面幾個鍵:

A、B、C、D、E、Shift_A、A

的輸入過程,簡要地看一下int 9中斷例程對鍵盤輸入的處理方法。

(1)初始狀態下,沒有鍵盤輸入,鍵盤緩沖區空,此時沒有任何元素。

image
(2)按下A鍵,引發鍵盤中斷;CPU執行int 9中斷例程,從60h端口讀出A鍵的通碼;然后檢測狀態字節,看看是否有Shift、Ctrl等切換鍵按下;發現沒有切換鍵按下,則將A鍵的掃描碼leh和對應的ASCII碼,即字母"a”的ASCII碼61h,寫入鍵盤緩沖區。緩沖區的字單元中,高位字節存儲掃描碼,低位字節存儲ASCII碼。此時緩沖區中的內容如下。

image
(3)按下B鍵,引發鍵盤中斷;CPU執行int 9中斷例程,從60h端口讀出B鍵的通碼;然后檢測狀態字節,看看是否有切換鍵按下;發現沒有切換鍵按下,將B鍵的掃描碼30h和對應的ASCII碼,即字母"b”的ASCII碼62h,寫入鍵盤緩沖區。此時緩沖區中的內容如下。

image
(4)按下C、D、E鍵后,緩沖區中的內容如下。

image
(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,寫入鍵盤緩沖區。此時緩沖區中的內容如下。

image
(7)松開左Shift鍵,引發鍵盤中斷;int 9中斷例程接收左Shift鍵的斷碼,設置 0040:17處的狀態字節的第1位為0,表示左Shift鍵松開。

(8)按下A鍵,引發鍵盤中斷;CPU執行int 9中斷例程,從60h端口讀出A鍵的通碼;然后檢測狀態字節,看看是否有切換鍵按下;發現沒有切換鍵按下,則將A鍵的掃描碼1Eh和A對應的ASCII碼,即字母"a”的ASCII碼61h,寫入鍵盤緩沖區。此時緩沖區中的內容如下。

image

17.2 使用int 16h中斷例程讀取鍵盤緩沖區

BIOS提供了int 16h中斷例程供程序員調用。int 16h中斷例程中包含的一個最重要的功能是從鍵盤緩沖區中讀取一個鍵盤輸入,該功能的編號為0。

下面的指令從鍵盤緩沖區中讀取一個鍵盤輸入,並且將其從緩沖區中刪除:

mov ah,0
int 16h

結果:(ah)=掃描碼,(al)=ASCII碼。

下面我們接着上一節中的鍵盤輸入過程,看一下int 16h如何讀取鍵盤緩沖區。

(1)執行

mov ah,0
int 16h

后,緩沖區中的內容如下。

image
ah中的內容為1Eh,al中的內容為61h。

(2)執行

mov ah,0
int 16h

后,緩沖區中的內容如下。

image
ah中的內容為30h,al中的內容為62h。

(3)執行

mov ah,0
int 16h

后,緩沖區中的內容如下。

image
ah中的內容為2Eh,al中的內容為63h。

(4)執行4次

mov ah,0
int 16h

后,緩沖區空。

image
ah中的內容為lEh,al中的內容為61h。

(5)執行

mov ah,0
int 16h

int 16h中斷例程檢測鍵盤緩沖區,發現緩沖區空則循環等待,直到緩沖區中有數據。

(6)按下A鍵后,緩沖區中的內容如下。

image
(7)循環等待的int 16h中斷例程檢測到鍵盤緩沖區中有數據,將其讀出緩沖區又 為空。

image
ah中的內容為1Eh,al中的內容為61h。

從上面我們可以看出,int 16h中斷例程的0號功能,進行如下的工作。

(1)檢測鍵盤緩沖區中是否有數據;
(2)沒有則繼續做第1步;
(3)讀取緩沖區第一個字單元中的鍵盤輸入;
(4)將讀取的掃描碼送入ah,ASCII碼送入al;
(5)將己讀取的鍵盤輸入從緩沖區中刪除。

可見,BIOS的int 9中斷例程和int 16h中斷例程是一對相互配合的程序,int 9中斷例程向鍵盤緩沖區中寫入,int 16h中斷例程從緩沖區中讀出。

它們寫入和讀出的時機不同,int 9中斷例程是在有鍵按下的時候向鍵盤緩沖區中寫入數據;而int 16h中斷例程是在應用程序對其進行調用的時候,將數據從鍵盤緩沖區中讀出。

我們在編寫一般的處理鍵盤輸入的程序的時候,可以調用int 16h從鍵盤緩沖區中讀取鍵盤的輸入。

編程,接收用戶的鍵盤輸入,輸入“r”,將屏幕上的字符設置為紅色;輸入“g”, 將屏幕上的字符設置為綠色;輸入“b”,將屏幕上的字符設置為藍色。

程序如下,畫線處的程序比較技巧,請讀者自行分析。

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

17.3 字符串的輸入

用戶通過鍵盤輸入的通常不僅僅是單個字符而是字符串。下面我們討論字符串輸入中的問題和簡單的解決方法。

最基本的字符串輸入程序,需要具備下面的功能。

(1)在輸入的同時需要顯示這個字符串;
(2)一般在輸入回車符后,字符串輸入結束;
(3)能夠刪除己經輸入的字符。

對於這3個功能,我們可以想象在DOS中,輸入命令行時的情況。

編寫一個接收字符串輸入的子程序,實現上面3個基本功能。因為在輸入的過程中需要顯示,子程序的參數如下:

  • (dh)、(dl)=字符串在屏幕上顯示的行、列位置;
  • ds:si指向字符串的存儲空間,字符串以0為結尾符。

下面我們進行分析。

(1)字符的輸入和刪除。

每個新輸入的字符都存儲在前一個輸入的字符之后,而刪除是從最后面的字符進行的,我們看下面的過程。

空字符串:

輸入"a" : a
輸入“b” : ab
輸入“c” : abc
輸入“d” : abed
刪除一個字符:abc
刪除一個字符:ab
刪除一個字符:a
刪除一個字符:

可以看出在字符串輸入的過程中,字符的輸入和輸出是按照棧的訪問規則進行的,即后進先出。這樣,我們就可以用棧的方式來管理字符串的存儲空間,也就是說字符串的存儲空間實際上是一個字符棧。字符棧中的所有字符,從棧底到棧頂,組成一個字符串。

(2)在輸入回車符后,字符串輸入結束。

輸入回車符后,可以在字符串中加入0,表示字符串結束。

(3)在輸入的同時需要顯示這個字符串。

每次有新的字符輸入和刪除一個字符的時候,都應該重新顯示字符串,即從字符棧的棧底到棧頂,顯示所有的字符。

(4)程序的處理過程。

現在我們可以簡單地確定程序的處理過程如下。

1、調用int 16h讀取鍵盤輸入;
2、如果是字符,進入字符棧,顯示字符棧中的所有字符;繼續執行1;
3、如果是退格鍵,從字符棧中彈出一個字符,顯示字符棧中的所有字符;繼續執行1;
4、如果是Enter鍵,向字符棧中壓入0,返回。

從程序的處理過程中可以看出,字符棧的入棧、出棧和顯示棧中的內容,是需要在多處使用的功能,我們應該將它們寫為子程序。

子程序:字符棧的入棧、出棧和顯示。

參數說明:

(ah)=功能號,0表示入棧,1表示出棧,2表示顯示;
ds:si指向字符棧空間;
對於0號功能:(al)=入棧字符;
對於1號功能:(al)=返回的字符;
對於2號功能:(dh)、(dl)=字符串在屏幕上顯示的行、列位置。

image
image
上面的子程序中,字符棧的訪問規則如下所示。

image

另外一個要注意的問題是,顯示棧中字符的時候,要注意清除屏幕上上一次顯示的內容。

我們現在寫岀完整的接收字符串輸入的子程序,如下所示。

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

17.4 應用int 13h中斷例程對磁盤進行讀寫

我們主要以3.5英寸軟盤為例,進行講解。

3.5英寸軟盤分為上下兩面,每面有80個磁道,每個磁道又分為18個扇區,每個扇區的大小為512個字節。

則:2面x80磁道x18扇區x512字節=1440KB≈1.44MB

磁盤的實際訪問由磁盤控制器進行,我們可以通過控制磁盤控制器來訪問磁盤。只能以扇區為單位對磁盤進行讀寫。在讀寫扇區的時候,要給出面號、磁道號和扇區號。面號和磁道號從0開始,而扇區號從1開始。

如果我們通過直接控制磁盤控制器來訪問磁盤,則需要涉及許多硬件細節。BIOS提供了對扇區進行讀寫的中斷例程,這些中斷例程完成了許多復雜的和硬件相關的工作。我們可以通過調用BIOS中斷例程來訪問磁盤。

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)=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
mov cl,1
mov dl,0
mov dh,0

mov ah,3
int 13h

入口參數:

(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)=出錯代碼

注意,下面我們要使用int 13h中斷例程對軟盤進行讀寫。直接向磁盤扇區寫入數據是很危險的,很可能覆蓋掉重要的數據。如果向軟盤的0面0道1扇區中寫入了數據,要使軟盤在現有的操作系統下可以使用,必須要重新格式化。在編寫相關的程序之前,必須要找一張空閑的軟盤。在使用int 13h中斷例程時一定要注意驅動器號是否正確,千萬不要隨便對硬盤中的扇區進行寫入。

編程:將當前屏幕的內容保存在磁盤上。

分析:1屏的內容占4000個字節,需要8個扇區,用0面0道的1~8扇區存儲顯存中的內容。程序如下。

assume cs:code

code segment

start:  mov ax,0b800h
        mov es,ax
        mov bx,0

        mov al,8
        mov ch,0
        mov cl,1
        mov dl,0
        mov dh,0
        mov ah,3
        int 13h

        mov ax,4c00h
        int 21h

code ends

end start


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM