第十五章
在計算機系統中,CPU除了能執行指令,進行運算外,還能對外部設備進行控制,接收它們的輸入,向它們進行輸出,即I/O能力,比如在文本編輯器中,我們按鍵盤中的一個鍵,可以看到屏幕上出現該字母,是CPU將從鍵盤上輸入的鍵對應的字符送到顯示器上
及時處理外設的輸入,有兩個問題,即外設的輸入隨時可能發生,CPU如何得知,以及CPU從何處得到外設的輸入
以鍵盤爲例進行討論
15.1 接口芯片和端口
外設的輸入不直接送入內存和CPU,而是送入相關的接口芯片的端口中,CPU向外設的輸出也是先送入端口中,再由相關的芯片送到外設,CPU還可以向外設輸出控制命令,控制命令也是先送到相關芯片的端口中
CPU通過端口和外部設備進行練習冊
15.2 外中斷信息
由於外設隨時都可能發生需要CPU及時處理的事件,因此CPU提供中斷機制,比如外設的輸入到達,相關芯片將向CPU發出相應的中斷信息嗎,CPU在執行完當前指令後,檢測到中斷信息,引發中斷過程,處理外設的輸入
PC系統中,外中斷源有兩類
1、可屏蔽中斷
CPU可以不響應,是否響應取決於標誌寄存器IF位的設置,當CPU檢測到可屏蔽中斷信息時,如果IF=1,則CPU在執行完當前指令後響應中斷,IF=0則可以屏蔽
可屏蔽中斷信息來自於CPU外部,中斷類型碼通過數據總線送入CPU,而內中斷的中斷類型碼是在CPU內部產生的,可屏蔽中斷引發的中斷過程除了中斷類型碼來源不同,其他步驟基本和內中斷相同
因此中斷過程中將IF設置爲0是爲了在進入中斷處理程序後,禁止其他的可屏蔽中斷
如果中斷處理程序中需要處理可屏蔽程序,將IF置1
sti ; IF=1
cli ; IF=0
2、不可屏蔽中斷
是CPU必須響應的中斷,CPU檢測到不可屏蔽中斷信息時,執行完當前指令後立即響應
8086CPU不可屏蔽中斷的中斷類型碼固定爲2,所以中斷過程不需要中斷類型碼
並且最後設置(IP)=(8) (CS)=(0AH)
幾乎所有外設引發的外中斷都是可屏蔽的(如鍵盤輸入)
不可屏蔽中斷是在系統中有必須處理的緊急情況時通知CPU,我們主要討論可屏蔽中斷
15.3 PC機鍵盤的處理過程
1、鍵盤輸入
鍵盤上每個鍵相當於一個開關,鍵盤中有一個芯片對每個鍵的開關狀態進行掃描
按下一個鍵時,開關接通,芯片產生一個掃描碼,掃描碼說明了按下的鍵在鍵盤上的位置,掃描碼被送入主板上相關接口芯片的寄存器中,該寄存器的端口地址爲60h
鬆開按下的鍵,也產生一個掃描碼,說明鬆開鍵的位置,該掃描碼也被送到60h端口中
按下鍵時產生的掃描碼成爲通碼,按下時產生的稱爲斷碼,掃描碼長度爲一個字節
斷碼=通碼+80h(通碼第7位爲0,斷碼第7位爲1)
如:g的通碼爲22h,斷碼爲a2h
2、引發9號中斷
鍵盤的輸入到達60h端口時,相關芯片就會向CPU發出中斷類型碼爲9的可屏蔽中斷信息,CPU根據IF的值決定是否響應中斷
3、執行int 9中斷例程
BIOS提供int 9中斷例程,進行基本的鍵盤輸入處理
執行過程:首先讀出60h端口中的掃描碼,如果時字符鍵的掃描碼,將該掃描碼和它對應的ASCII碼送入內存中的BIOS鍵盤緩衝區;如果是控制鍵(如Ctrl)和切換鍵(CapsLock)的掃描碼,將其轉變爲狀態字節(用二進制位記錄控制鍵和切換鍵狀態的字節)寫入內存中存儲狀態字節的單元,最後對鍵盤系統進行相關的控制
BIOS鍵盤緩衝區是系統啓動後BIOS用於存放int 9中斷例程所接收的鍵盤輸入的內存區
該內存區可以存儲15個鍵盤輸入,一個鍵盤輸入佔一個字單元,高位存放掃描碼,低位存放字符碼
0040:17這個內存單元存儲鍵盤狀態字節,該字節記錄了控制鍵和切換鍵的狀態,具體各個位記錄的信息如下
15.4 編寫int9中斷例程
編程:在屏幕中間依次顯示“a”~“z”,並可以讓人看得清在顯示過程中,按下Esc鍵後,改變顯示的顏色
如果用普通的循環向一個內存單元依次傳送一個字母,由於CPU執行速度很快,所以字母切換太快,無法看清
所以每顯示一個字母,讓CPU執行一段時間的空循環,用兩個16位寄存器存放32位的循環次數
現在需要改變顯示的顏色,我們編寫的int 9中斷例程功能:
首先從60端口讀鍵盤的輸出
調用BIOS的int 9中斷例程處理其他硬件細節
判斷是否爲Esc的掃描碼
有一個問題在於新的int 9中斷例程會改變中斷向量表中相應的入口地址,那麼我們想調用原來的int 9中斷例程就會有麻煩,因此在更改中斷向量表中的入口地址前,先保存原有的地址,然後對int指令進行模擬,從而實現對中斷例程的調用
int n指令執行時,中斷類型碼n的作用就是從中斷向量表中獲取中斷例程的入口地址,而這裏我們已經提前保存了入口地址,因此只需要標誌寄存器入棧,設置IF=0、TF=0,隨後CS、IP入棧,最後修改CS、IP的值
我們發現call dword ptr ds:[0]即可完成後兩步
最後一個問題是,在程序返回前,需要將中斷向量表中的int 9中斷例程的入口地址恢復爲原來的地址,否則程序返回後,別的程序將無法使用鍵盤
assume cs:code
stack segment
db 128 dup (0)
stack ends
data segment
dw 0,0 ; 保存原有中斷例程的地址
data ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
mov ax,data
mov ds,ax
mov ax,0
mov es,ax
push es:[9*4] ; 將原來的int 9中斷例程的入口地址
pop ds:[0] ; 保存到ds:0 ds:2單元中
push es:[9*4+2]
pop ds:[2]
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs ; 設置新的int 9中斷例程的入口地址
mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:[160*12+40*2],ah
call delay ; 空循環,保證每個字母都可以看得清
inc ah ; 下一個字母
cmp ah,'z'
jna s
mov ax,0
mov es,ax
push ds:[0] ; 該程序不安裝
push ds:[2] ; 只是臨時更改int 9中斷例程的入口
pop es:[9*4] ; 在顯示字母的過程中只要按下Esc就可以變色
pop es:[9*4+2] ; 將int 9中斷例程的入口恢復爲原來的地址
mov ax,4c00h
int 21h
delay: push ax
push dx
mov dx,1000h ; 循環10000000h次
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0 ; 只有ax dx都不等於0
jne s1 ; 循環才結束
pop dx
pop ax
ret
; -------------以下爲新的int 9中斷例程------------
int9: push ax
push bx
push es
in al,60h ; 從端口取出掃描碼
; 調用系統提供的int 9中斷例程
pushf ; 標誌寄存器入棧
pushf
pop bx
and bh,11111100b ; 設置IF=0 TF=0
push bx
popf
call dword ptr ds:[0] ;相當於CS IP入棧並修改CS IP的值
cmp al,1 ; 判斷是否爲Esc鍵
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1] ; 屬性值加一,改變顏色
int9ret: pop es
pop bx
pop ax
iret
code ends
end start
在模擬int9中斷例程時,我門可以精簡程序,因爲在進入我們寫的中斷例程中時,IF呵TF都已經置爲0,因此在進入BIOS的int 9中斷例程時,沒必要再設置一次IF和TF
pushf ; 標誌寄存器入棧
pushf
pop bx
and bh,11111100b ; 設置IF=0 TF=0
push bx
popf
call dword ptr ds:[0] ;相當於CS IP入棧並修改CS IP的值
; 精簡爲
pushf ; 標誌寄存器入棧
call dword ptr ds:[0] ;相當於CS IP入棧並修改CS IP的值
還有一個問題,如果在主程序重新設置的int 9中斷例程的段地址和偏移地址的指令之間發生了鍵盤中斷,則CPU將轉去一個錯誤的地址執行(CS寄存器中的內容可能會改變)
cli ; 設置IF爲0.不響應可屏蔽中斷
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs ; 設置新的int 9中斷例程的入口地址
sti
15.5 安裝新的int 9中斷例程
安裝一個新的int 9中斷例程,在DOS下,按F1鍵後改變當前屏幕的顏色
assume cs:code
stack segment
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
push cs
pop ds
mov ax,0
mov es,ax
; 安裝
mov si,offset int9
mov di,204h
mov cx,offset int9end-offset int9
cls
rep movsb
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[20h]
cli
mov word ptr es:[9*4],204h
moc word ptr es:[9*4+2],0
mov ax,4c00h
int 21h
int9: push ax
push bx
push cx
push es
in al,60h
pushf
call dowrd ptr cs:[200h]
cmp al,3bh ; F1掃描碼爲3bh
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s: inc byte ptr es:[bx]
add bx,2
loop s
int9ret: pop es
pop cx
pop ax
pop ax
iret
int9end: nop
code ends
end start
端口和中斷機制是CPU進行I/O的基礎