8086-6-中斷
中斷就是打斷處理器當前的執行流程,去執行一些和當前工作不相干的指令,執行完之後,還可以返回到原來的程式流程繼續執行。 就好比你在打遊戲突然老闆來電話了,你不得不先停止打遊戲然後來處理這件更為重要的事件,然後打完電話之後繼續打遊戲。
中斷的一些概念:
中斷號:
由於CPU需要通過對不同類型的中斷進行不同處理,所以每種類型的中斷都被統一編號,這稱為中斷類型號、中斷向量或者中斷號。Intel 處理器允許256 個中斷,中斷號的範圍是0~255
中斷源:
中斷訊號的來源,或者說產生中斷的設備,被稱為中斷源。
中斷嵌套:
當一個中斷事件正在處理時,如果來了一個優先順序更高的中斷事件時,允許暫時中止當前的中斷處理,先為優先順序較高的中斷事件 服務,這稱為中斷嵌套。
實模式下的中斷向量表(Interrupt Vector Table,IVT):
所謂中斷處理,其實就是處理器要執行一段與該中斷有關的程式(指令)你也可以將其當作一個函數。處理器可以識別256 個中斷,那麼理論上就需要256 段程式碼。這些程式碼實際存放的位置並不重要,重要的是,在實模式下,處理器要求將它們的入口點也就是起始地址集中存放到記憶體中從物理地址0x00000 開始,到0x003ff 結 束,共1KB 的空間內,這就是所謂的中斷向量表。
每個中斷的入口點地址在中斷向量表中佔2 個字,分別是中斷處理程式碼的偏移地址和段地址。中斷0的入口點位於物理地址0x00000 處, 也就是邏輯地址0x0000:0x0000;中斷1 的入口點位於物理地址0x00004 處,即邏輯地址0x0000:0x0004;其他中斷入口點地址以此類推。
中斷分類:
中斷大致上可以分為硬體中斷和軟體中斷(簡稱為軟中斷)。
顧名思義,硬體中斷由硬體來提供,比如說:CPU,滑鼠鍵盤等。而軟體鍵盤由內部的程式碼來定義。
硬體中斷:
硬體中斷還可以分為外部硬體中斷和內部硬體中斷,外部硬體中斷是指除CPU以外的硬體對應的中斷,而內部硬體中斷是內部CPU對應的中斷。
外部硬體中斷:
外部硬體中斷,就是除處理器以外的外部設備中來的中斷訊號。當外部設備發生錯誤,或者有數據要傳送(比如,從網路中接收到一個針對當前主機的數據包),或者處理器交給它的事情處理完了(比如,列印已經完成),它們都會告訴CPU先停下工作,來臨時處理一下。
外部硬體中斷又可以根據中斷是否緊急而細分:因為有一些嚴重的事情,比如說電池沒電了,這個如果不處理馬上就會關機了,而有一些不是很重要,比如說鍵盤輸入,有時候卡死了為了防止外部硬體繼續倒騰導致系統直接崩潰,可以選擇把這種不是很重要的中斷進行屏蔽掉。所以外部硬體中斷又分為可屏蔽中斷和不可屏蔽中斷。
外部硬體中斷是通過兩個訊號線引入處理器內部的。 從8086 處理器開始,這兩根線的名字就叫NMI 和INTR。
(這是一個簡化的示意圖, 不是真正的設備連接圖。)
當一個外部硬體中斷髮生時,處理器將會從中斷引腳NMI或INTR中得到通知,其中不可屏蔽中斷採用NMI引腳,而可屏蔽中斷採用INTR引腳。
不可屏蔽中斷(Non Maskable Interrupt,NMI):
不可屏蔽中斷是針對一些硬體的很嚴重的事件進行處理。所有的嚴重事件都必須無條件地加以處理,這種類型的中斷是不會被阻斷和屏蔽的,稱為非屏蔽中斷 (Non Maskable Interrupt,NMI)。
在傳統的兼容模式下,NMI 的中斷源通過一個與非門連接到處理器。處理器的NMI 引腳是高電平有效的,而中斷訊號是低電平有效。 當不存在中斷的時候,與非門的所有輸入都為高電平也就是所有中斷源的都沒有發出中斷資訊,因此與非門的結果就是低電平所以處理器的NMI 引 腳為低電平,這CPU沒有從NMI引腳獲得中斷資訊。
當至少有一個不可屏蔽中斷髮生時都會導致與非門的輸出結果為高電平,也就會導致NMI引腳為高電平,而CPU就會接受到NMI,然後進行對應的處理。
(與非門:
由於不可屏蔽中斷的非常特殊性,幾乎所有觸發NMI 的中斷事件對處理器來說都是致命的,甚至是不可糾正的。所以在這種情況下,努力去搞清楚發生了什麼,通常沒有太大的意義,這樣的事最好留到事後, 讓專業維修人員來做。 因此在實模式下,NMI 被賦予了統一的中斷號2,不再進行細分。一旦發生2號中斷,處理器和軟體系統通常會放棄繼續正常工作,也不會糾正已經發生的問題和錯誤,很可能只是由軟體系統給出一個提示資訊。
可屏蔽中斷(Interrupt Request)INTR:
這裡的INTR就不是可屏蔽中斷的簡稱了,而是一個Interrupt request的簡稱。
這類中斷有兩個特點,第一是數量很多,畢竟有很多外部設備;第二是它們可以被屏蔽,這樣處理器就不對它們進行處理。所以,這類硬體中斷稱為可屏蔽中斷。
可屏蔽中斷是通過INTR 引腳進入處理器內部的,像NMI一樣,不可能為每一個中斷源都提供一個引腳。而且,處理器每次只能處理一個中斷。在這種情況下,需要一個代理,來接受外部設備發出的中斷訊號。 還有,多個設備同時發出中斷請求的幾率也是很高的,所以該代理的還包括對它們抉擇到底讓它們中的哪一個優先向處理器提出服務請求。
在個人電腦中,用得最多的中斷代理就是8259晶片,這類硬體就是通常所說的中斷控制器( Interrupt Controller,IC)),從8086 處理器開始,它就一直提供這種服務。即使是現在,在絕大多數單處理器的電腦中,也依然有它的存在。
8559晶片
Intel 處理器允許256 個中斷,中斷號的範圍是0~255,8259 提供其中的15 個,但中斷號並不固定。之所以不固定,是因為設計時,允許軟體根據自己的需要靈活設置中斷號,以防止發生衝突。該中斷控制器晶片有自己的埠號,可以像訪問其他外部設備一樣用in 和 out 指令來改變它的狀態,包括各引腳的中斷號。正是因為這樣,它又叫可編程中斷控制器(Programmable Interrupt Controller,PIC)。
每片8259 只有8 個中斷輸入引腳,在個人電腦上使用它,需要兩塊。如圖所示,第一塊8259 晶片的代理輸出的INT 直接送到處理器的INTR引腳,這是主片(Master);第二塊 8259 晶片的INT輸出送到第一塊的引腳2 上,是從片(Slave),兩塊晶片之間形成級聯(Cascade)關係。
所以兩塊8259 晶片可以向處理器提供15 個中斷訊號。根據需要,這些中斷引腳可以被各種設備使用。
8259 的主片引腳0(IR0)接的是系統定時器/計數器晶片;從片的引腳0(IR0)接的是實時時鐘晶片RTC這兩塊晶片的固定連接即使是在硬體更新換代非常頻繁的今天,也沒有改變。
在8259 晶片內部,有中斷屏蔽暫存器(Interrupt Mask Register, IMR),這是個8 位暫存器,對應著該晶片的8 個中斷輸入引腳,對應的位是0 還是1,決定了從該引腳來的中斷訊號是否能夠通過8259 送往處 理器(0 表示允許,1 表示阻斷)。當外部設備通 過某個引腳送來一個中斷請求訊號時,如果它沒有被IMR 阻斷,那麼, 它可以被送往處理器。
(注,8259 晶片是可編程的,主片的埠號是 0x20 和0x21,從片的埠號是0xa0 和0xa1,可以通過這些埠訪問 8259 晶片,設置它的工作方式,包括IMR 的內容。)
中斷能否被處理,除了要看8259 晶片的選擇外,還需要由處理器來決定。在處理器內部,標誌暫存器有一個標誌位IF,這就是中斷標誌(Interrupt Flag)。當IF 為0 時,所有從處理器INTR 引腳來的中斷訊號都被忽略掉;當其為1 時,處 理器可以接受和響應中斷。 IF 標誌位可以通過兩條指令cli 和sti 來改變。這兩條指令都沒有操作 數,cli(CLear Interrupt flag)用於清除IF 標誌位,sti(SeT Interrupt flag)用於置位IF 標誌為1。
在電腦內部,中斷髮生得非常頻繁,當一個中斷正在處理時,其他中斷也有可能會響應,甚至會有多個中斷同時發生的情況。這就需要中斷控制器了,8259 晶片會記住它們,並按一定的策略決定先為誰服務。總體上來說,中斷的優先順序和引腳是相關的,主片的IR0引腳優先順序最高,IR7引腳最低,從片也是如此。當然,還要考慮到從片是級聯在主片的IR2引腳上。
小結外部硬體中斷:
外部硬體中斷分為可屏蔽和不可屏蔽,對應NMI和INTR兩個引腳,NMI由於很嚴重只要發生了就直接停下就好了,而不可屏蔽中斷由於功能很多需要選擇所以就需要一個中斷控制器來處理,中斷控制器里可以選擇是否響應中斷,而CPU里也有IF標誌位來選擇是否響應中斷。
內部硬體中斷:
內部硬體中斷髮生在處理器內部,是由執行的指令引 起的。比如,當處理器檢測到div 或者idiv 指令的除數為零時,或者除法的結果溢出時,將產生中斷0(0 號中斷),這就是除法錯誤中斷。
內部中斷不受標誌暫存器IF 位的影響,它們的中斷類型是固定的,可以立即轉入相應的處理過程。
軟中斷
軟中斷與硬體中斷無關,是寫好的存放在電腦裡面的中斷,可以類比為一個API函數。
軟中斷是由int 指令引起的中斷處理。中斷號在指令中給出,int 指令的格式如下:
int3 //int3 是斷點中斷指令 int 立即數 into
注意,int3 和int 3是不一樣的,int3是一個特殊的斷點指令,而int 3是中斷向量表中的第四個中斷程式。
into 是溢出中斷指令,機器碼為0xCE,也是單位元組指令。當處理器 執行這條指令時,如果標誌暫存器的OF 位是1,那麼,將產生4 號中 斷。否則,這條指令什麼也不做。
最有名的軟中斷是BIOS 中斷,之所以稱為BIOS 中斷,是因為這些中斷功能是在電腦加電之後,由BIOS 程式執行期間建立起來的。
CPU調用中斷過程:
拿外部硬體中斷來進行舉例。
當中斷髮生時,如果從外部硬體到處理器之間的道路都是暢通的, 那麼,處理器在執行完當前的指令後,會立即為硬體服務。它首先會響應中斷,告訴8259 晶片(中斷管理器)準備著手處理該中斷。接著要求 8259 晶片把中斷號送過來。8259 晶片它會把對應的中斷號告訴處理器,處理器接受到中斷號後就開始處理:
① 保護斷點的現場。首先要將標誌暫存器FLAGS 壓棧,然後清除它的IF 位和TF 位。接著,再將當前的代 碼段暫存器CS 和指令指針暫存器IP 壓棧。
② 執行中斷處理程式。由於處理器已經拿到了中斷號,它將該號碼乘以4(因為每個中斷地址在中斷向量表中佔4 位元組),就得到了該中斷入口點在中斷向量表中的偏移地址。接著,從表中依次取出中斷程式的偏移地址和段地址,並分別傳送到IP 和CS,然後,處理器就開始執行中斷處理程式了。
(由於IF 標誌被清除,在中斷處理過程中,處理器將不再響應硬體中斷。如果希望更高優先順序的中斷嵌套,可以在編寫中斷處理程式 時,適時用sti 指令開放中斷。)
③ 返回到斷點處接著執行。所有中斷處理程式的最後一條指令必須是中斷返回指令iret。這將導致處理器依次從棧中彈出(恢復)IP、CS 和 FLAGS 的原始內容,於是轉到主程式接著執行。
;這裡的iret指令用彙編來描述就是 pop ip pop cs popf
其它的中斷調用過程可以參考外部硬體調用過程,只是少了一個中斷管理器。
為什麼進行中斷處理時,需要先將IF、TF都設置為0?
將IF設置為0的原因主要是因為8086CPU的設計者認為中斷處理程式一般是不需要對其它中斷做出響應的。
因此默認的將IF設置為0,禁止CPU處理可屏蔽中斷。當然,如果有的中斷處理程式確實需要處理可屏蔽中斷,也可以在中斷處理程式中開中斷,將IF重新設置為1。指令sti,設置IF=1;指令cti,設置IF=0。
將TF單步調試功能關閉的原因則是為了避免出現單步中斷的死循環。
試想如果開啟了單步調試功能,那麼在進入中斷處理程式,並執行完第一條指令後,便會引發單步中斷,進而跳轉到單步中斷處理程式中。而在執行了單步中斷處理程式的第一條指令後,又會再度引發單步中斷,這成為了一個死循環。因此,在進入中斷處理程式之前,必須首先把TF置為0,關閉單步調試功能,以避免上述情況產生。
手動編寫軟中斷:
首先明白如何寫中斷。
1:編寫中斷程式
2:將中斷程式保存在記憶體中的某一個位置
3:將中斷程式的入口點保存到中斷向量表裡
4:調用中斷程式。
1 編寫中斷程式:
這裡我採用了很簡單的程式碼,修改顯示卡的第一個字元的內容和顏色:
show: mov ax,0xb800 mov es,ax mov byte es:[0],0x48 mov byte es:[1],0x04 iret
2 將中斷程式保存在記憶體中:
8086CPU的記憶體分布:
我們可以將我們的中斷程式保存到DRAM記憶體條里,我們需要找一塊空的記憶體地址就可以。
這裡我查看了0x10000物理地址往上的地址空間:
發現是空的,我就採用這塊地址空間了。
然後我們需要把中斷程式的硬編碼保存進去。
mov ax,0 mov ds,ax mov ax,0x1000 mov es,ax mov si,show mov di,0 mov cx,showend-show cld rep movsb
3 將中斷程式的入口點保存到中斷向量表裡
首先用bochs虛擬機查看中斷向量表的空餘中斷地址:
我選擇了這裡物理地址為0x180的地方,然後我們將入口點的段地址和偏移地址賦值進去。
mov ax,0x0 mov es,ax mov es:[0x180],word 0x0 mov es:[0x182],word 0x1000 ;//先是偏移地址,再是段地址
4 調用中斷程式
調用軟中斷很簡單,只需要int n就行了,n是你的中斷地址相對於起始中斷地址的一個數組偏移值。
這裡 n = 行數-1 * 4+對於行的起始地址的偏移量 =24*4+0=96
所以就直接調用指令就可以了:
int 96
驗證中斷調用是否成功:
整個彙編程式的源程式碼:
segment myInterrupt vstart=0x7c00 ;將機器碼賦值給0x1000:0的記憶體地址空間 start: mov ax,0 mov ds,ax mov ax,0x1000 mov es,ax mov si,show mov di,0 mov cx,showend-show cld rep movsb ;將中斷入口點地址保存到中斷向量表 mov ax,0x0 mov es,ax mov es:[0x180],word 0x0 mov es:[0x182],word 0x1000 int 96 jmp start show: mov ax,0xb800 mov es,ax mov byte es:[0],0x48 mov byte es:[1],0x04 iret showend: nop times 510-($-$$) db 0 db 0x55,0xaa
將其編譯後寫入硬碟的主引導扇區就可以載入了。
採用bochs 調試虛擬機來調試,首先將程式運行到主引導扇區的地方:
然後查看我們的彙編指令,並給int 96指令打一個斷點:
然後運行到斷點處並查看記憶體是否寫入:
可以看到已經成功寫入記憶體地址為0x10000的地址空間了。
然後查看中斷向量表是否成功寫入:
可以看到記憶體地址 0x180的內容已經更改為 0x10000000,因為intel是採用的小端位元組序,所以我們肯定更改成功了。
然後我們給int 96中斷處理程式的入口地址打一個斷點,並運行查看是否允運行到了我們的中斷處理程式:
可以看到已經運行到我們的中斷處理程式處了,而且此時的物理地址也改為了0x10000(可能有人想問前面的if不是置零了嗎,int3斷點中斷也是中斷啊,為啥這裡就可以中斷嵌套呢,其實很簡單,那個if是針對外部中斷設備的可屏蔽中斷的,我們這個是軟中斷,和它無關。)
然後我們將這個中斷處理程式運行完,並查看顯示器的內容是否修改:
可以看到完美實現。所以我們的程式碼邏輯肯定沒有問題,以上就是一個簡單的軟中斷實現。
小結: