第十二章 內中斷
引言
本書主要講解硬體中斷。
12.1 內中斷的產生
12.2 中斷處理程式
12.3 中斷向量表
- 中斷向量表在記憶體中存放,對於8086PC機,中斷向量表指定存放在記憶體地址0處。
- 從記憶體0000:0000到0000:03FF的1024個單元中存放著中斷向量表。為什麼是1024個位元組呢?我們回憶,段地址和偏移地址分別都是16位的,它倆經過組合構成一個20位的實體地址。16位相當於2個位元組,我們要得到實體地址就要分別存放段地址和偏移地址,所以一個實體地址需要4個位元組(也就是4個記憶體單元)來存放,因為中斷向量表中有256箇中斷,一箇中斷對應一個實體地址,所以需要256*4=1024個位元組,即1024個記憶體單元。
- 我們在第五章有講,記憶體0000:0000~0000:03FF,大小為1KB的空間是系統存放中斷處理程式入口地址的中斷向量表。一般情況下,從0000:0200~0000:02FF(這是256個位元組,即256個記憶體單元)的256個位元組的空間所對應的中斷向量表項都是空的,作業系統的其它應用程式都不佔用。
12.4 中斷過程
8086CPU的中斷過程:
- (從中斷資訊中)取得中斷型別碼;
- 標誌暫存器的值入棧(保護標誌位);
- 設定標誌暫存器的第8位 TF 和第9位 IF 的值為 0;
- CS暫存器的內容入棧;
- IP暫存器的內容入棧;
- 從記憶體地址為中斷型別碼*4和中斷型別碼*4+2的兩個字單元中讀取中斷處理程式的入口地址設定IP和CS。
更簡潔的描述中斷過程,如下:
- 取得中斷型別碼N;
- pushf
- TF = 0,IF = 0
- push CS
- push IP
- (IP) = (N*4),(CS) = (N*4+2)
在最後一步完成後,CPU開始執行由程式設計師編寫的中斷處理程式。
動畫演示中斷過程(13:45處)
12.5 中斷處理程式和iret指令
中斷處理程式,常規的步驟:
- 儲存用到的暫存器;
- 處理中斷;
- 恢復用到的暫存器;
- 用 iret 指令返回。
iret 指令的功能用匯編語法描述為:
pop IP
pop CS
popf
iret 通常和硬體自動完成的中斷過程配合使用。可以看到,在中斷過程中,暫存器入棧的順序是標誌暫存器、CS、IP,而 iret 的出棧順序是IP、CS、標誌暫存器,剛好和入棧對應,實現了用執行中斷處理程式前的CPU現場恢復標誌暫存器和CS、IP的工作。iret 指令執行後,CPU回到執行中斷處理程式前的執行點繼續執行程式。
12.6 除法錯誤中斷的處理
除法溢位對應0號中斷。
我們編寫程式 test.asm
測試除法溢位:
assume cs:codesg
codesg segment
start:
mov ax,1000h ;被除數1000
mov bh,1 ;除數1
div bh ;bh決定了該除法為8位除法
;商暫存器 al 為 8 位,儲存無符號數除法所得商範圍 0~255
;1000/1=1000,很明顯在 al 中無法存放,引發除法溢位
codesg ends
end start
div 指令可以做除法。當進行 8 位除法的時候,用 al 儲存結果的商,ah 儲存結果的餘數;進行 16 位除法的時候,用 ax 儲存結果的商,dx 儲存結果的餘數。
test.asm
編譯,連結和Debug截圖:
可以看到引發除法溢位 Divide overflow。
12.7 程式設計處理0號中斷
12.8 安裝
更詳細的程式框架:
assume cs:code
code segment
start:
;設定ds:si指向源地址
;設定es:di指向目的地址
;設定cx為傳輸長度
;設定傳輸方向為正
rep movsb
;設定中斷向量表
mov ax,4c00h
int 21h
do0:
;顯示字串"Welcome to Fishc.com!"
mov ax,4c00h
int 21h
code ends
end start
更明確的的程式:
assume cs:code
code segment
start:
;設定ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;設定es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;設定cx為傳輸長度
mov cx,do0部分程式碼的長度
;設定傳輸方向為正
cld
rep movsb
;設定中斷向量表
mov ax,4c00h
int 21h
do0:
;顯示字串"Welcome to Fishc.com!"
mov ax,4c00h
int 21h
code ends
end start
接著就是計算do0部分程式碼的長度:
assume cs:code
code segment
start:
;設定ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;設定es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;設定cx為傳輸長度
mov cx,offset do0end - offset do0
;設定傳輸方向為正
cld
rep movsb
;設定中斷向量表
mov ax,4c00h
int 21h
do0:
;顯示字串"Welcome to Fishc.com!"
mov ax,4c00h
int 21h
do0end: nop
code ends
end start
接下來是do0程式,do0程式的主要任務是顯示字串,程式如下:
do0:;顯示字串"Welcome to Fishc.com!"
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;設定es:di指向視訊記憶體空間的中間位置
mov cx,21 ;設定cx為字串長度
s: ;把字串中的字元一個一個的拷貝過去
mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end: nop
現在,我們得到了相對完整的彙編程式版本 program1.asm
:
assume cs:code
data segment
db "Welcome to Fishc.com!"
data ends
code segment
start:
;設定ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;設定es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;設定cx為傳輸長度
mov cx,offset do0end - offset do0
;設定傳輸方向為正
cld
rep movsb
;設定中斷向量表
mov ax,4c00h
int 21h
do0:;顯示字串"Welcome to Fishc.com!"
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;設定es:di指向視訊記憶體空間的中間位置
mov cx,21 ;設定cx為字串長度
s: ;把字串中的字元一個一個的拷貝過去
mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end: nop
code ends
end start
程式 program1.asm
看似合理,可實際上卻大錯特錯。
注意,“Welcom to Fishc.com!”在程式 program1
的data段中。程式 program1
執行完成後返回,它所佔用的記憶體空間被系統釋放,而在其中存放的“Welcom to Fishc.com!”也將很可能被別的資訊覆蓋。而do0程式被放到了0000:0200處,隨時都會因發生了除法溢位而被CPU執行,很難保證do0程式從原來程式 program1
所處的空間中取得的是要顯示的字串“Welcom to Fishc.com!”,因為字串“Welcom to Fishc.com!”是存放在資料段的,隨時可能被覆蓋,不是一段安全的記憶體空間。因為 program1
執行完,它的資料段就被釋放了。
故,由於do0程式隨時可能被執行,而它要用到字串“Welcom to Fishc.com!”,所以該字串也應該存放在一段不會被覆蓋的空間中。解決思路是把字串存放到0000:0200處,也就是do0程式在記憶體中的地址0000:0200處。
do0: ;顯示字串"Welcome to Fishc.com!"
jmp short do0start ;執行到此處,CPU直接跳到do0start執行
db "Welcome to Fishc.com!" ;在程式碼段裡存放資料。這算是“歪門邪道”
do0start:
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;設定es:di指向視訊記憶體空間的中間位置
這樣修改後,我們的字串就能跟隨do0儲存在安全空間中,但是問題又來了。do0程式執行過程中必須找到“Welcom to Fishc.com!”,那麼它在哪裡呢?
首先來看段地址,“Welcom to Fishc.com!”和do0的程式碼處於同一個段中,而除法溢位發生時,CS中必然存放do0的段地址,也就是“Welcom to Fishc.com!”的段地址;再來看偏移地址,0000:0200處的指令為 jmp short do0start
,這條指令佔兩個位元組,所以“Welcom to Fishc.com!”的偏移地址為0202h。
最後,我們將do0的入口地址0000:0200寫入中斷向量表的0號表項中,使do0成為0號中斷的中斷處理程式。0號表項的地址為0000:0000,其中0000:0000字單元存放偏移地址;0000:0002字單元存放段地址。
完整程式實現 program2.asm
:
assume cs:code
code segment
start:
;設定ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;設定es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;設定cx為傳輸長度
mov cx,offset do0end - offset do0
;設定傳輸方向為正
cld
rep movsb
;設定中斷向量表
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
do0: ;顯示字串"Welcome to Fishc.com!"
jmp short do0start ;執行到此處,CPU直接跳到do0start執行
db "Welcome to Fishc.com!" ;在程式碼段裡存放資料。這算是“歪門邪道”
do0start:
mov ax,cs
mov ds,ax
mov si,202h ;設定ds:si指向字串
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;設定es:di指向視訊記憶體空間的中間位置
mov cx,21 ;設定cx為字串長度
s: ;把字串中的字元一個一個的拷貝過去
mov al,[si]
mov es:[di],al
inc si
add di,1
mov al,02h ;設定顏色
mov es:[di],al
add di,1
loop s
mov ax,4c00h
int 21h
do0end: nop
code ends
end start
當我們的程式 program2.asm
執行過後,另外一個程式觸發了0號中斷,就會顯示出字串“Welcom to Fishc.com!”,而不是除法溢位提示。
實際演示截圖
在沒有執行
program2.exe
前,debug除法溢位程式test.exe
單步跟蹤提示除法溢位 Divide overflow。編譯、連結program2.asm
。執行
program2.exe
之後,再 debugtest.exe
。
12.9 do0
12.10 設定中斷向量
12.11 單步中斷
標誌暫存器的 TF 位為 1,則CPU產生單步中斷。
CPU 提供單步中斷功能的原因就是,為單步跟蹤的執行過程,提供了實現機制。