文章目錄
15.1 接口芯片和端口
15.2 外中斷信息
1.可屏蔽中斷
2.不可屏蔽中斷
15.3 PC機鍵盤的處理過程
1.鍵盤輸入
2.引發9號中斷
3.執行int 9中斷例程
15.4 編寫int 9中斷例程
鍵盤輸入的處理過程:
1.鍵盤產生掃描碼
2.掃描碼送入60h端口
3.引發9號中斷
4.CPU執行int9中斷例程處理鍵盤輸入
編程,在屏幕中間依次顯示"a"-“z”,並可以讓人看清。在顯示的過程中,按下Esc鍵後,改變顯示的顏色。
程序如下:
DATAS SEGMENT
;此處輸入數據段代碼
DATAS ENDS
STACK SEGMENT
db 128 dup (0)
STACK ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACK
START:
mov ax,stack
mov ss,ax
mov sp,128
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 AH,4CH
INT 21H
delay:
push ax
push dx
mov dx,10h ;循環100000次
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
CODES ENDS
END START
運行結果:
如何實現按下Esc鍵後,改變顯示的顏色呢?
程序如下:
DATA SEGMENT
dw 0,0
DATA ENDS
STACK SEGMENT
db 128 dup (0)
STACK ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATA,SS:STACK
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] ;將原來int 9中斷例程的入口地址保存在ds:0、ds:2單元中
push es:[9*4+2]
pop ds:[2]
mov word ptr es:[9*4],offset newint9;在中斷向量表中設置新的 int 9中斷例程的入口地址
mov es:[9*4+2],cs
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
push ds:[0];將中斷向量表中int 9中斷例程的入口恢復爲原來的地址
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
MOV AH,4CH
INT 21H
delay:
push ax
push dx
mov dx,10h
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
newint9:
push ax
push bx
push es
in al,60h
pushf ;對應後面的調用原int 9中斷
pushf ;對應後面的popf
pop bx
and bh,11111100b ;IF和TF是標誌寄存器的第9位和第8位,都置爲0
push ax
popf
call dword ptr ds:[0] ;對int指令進行模擬,調用原來的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
CODES ENDS
END START
運行結果:
檢測點 15.1
(1)仔細分析一下上面的int 9中斷例程,看看是否可以精簡一下?
其實在我們的int 9中斷例程中,模擬int指令調用原int 9中斷例程的程序段是可以精簡的,因爲在進入中斷例程後,IF和TF都已經置零,沒有必要再進行設置了。對於程序段:
pushf ;將寄存器值入棧
pushf ;將寄存器值入棧
pop ax ;彈棧到ax中,(ax)=(flag)
and ah, 11111100b ;注意是高8位,IF和TF是標誌位中的第9和第8位,按位與
push ax ;將修改後的值入棧。
popf ;將修改後的值彈棧到標準寄存器中。這時IF=0,TF=0
call word ptr ds:[0];調用原來的int9中斷例程。
可以精簡爲:
pushf
call word ptr ds:[0]
兩條指令。
程序分析:
【1】由於我們無論是調用那個中斷處理例程,CPU都幹如下的活:
(1)從中斷信息中取得中斷類型碼
(2)標誌寄存器入棧保存(因爲在中斷過程中要改變標誌寄存器的值。)
(3)設置標誌寄存器的第8位TF(跟蹤標誌)和第9位IF(中斷標誌)爲0.(防止單步中斷和其他外部中斷髮生)
(4)cs的內容入棧
(5)IP的內容入棧
(6)設置ip的值爲:N(中斷類型碼)*4;設置cs的值:N*4+2
所以第二個pushf是多餘的指令,然後是設置IF和TF的指令也是多餘的了。
【2】爲什麼還有個pushf呢?
這個pushf指令壓棧標誌寄存器,確實是保護標誌寄存器的值,也是爲了與中斷程序中的iret(它內部CPU操作步驟有popf)相呼應。
如果沒有這個pushf,那麼iret指令執行中出棧到標準寄存器的值可能不正確了。
程序分析:
【1】關於設置中斷向量表的指令,在程序中就2段。
mov word ptr es:[9*4], offset int9
mov es:[9*4+2], cs ;將新的int9入口地址寫入中斷向量表中
; 將中斷向量回寫回中斷向量表中
mov ax, 0
mov es, ax ;將es指向0000段內存中斷向量表
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
如果在指令執行在這2段中間,引發了鍵盤中斷事件,由於正在設置中斷向量表,故(ip)和(cs)值可能不確定。爲了避免這2段代碼不受到中斷事件的干擾,將中斷屏蔽了。
【2】sti和cli指令的用法:
cli 禁止中斷髮生
sti 允許中斷髮生
【3】這二段代碼前後加上cli和sti指令即可:
;在中斷向量表中設置新的中斷入口地址的時候不讓其發生中斷
cli
mov word ptr es:[9*4],offset int9
mov word ptr es:[9*4+2],cs
sti
恢復中斷向量表int9的源地址時:
cli
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
sti
15.5 安裝新的int9中斷例程
完整代碼如下:
ASSUME CS:CODES
STACK SEGMENT
db 128 dup (0)
STACK ENDS
CODES 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 ;設置ds:si指向源地址
mov di,204h ;設置es:di指向目的地址
mov cx,offset int9end - offset int9 ;設置cx爲傳輸長度
cld ;設置傳輸方向爲正
rep movsb ;重複前綴指令,在cx不等於0的情況下執行movsb,每次cx減1.movsb把ds:[si]的cx個字節複製到es:[di]
push es:[9*4]
pop ds:[200h] ;將原來int 9中斷例程的入口地址保存在ds:200h、ds:202h單元中
push es:[9*4+2]
pop ds:[202h]
cli
mov word ptr es:[9*4],204h;把新的int 9中斷例程安裝在0:204處
mov word ptr es:[9*4+2],0
sti
MOV AH,4CH
INT 21H
int9:
push ax
push bx
push cx
push es
in al,60h
pushf ;對應後面的調用原int 9中斷
call dword ptr cs:[200h] ;對int指令進行模擬,調用原來的int 9中斷例程
cmp al,3bh ;F1的掃描碼爲3bh
jne int9ret
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 bx
pop ax
iret
int9end:nop
CODES ENDS
END START
不過這段書上的代碼我運行起來沒有結果,於是我把新裝的int 9 中斷和前面動態顯示’a’-'z’字符的代碼組合起來了。
ASSUME CS:CODES,ss:stack
STACK SEGMENT
db 128 dup (0)
STACK ENDS
CODES 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 ;設置ds:si指向源地址
mov di,204h ;設置es:di指向目的地址
mov cx,offset int9end - offset int9 ;設置cx爲傳輸長度
cld ;設置傳輸方向爲正
rep movsb ;重複前綴指令,在cx不等於0的情況下執行movsb,每次cx減1.movsb把ds:[si]的cx個字節複製到es:[di]
push es:[9*4]
pop ds:[200h] ;將原來int 9中斷例程的入口地址保存在ds:200h、ds:202h單元中
push es:[9*4+2]
pop ds:[202h]
cli
mov word ptr es:[9*4],204h;把新的int 9中斷例程安裝在0:204處
mov word ptr es:[9*4+2],0
sti
call start2
MOV AH,4CH
INT 21H
int9:
push ax
push bx
push cx
push es
in al,60h
pushf ;對應後面的調用原int 9中斷
call dword ptr cs:[200h] ;對int指令進行模擬,調用原來的int 9中斷例程
cmp al,3bh ;F1的掃描碼爲3bh
jne int9ret
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 bx
pop ax
iret
int9end:nop
start2:
mov ax,0b800h
mov es,ax
mov ah,'a'
s0:
mov es:[160*12+40*2],ah
call delay
inc ah
cmp ah,'z'
jna s0
MOV AH,4CH
INT 21H
delay:
push ax
push dx
mov dx,10h ;循環100000次
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
CODES ENDS
END START
運行結果如下(突然鬼畜了起來哈哈哈):
總結
這一章中,我們通過對鍵盤輸入的處理,講解了CPU對外設輸入的通常處理方法。
即:
- 外設的輸入送入端口;
- 向CPU發出外中斷(可屏蔽中斷)信息;
- CPU檢測到可屏蔽中斷信息,如果IF=1,CPU在執行完當前指令後響應中斷,執行相應的中斷例程;
- 可在中斷例程中實現對外設輸入的處理。
端口和中斷機制,是CPU進行I/O的基礎。