外中斷
以前我們討論的都是CPU對指令的執(zhí)行。
我們知道,CPU在計算機(jī)系統(tǒng)中,除了能夠執(zhí)行指令,進(jìn)行運(yùn)算以外,還應(yīng)該能夠?qū)ν獠吭O(shè)備進(jìn)行控制,接收它們的輸入,向它們進(jìn)行輸出。也就是說,CPU除了有運(yùn)算能力外,還要有I/O(Input/Output,輸入/輸出)能力。
比如,我們按下鍵盤上的確個鍵,CPU最終要能夠處理這個鍵。在使用文本編輯器時,按下a鍵后,我們可以看到屏幕上出現(xiàn)“a”,是CPU將從鍵盤上輸入的鍵所對應(yīng)的字符送到顯示上的。
要及時處理外設(shè)的輸入,顯然需要解決兩個問題:
一是外設(shè)的輸入隨時可能發(fā)生,CPU如何得知?
二是CPU從何處得到外設(shè)的輸入?
接口芯片和端口
在前面我們知道,在PC系統(tǒng)的接口卡和主板上,裝有各種接口芯片。
這些外設(shè)接口芯片的內(nèi)部有若干寄存器,CPU將這些寄存器當(dāng)作端口來訪問。
外設(shè)的輸入不直接送入內(nèi)存和CPU,而是送入相關(guān)的接口芯片的端口中;
CPU向外設(shè)的輸出也不是直接送入外設(shè),而是先送入端口中,再由相關(guān)的芯片送到外設(shè)。
CPU還可以向外設(shè)輸出控制命令,而這些控制命令也是先送到相關(guān)芯片的端口中,然后再由相關(guān)的芯片根據(jù)命令對外設(shè)實(shí)施控制。
可見,CPU通過端口和外部設(shè)備進(jìn)行聯(lián)系。
外中斷信息
現(xiàn)在,我們知道了外設(shè)的輸入被存放在端口中,可是外設(shè)的輸入隨時都有可能到達(dá),CPU如何及時地知道,并進(jìn)行處理呢?更一般地講,就是外設(shè)隨時都可能發(fā)生需要CPU及時處理的事件,CPU如何及時得知并進(jìn)行處理?
CPU提供中斷機(jī)制來滿足這種需要。前面講過,當(dāng)CPU的內(nèi)部有需要處理的事情發(fā)生的時候,將產(chǎn)生中斷信息,引發(fā)中斷過程。這種中斷信息來自CPU的內(nèi)部。
還有一種中斷信息,來自于CPU外部,當(dāng)CPU外部有需要處理的事情發(fā)生的時候,比如說,外設(shè)的輸入到達(dá),相關(guān)芯片將向CPU發(fā)出相應(yīng)的中斷信息。CPU在執(zhí)行完當(dāng)前指令后,可以檢測到發(fā)送過來的中斷信息,引發(fā)中斷過程,處理外設(shè)的輸入。
在PC系統(tǒng)中,外中斷源一共有兩類:
1、可屏蔽中斷
可屏蔽中斷是CPU可以不響應(yīng)的外中斷,CPU是否響應(yīng)可屏蔽中斷,要看標(biāo)志寄存器的IF位的設(shè)置。當(dāng)CPU檢測到可屏蔽中斷信息時,如果IF=1,則CPU在執(zhí)行完當(dāng)前指令后響應(yīng)中斷,引發(fā)中斷過程;如果IF=0,則不響應(yīng)可屏蔽中斷。
回憶一下內(nèi)中斷所引發(fā)的中斷過程:
1)取中斷類型碼n;
2)標(biāo)志寄存器入棧,IF=0,TF=0;
3)CS、IP入棧;
4)(IP)=(n*4),(CS)=(n*4+2)
由此轉(zhuǎn)去執(zhí)行中斷處理程序。
可屏蔽中斷所引發(fā)的中斷過程,除在第1步的實(shí)現(xiàn)上有所不同外,基本上和內(nèi)中斷的中斷過程相同。因?yàn)榭善帘沃袛嘈畔碜杂?/span>CPU外部,中斷類型碼是通過數(shù)據(jù)總路線送入CPU的;而內(nèi)中斷的中斷類型碼是在CPU內(nèi)部產(chǎn)生的。
現(xiàn)在,我們可以解釋中斷過程中將IF設(shè)置為0的原因了。將IF置0的原因就是,在進(jìn)入中斷處理程序后,禁止其他的可屏蔽中斷。
當(dāng)然,如果在中斷處理程序中需要處理可屏蔽中斷,可以用指令將IF置1。8086CPU提供的設(shè)置IF的指令如下:
sti,用于設(shè)置IF=1;
cli,用于設(shè)置IF=0。
2、不可屏蔽中斷
不可屏蔽中斷是CPU必須響應(yīng)的外中斷。當(dāng)CPU檢測到不可屏蔽中斷信息時,則在執(zhí)行完當(dāng)前指令后,立即響應(yīng),引發(fā)中斷過程。
對于8086CPU,不可屏蔽中斷的中斷類型碼固定為2,所以中斷過程中,不需要取中斷類型碼。則不可屏蔽中斷的中斷過程為:
(1)標(biāo)志寄存器入棧,IF=0,TF=0;
(2)CS、IP入棧;
(3)(IP)=(8),(CS)=(0AH)。
幾乎所有由外設(shè)引發(fā)的外中斷,都是可屏蔽中斷。當(dāng)外設(shè)有需要處理的事件(比如說鍵盤輸入)發(fā)生時,相關(guān)芯片向CPU發(fā)出可屏蔽中斷信息。不可屏蔽中斷是在系統(tǒng)中有必須處理的情況發(fā)生時用來通知CPU的中斷信息。
PC機(jī)鍵盤的處理過程
下面來看一下鍵盤輸入的處理過程,并以此來體會一下PC機(jī)處理外設(shè)輸入的基本方法。
1、鍵盤輸入
鍵盤上的每一個鍵相當(dāng)于一個開關(guān),鍵盤中有一個芯片對鍵盤上的每一個鍵的開關(guān)狀態(tài)進(jìn)行掃描。
按下一個鍵時,開關(guān)接通,該芯片就產(chǎn)生一個掃描碼,掃描碼說明了按下的鍵在鍵盤上的位置。掃描碼被送入主板上的相關(guān)接口芯片的寄存器中,該寄存器的端口地址為60H。
松開按下的鍵時,也產(chǎn)生一個掃描碼,掃描碼說明了松開的鍵在鍵盤上的位置。松開按鍵時產(chǎn)生的掃描碼也被送入60H端口中。
一般將按下一個鍵時產(chǎn)生的掃描碼稱為通碼,松開一個鍵產(chǎn)生的掃描碼稱為斷碼。
掃描碼長度為一個字節(jié),通碼的第7位為0,斷碼的第7位為1,即:
斷碼=通碼+80H
比如:g鍵的通碼為22H,斷碼為a2H。
2、引發(fā)9號中斷
鍵盤的輸入到達(dá)60H端口時,相關(guān)的芯片就會向CPU發(fā)出中斷類型碼為9的可屏蔽中斷信息。CPU檢測到該中斷信息后,如果IF=1,則響應(yīng)中斷,引發(fā)中斷過程,轉(zhuǎn)去執(zhí)行int 9中斷例程。
3、執(zhí)行int 9中斷例程
BIOS提供了int 9中斷例程,用來進(jìn)行基本的鍵盤輸入處理,主要的工作如下:
(1)讀出60H端口中的掃描碼;
(2)如果是字符鍵的掃描碼,將該掃描碼和它所對應(yīng)的字符碼(即ASCII碼)送入內(nèi)存中的BIOS鍵盤緩沖區(qū);如果是控制鍵(比如Ctrl)和切換鍵(比如CapsLock)的掃描碼,則將其轉(zhuǎn)變?yōu)?span>狀態(tài)字節(jié)(用二進(jìn)制位記錄控制鍵和切換鍵狀態(tài)的字節(jié))寫入內(nèi)存中存儲狀態(tài)字節(jié)的單元。
(3)對鍵盤系統(tǒng)進(jìn)行相關(guān)的控制,比如說,向相關(guān)芯片發(fā)出應(yīng)答信息。
BIOS鍵盤緩沖區(qū)是系統(tǒng)啟動后,BIOS用于存放int 9中斷例程所接收的鍵盤輸入的內(nèi)存區(qū)。該內(nèi)存區(qū)可以存儲15個鍵盤輸入,因?yàn)?/span>int 9中斷例程除了接收掃描碼外,還要產(chǎn)生和掃描碼對應(yīng)的字符碼,所以在BIOS鍵盤緩沖區(qū)中,一個鍵盤輸入用一個字單元存放,高位字節(jié)存放掃描碼,低位字節(jié)存放字符碼。
0040:17單元存儲鍵盤狀態(tài)字節(jié),該字節(jié)記錄了控制鍵和切換鍵的狀態(tài)。鍵盤狀態(tài)字節(jié)各位記錄的信息如下:
0:右shift狀態(tài),置1表示按下右shift鍵;
1:左shift狀態(tài),置1表示按下左shift鍵;
2:Ctrl狀態(tài),置1表示按下Ctrl鍵;
3:Alt狀態(tài),置1表示按下Alt鍵;
4:ScrolLock狀態(tài),置1表示Scroll指示燈亮;
5:NumLock狀態(tài),置1表示小鍵盤輸入的是數(shù)字;
6:CapsLock狀態(tài),置1表示輸入大寫字母;
7:Insert狀態(tài):置1表示處于刪除狀態(tài)。
編寫int 9中斷例程
鍵盤輸入的處理過程:
1)鍵盤產(chǎn)生掃描碼;
2)掃描碼送入60h端口;
3)引發(fā)9號中斷;
4)CPU執(zhí)行int 9中斷例程處理鍵盤輸入。
上面的過程中,第(1)、(2)、(3)步都是由硬件系統(tǒng)完成的。我們能夠改變的只有int 9的中斷處理程序。
編程:在屏蔽中間依次顯示“a”~“z”,并可以讓人看清。在顯示的過程中,按下Esc鍵后,改變顯示的顏色。
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov ah,’a’
s: mov es:[160*12+40*2],ah
inc ah
cmp ah,’z’
jna s
mov ax,4c00h
int 21h
code ends
end start
上面的程序的執(zhí)行過程中,我們無法看清屏幕上的顯示,因?yàn)橐粋€字母剛顯示到屏幕上,因?yàn)?/span>CPU執(zhí)行指令太快了。
我們應(yīng)該在每顯示一個字母后,延時一段時間,讓人看清后,再顯示下一個字母。
那么如何延時呢?我們讓CPU執(zhí)行一段時間的空循環(huán),因?yàn)楝F(xiàn)在的CPU的速度都非常快,所以循環(huán)的次數(shù)一定要大,我們用兩個16位寄存器來存放32位的循環(huán)次數(shù)。如下:
mov dx,10h
mov ax,0
s: sub ax,1
sbb dx,0
cmp ax,0
jne s
cmp dx,0
jne s
上面的程序,循環(huán)100000h次。我們可以將循環(huán)延時的程序段寫為一個子程序。
assume cs:code
stack segment
db 128 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
mov ax,0b800h
mov ex,ax
mov ah,’a’
s: mov es:[160*12+40*2],ah
call delay
inc ah
cmp ah,’z’
jna s
mov ax,4c00h
int 21h
delay: push ax
push dx
mov dx,1000h ;循環(huán)10000000次,讀者可以根據(jù)自己機(jī)器的速度調(diào)整循環(huán)次數(shù)
mov ax,0
s1: sub ax,1
sbb dx,0
jne sl
cmp dx,0
jne s1
pop dx
pop ax
ret
code ends
end start
上面的程序,顯示“a”~“z”,并可以讓人看清,這個任務(wù)已經(jīng)實(shí)現(xiàn)。
那么如何實(shí)現(xiàn),按下Esc鍵后,改變顯示的顏色呢?
鍵盤輸入到達(dá)60h端口后,就會引發(fā)9號中斷,CPU則轉(zhuǎn)去執(zhí)行int 9中斷例程。我們可以編寫int 9中斷例程,功能如下:
(1)從60h端口讀出鍵盤的輸入;
(2)調(diào)用BIOS的int 9中斷全程,處理其他硬件細(xì)節(jié);
(3)判斷是否為Esc鍵的掃描碼,如果是,改變顯示的顏色后返回;如果不是則直接返回。
分析:
1、從端口60h讀出鍵盤的輸入
in al,60h
2、調(diào)用BIOS的int 9中斷例程
注意,我們寫的中斷處理程序要成為新的int 9中斷例程,主程序必須要將中斷向量表中的int 9中斷例程的入口地址改為我們寫的中斷處理程序的入口地址。則在新的中斷處理程序中調(diào)用原來的int 9中斷例程時,中斷向量表中的int 9中斷例程的入口地址卻不是原來的int 9中斷例程的地址,所以我們不能使用int 指令直接調(diào)用。
要能在我們寫的新的中斷例程中調(diào)用原來的中斷例程,就必須在將中斷向量表中的中斷例程的入口地址改為新地址之前,將原來的入口地址保存起來。這樣,在需要調(diào)用的時候我們才能找到原來的中斷例程的入口。
有了入口地址后,我們?nèi)绾芜M(jìn)行調(diào)用呢?
當(dāng)然不能使用指令int 9來調(diào)用,我們可以用別的指令來對int 指令進(jìn)行一些模擬,從而實(shí)現(xiàn)對中斷例程的調(diào)用。
int指令在執(zhí)行的時候,CPU進(jìn)行下面的工作:
1)取中斷類型碼n;
2)標(biāo)志寄存器入棧;
3)IF=0,TF=0;
4)CS、IP入棧;
5)(IP)=(n*4),(CS)=(n*4+2)。
取中斷類型碼是為了定位中斷例程的入口地址,在我們的問題中,中斷例程的入口地址已經(jīng)知道。所以,我們用別的指令模擬int指令時候,不需要做第(1)步。在假設(shè)要調(diào)用的中斷例程的入口地址在ds:0和ds:2單元中的前提下,我們將int過程用下面幾步模擬:
1)標(biāo)志寄存器入棧;
2)IF=0,TF=0;
3)CS、IP入棧;
4)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。
所以int 過程的模擬過程變?yōu)椋?/span>
1)標(biāo)志寄存器入棧;
2)IF=0,TF=0;
3)call dword ptr ds:[0]
對于(1),可用pushf實(shí)現(xiàn)。
對于(2),可用下面的指令實(shí)現(xiàn)。
pushf
pop ax
and ah,11111100b ;IF和TF為標(biāo)志寄存的第9位和第8位
push ax
popf
則,模擬int指令的調(diào)用功能,調(diào)用入口地址在ds:0、ds:2中的中斷例程的程序?yàn)椋?/span>
pushf ;標(biāo)志寄存器入棧
pushf
pop ax
and ah,11111100b
push ax
popf ;IF=0, TF=0
call dword ptr ds:[0] ;CS、IP入棧;(IP)=((ds)*16+0), (CS)=((ds)*16+2)
3、如果是Esc鍵掃描碼,改變顯示的顏色后返回
如果改變顯示的顏色?
顯示的位置是屏幕的中間,即第12行40列,顯存中的偏移地址為:160*12+40*2。所以字符的ASCII碼要送入b800:160*12+40*2處。而b800:160*12+40*2+1處是字符的屬性,我們只要改變此處的數(shù)據(jù)就可以改變在b800:160*12+40*2處顯示的字符的顏色了。
該程序的最后一個問題是,要在程序返回前,將中斷向量表中的int 9中斷例程的入口地址恢復(fù)為原來的地址。否則程序返回后,別的程序?qū)o法使用鍵盤。
完整的程序如下:
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]
pop ds:[0]
push es:[9*4+2]
pop ds:[2] ;將原來的int 9中斷例程的入口地址保存在ds:0、ds:2單元中
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs ;在中斷向量表中設(shè)置新的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]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2] ;將中斷向量表中int 9中斷例程的入口恢復(fù)為原來的地址
mov ax,4c00h
int 21h
delay:push ax
push dx
mov dx,1000h
mov ax,0
s1:sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret
;-------以下為新的int 9中斷例程------------------
int9: push ax
push bx
push es
in al,60h
pushf
pushf
pop bx
and bh,11111100b
push bx
popf
call dword ptr ds:[0] ;對int指令進(jìn)行模擬,調(diào)用原來的int 9中斷例程
cmp al,1
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1] ;將屬性值加1,改變顏色
int9ret:pop es
pop bx
pop ax
iret
code ends
end start
檢測點(diǎn)15.1
(1)模擬int指令調(diào)用原int9中斷例程的程序段是可以精簡的,因?yàn)樵谶M(jìn)入中斷例程后,IF和TF都已經(jīng)置0,沒有必要再進(jìn)行設(shè)置了。
對于程序段:
pushf
pushf
pop ax
and ah,11111100b
push ax
popf
call dword ptr ds:[0]
可以精簡為:
pushf
call dword ptr ds:[0]
再條指令。
(2)仔細(xì)分析上面程序中的主程序,會發(fā)現(xiàn)一個潛在的問題?在主程序中,如果在執(zhí)行設(shè)置int9中斷例程的段地址和偏移地址的指令之間發(fā)生了鍵盤中斷,則CPU將轉(zhuǎn)去一個錯誤的地址執(zhí)行,將發(fā)生錯誤。
排除潛在問題方法:
在pop ds:[2]指令后加入一條cli指令,并在mov es:[9*4+2],cs指令后加入一條sti指令即可。意思是在這段期間,讓IF=0,禁止或屏蔽外中斷處事程序的執(zhí)行。
安裝新的int9中斷處理例程
安裝一個新的int 9中斷例程,使得原int 9中斷例程的功能得到擴(kuò)展。
任務(wù):安裝一個新的int 9中斷例程,功能:在DOS下,按F1鍵后改變當(dāng)前屏幕的顯示顏色,其他的鍵照常處理。
分析:
(1)改變屏幕的顯示顏色
改變從B8000開始的4000個字節(jié)中的所有奇地址單元中的內(nèi)容,當(dāng)前屏幕的顯示顏色即發(fā)生改變。程序如下:
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s: nc byte ptr es:[bx]
add bx,2
loop s
(2)其他鍵照常處理
可以調(diào)用原int 9中斷處理程序,來處理其他的鍵盤輸入。
(3)原int 9中斷例程入口地址的保存
因?yàn)樵诰帉懙男?/span>int 9中斷例程中要調(diào)用原int 9中斷例程,所以,要保存原int 9中斷例程的入口地址。保存在哪里?顯然不能保存在安裝程序中,因?yàn)榘惭b程序返回后地址將丟失。我們將地址保存在0:200單元處。
(4)新int 9中斷例程的安裝
我們可將新的int 9中斷例程安裝在0:204處。
完整的程序如下:
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 ;設(shè)置ds:si指向源地址
mov di,204h ;設(shè)置es:di指向目的地址
mov cx,offset int9end - offset int9 ;設(shè)置cx為傳輸長度
cld ;設(shè)置傳輸方向?yàn)檎?/span>
rep movsb
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h] ;保存原有的int 9中斷例程入口地址
cli ;設(shè)置IF為0,禁止CPU執(zhí)行可屏蔽外中斷。
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0 ;設(shè)置int 9中斷向量,指向新的中斷例程入口地址
sti ;恢復(fù)設(shè)置IF為1
mov ax,4c00h
int 21h
int9:push ax
push bx
push cx
push es
in al,60h
pushf
call dword ptr cs:[200h] ;當(dāng)此中斷例程執(zhí)行時(CS)=0
;cmp al,1 ;F1的掃描碼為3bh
;jne int9ret
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s:mov byte ptr es:[bx],2
;inc byte ptr es:[bx]
and bx,2
loop s
int9ret:pop es
pop cx
pop bx
pop ax
iret
int9end:nop
code ends
end start
通過對鍵盤輸入的處理,了解了CPU對外設(shè)輸入的通常 通常方法,即:
1)外設(shè)的輸入送入端口;
2)向CPU發(fā)出外中斷(可屏蔽中斷)信息;
3)CPU檢測到可屏蔽中斷信息,如果IF=1,CPU在執(zhí)行完當(dāng)前指令后響應(yīng)中斷,執(zhí)行相應(yīng)的中斷例程;
4)可在中斷例程中實(shí)現(xiàn)對外設(shè)輸入的處理。
端口和中斷機(jī)制,是CPU進(jìn)行I/O的基礎(chǔ)。
指令系統(tǒng)總結(jié)
8086CPU提供以下幾大類指令:
1、數(shù)據(jù)傳送指令
mov, push, pop, pushf, popf, xchg等。
實(shí)現(xiàn)寄存器和內(nèi)存、寄存器和寄存器之間的單個數(shù)據(jù)傳送。
2、自述運(yùn)算指令
add, sub, adc, sbb, inc, dec, cmp, imul, idiv, aaa 等。
實(shí)現(xiàn)寄存器和內(nèi)存中的數(shù)據(jù)的算術(shù)運(yùn)算。它們的執(zhí)行結(jié)果影響標(biāo)志寄存器的:sf, zf, of, cf, pf, af位。
3、邏輯指令
and, or, not, xor, test, shl, shr, sal, sar, rol, ror, rcl, rcr等。
除了not指令外,它們的執(zhí)行結(jié)果都影響標(biāo)志寄存器的相關(guān)標(biāo)志位。
4、轉(zhuǎn)移指令
可以修改IP,或同時修改CS和IP的指令統(tǒng)稱為轉(zhuǎn)移指令。分為以下幾類:
1)無條件轉(zhuǎn)移指令,比如:jmp;
2)條件轉(zhuǎn)移指令,比如:jcxz, je, jb, ja, jnb, jna等;
3)循環(huán)指令,比如:loop;
4)過程,比如:call, ret, reft;
5)中斷,比如:int, iret。
5、處理機(jī)控制指令
這些指令對標(biāo)志寄存器或其他處理機(jī)狀態(tài)進(jìn)行設(shè)置,比如:cld, std, cli, sti, nop, clc, cmc, stc, hlt, wait, csc, lock等都是處理機(jī)控制指令。
6、串處理指令
這些指令對內(nèi)存中的批量數(shù)據(jù)進(jìn)行處理,比如:movsb, movsw, cmps, scas, lods, stos等。
基要使用這些指令方便地進(jìn)行批量數(shù)據(jù)的處理,則需要和rep, repe, repne等前綴指令配合使用。