實驗內容
編寫0號中斷處理程序,使得在除法溢出發生時,在屏幕中間顯示字符串"divide error!",然后返回到DOS。
解題
這一章都在介紹中斷,包括中斷的產生、中斷處理程序、中斷向量表、中斷過程、相關指令。
解決本次實驗的前提是將本章的內容理解好,那么在完成這部分(原書第12章-內中斷)之后,開始實驗吧~
分析整個中斷過程
(1)當發生除法溢出的時候,產生0號中斷信息,從而引發中斷。
CPU會完成以下工作:
- 取得中斷類型碼 0
- 標志寄存器入棧,TF、IF 設置為 0
- CS、IP入棧
- (IP) = (0 * 4), (CS) = (0 * 4 + 2)
對於第二步,為社么要將TF、IF設置為0?
在十一小節中王爽老師給出了解答:CPU在執行完一條指令之后,如果檢測到標志寄存器TF位為1,則產生單步中斷,引發中斷過程。單步中斷的中斷類型碼為1,它引發的中斷過程如下:
這也是本問題的關鍵了,反過來想,如果TF不設置為0,很顯然CPU在即將執行中斷處理程序的第一條指令時檢測到了TF為1,就會在執行完該條指令之后繼續進入到單步中斷的過程當中… 就這樣可能無限循環下去了,所以在中斷處理之前必須將TF置為0。
對於IF書中沒有多余解釋,對此筆者搜集到了以下資料:
↑Linux系統中的中斷分類
只看表格中的第二行第一條的處理方式一欄:清除標志寄存器eflags的IF標志可屏蔽中斷,筆者推測IF標志位的功能就是顯示中斷的開(On)關(Off),可以譯為:Interrupt Flag,
(查閱了其他博客也確實是這樣)
IF:中斷允許標志位。它用來控制8086是否允許接收外部中斷請求。若IF=1,8086能響應外部中斷,反之則屏蔽外部中斷;
這么想就能夠串通了,只是用來屏蔽掉其他中斷(處理中斷的過程中關閉中斷功能,確保能夠順利執行完本次中斷,處理完成后再打開中斷)
好了,現在明白CPU在發生中斷之后做的事情了,那么我們需要完成什么呢?
1.相關處理
2.向顯示緩沖區寫入想要顯示的字符串 “divide error!”
3.返回 DOS
按照王爽老師的講解,將這段程序命名為 do0。
但是一個從未碰到過的問題來了:do0程序應該放到哪?
do0應該放到內存中,因為除法溢出隨時可能發生,CPU隨時都可能將CS:IP指向do0的入口,然后執行它。
按理來說我們需要向操作系統申請一塊空間去放置do0程序,但是過多的討論申請內存將偏離問題的主線,所以這里簡單做:直接使用一塊別的程序不會用到的內存區,將do0拷貝到其中就可以了。
在 12.3 中斷向量表 章節中作者講解了中斷向量表的存儲位置(針對8086CPU):
0000:0000 到 0000:03FF 共計 400H (1024字節)個單元中存放了256個中斷,但是實際上系統中要處理的中斷事件沒有達到256個,所以在表中有許多單元是空着的。
根據書中的指示:0000:0200 到 0000:02FF 的256個字節的空間所對應的單元都是空的
因此,可以將do0拷貝到內存0000:0200處
do0程序的放置解決了,接下來就是當發生除法溢出的時候,CPU會取得中斷碼0,然后到(4 * 0) = 0H的地址(0000:0000H)去找中斷處理程序的偏移地址,到(4 * 0 + 2) = 2H的地址找中斷處理程序的段地址。
如下圖:
總結一下將要做的事情:
- 編寫可以顯示“divide error!”的中斷處理程序 do0;
- 將do0拷貝到內存0000:0200處;
- 將do0的入口地址0000:0200 存儲在中斷向量表0號單元中。
程序框架:
安裝do0程序與設置中斷向量表
需要明晰的是:我們編寫的程序在運行時do0處的代碼是不執行的!
我們要做的是將do0這部分代碼放入到之前選擇好的那塊沒有程序會使用的內存空間0000:0200H上,並且設置好中斷向量表中0號單元的內容,這樣CPU就可以在發生除法溢出的時候乖乖地去0號向量表單元取出對應的偏移地址、段地址,然后順利完成中斷處理程序do0的執行。
安裝do0程序
將編寫好的do0程序代碼送入到0000:0200H內存段中,需要使用到 movsb指令,如下(由於csdn沒有比較好展示匯編代碼的方式,這里筆者先放上Notepad++中的截圖方便查看):
上面就是do0程序的安裝過程,其中需要注意的是第18行:
mov cx, offset do0end - offset do0
為了計算do0段的大小,需要額外設置標號 do0end 放在do0程序段結束的位置。
注:如果只是單純的計算出所編寫的do0程序塊大小,然后賦值給cx,這種方式的編程很明顯是不可取的,因為do0稍微改一下可能影響程序段的大小,又得重新計算,所以使用標號法交給編譯器去做這些麻煩的事情吧~
設置中斷向量表
對於0號中斷,我們需要在0000:0000處填上偏移地址,在0000:0004處填上中斷處理程序段地址。
注:對於N號中斷,偏移地址應該填在0000:(N * 4) 字單元中,段地址應該填在0000:(N * 4 + 2) 字單元中。
編寫do0程序
do0程序需要做的:
1.相關處理
2.向顯示緩沖區寫入想要顯示的字符串 “divide error!”
3.返回 DOS
這個相關處理指的是什么?
先別急,看第二條:向顯示緩沖區寫入想要顯示的字符串 “divide error!”
這個我們熟悉吧?不熟悉請點這里-> 在顯示緩沖區內編程完成字符串顯示
這個不難,但是既然是字符串的顯示,字符串的存儲位置也是需要考慮的問題。
想一想:像上圖這樣放在代碼的起始位置行不行?
答案是不行,因為那樣字符串的位置是不夠“安全”的,因為在整個程序(完成拷貝的程序,不是do0程序)結束之后,原來的空間會被釋放(別的程序可能會用到它),那么難免這塊空間會被修改,我們需要一塊在do0程序被執行時裝有字符串的空間,說起來比較抽象,看代碼:
完整代碼
assume cs:code
;編寫0號中斷程序,使得在除法溢出發生時,
;在屏幕中顯示字符串"divide error!"
;然后返回dos
code segment
start:
;首先將中斷處理程序送入到中斷向量地址處
mov ax, cs
mov ds, ax
mov si, offset do0 ;源地址 cs:offset do0
mov ax, 0
mov es, ax
mov di, 200H ;目標地址 0:200H
mov cx, offset do0end - offset do0 ;用標號計算出do0段的大小
cld ;設置si、di遞增
rep movsb
;設置中斷向量表
mov ax, 0
mov es, ax
mov word ptr es:[0 * 4], 200h ;0:4H = 200H
mov word ptr es:[0 * 4 + 2], 0 ;0:0 = 0H
mov ax, 4c00h
int 21h
do0:
jmp short do0start
db "divide error!"
do0start:;中斷程序執行開始處:打印字符串
mov ax, cs
mov ds, ax
mov si, 202H
mov ax, 0b800H
mov es, ax ;找到顯卡位置
mov di, 160 * 12 + 36 * 2 ;顯示在顯卡中間
mov cx, 13 ;一共13個字符,挨個拷貝
mov dh, 11000010B ;顯示 紅底閃爍綠字
s:
mov dl, [si] ;將需要顯示的字符串放到dl
mov es:[di], dl ;輸送到顯卡處
mov es:[di + 1], dh;設置字的屬性
inc si ;往后偏移一個字節
add di, 2;往后偏移一個字
loop s
mov ax, 4c00h
int 21H
do0end:
nop
code ends
end start