【中斷】那么重要,它的本質(zhì)到底是什么?
時間:2021-09-10 16:31:49
手機(jī)看文章
掃描二維碼
隨時隨地手機(jī)看文章
[導(dǎo)讀]在軟件開發(fā)中,中斷是一個繞不開的重要話題,但是,不知道您是否遇到過這樣的困惑:很多書籍、文章在介紹中斷相關(guān)的知識點(diǎn)時,說的都挺有道理。這篇文章對中斷的講解很正確,那篇文章在描述中斷的時候也挺對的,但是,這兩篇文章中,怎么有些內(nèi)容是矛盾的???!單獨(dú)看任何一篇文章感覺都有道理,看的越...
在軟件開發(fā)中,中斷是一個繞不開的重要話題,但是,不知道您是否遇到過這樣的困惑:很多書籍、文章在介紹中斷相關(guān)的知識點(diǎn)時,說的都挺有道理。這篇文章對中斷的講解很正確,那篇文章在描述中斷的時候也挺對的,但是,這兩篇文章中,怎么有些內(nèi)容是矛盾的啊?! 單獨(dú)看任何一篇文章感覺都有道理,看的越多,反而越迷糊?好比在森林里迷路了,如果只有一個指南針,肯定能走出來。但是,如果你有? 只有對這個全局的地圖掌握了,在具體學(xué)習(xí)每一個局部的知識點(diǎn)時,才能知道自己所處的位置在哪里,才不至于走偏。這篇文章,我們繼續(xù)去繁從簡,從? 因此,這篇文章我們講解的就是在實(shí)模式下的中斷,這一點(diǎn)請大家先明白。 對于通過? 剛才已經(jīng)說過, 如果把一個中斷向量看作函數(shù)指針,那么這個中斷向量表就相當(dāng)于是函數(shù)指針數(shù)組。舉例:假設(shè)? 意思是:
- EOF -
2?個指南針,所指的方向卻是相反的,這個時候應(yīng)該相信誰呢?!我們仔細(xì)梳理了一下就會發(fā)現(xiàn):每一篇文章都是在一定的語境、一定的上下文環(huán)境中來講解的,不同文章的矛盾之處,恰恰是它們所描述的那個上下文大環(huán)境不同。上下文環(huán)境,就是描述當(dāng)前正在執(zhí)行的程序相關(guān)的靜態(tài)信息,比如:有哪些代碼段,??臻g在哪里,進(jìn)程描述信息在什么位置,當(dāng)前執(zhí)行到哪一條指令等等。如果我們沒有一個全局的視角,在同一個上下文環(huán)境中來對比不同的文章,就會讓自己的理解和認(rèn)識越來越蒙圈。因此,對于這種概念比較龐雜,無法用某種確定的邏輯來貫穿的知識點(diǎn),在腦袋中一定要有一幅全局的地圖。
8086?這個最簡單的處理器入手,來聊一下關(guān)于中斷的一些知識。有了這個儲備,理清了基本的脈絡(luò)之后,以后再去學(xué)習(xí)?Linux?系統(tǒng)中的中斷相關(guān)內(nèi)容時,才會有原來如此的感覺!中斷向量與中斷描述符
中斷向量這個詞很時髦,也很神秘!按道理,不應(yīng)該在第一部分就端上中斷向量這盤硬菜,應(yīng)該從中斷源開始聊起。但是,畢竟我們已經(jīng)學(xué)習(xí)過那么多關(guān)于中斷的知識了,腦袋中肯定是對中斷已經(jīng)有了一些的基本認(rèn)知。所以,在這里我們還是首先來明確一下中斷向量和中斷描述符這個問題。在前面的文章中已經(jīng)聊過關(guān)于實(shí)模式和保護(hù)模式的問題,在?【Linux 從頭學(xué)】這個系列中,我們一直以來描述的都是實(shí)模式下的事情。本文是實(shí)模式下的最后一篇文章,下一篇文章將會進(jìn)入保護(hù)模式。那么,中斷向量就是工作在實(shí)模式下的,處理器通過中斷號和中斷向量,來定位到相應(yīng)的中斷處理程序。而中斷描述符呢,就是工作在保護(hù)模式下,處理器通過中斷號和中斷描述符,來定位到相應(yīng)的中斷處理程序。也就是說:中斷向量和中斷描述符,它倆的根本作用是一樣的。只是它們存在于不同的大環(huán)境中,而且從描述上也能感覺到,保護(hù)模式下的中斷描述符會更復(fù)雜一些,功能也更強(qiáng)大一些。它倆就像一對兄弟一樣,從外表上看是差不多,功能也是類似。但是透入到內(nèi)部去看,就會發(fā)現(xiàn)有很多的不同之處。
中斷的分類
在?x86?系統(tǒng)中,中斷的分類如下:內(nèi)部中斷
所謂的內(nèi)部中斷,是在?CPU?內(nèi)部產(chǎn)生并進(jìn)行處理的。比如:對于內(nèi)部中斷,有時候也稱之為異常。軟中斷也屬于內(nèi)部中斷,是非常有用的,它是由?
- CPU 遇到一條除以 0 的指令時,將產(chǎn)生 0 號中斷,并調(diào)用相應(yīng)的中斷處理程序;
- CPU 遇到一條不存在的非法指令時,將產(chǎn)生 6 號中斷,并調(diào)用相應(yīng)的中斷處理程序;
int?指令觸發(fā)的。比如?int3?這條指令,gdb?就是利用它來實(shí)現(xiàn)對應(yīng)用程序的調(diào)試。很久之前寫過這樣的一篇文章原來gdb的底層調(diào)試原理這么簡單,其中就描述了?gdb?是如何通過插入一個?int?指令,來替換被調(diào)試程序的指令碼,從而實(shí)現(xiàn)斷點(diǎn)調(diào)試功能的。外部中斷
x86?CPU?上有?2?個中斷引腳:INT?和?INTR,分別對應(yīng):不可屏蔽中斷和可屏蔽中斷。所謂不可屏蔽,就是說:中斷不可以被忽視,CPU?必須處理這個中斷。如果不處理,程序就沒法繼續(xù)執(zhí)行。而對于可屏蔽中斷,CPU?可以忽略它不執(zhí)行,因?yàn)檫@類中斷不會對系統(tǒng)的執(zhí)行造成致命的影響。對于外部的可屏蔽中斷,CPU?上只有一根?INTR?引腳,但是需要產(chǎn)生中斷信號的設(shè)備那么多,如何對眾多的中斷信號進(jìn)行區(qū)分呢?一般都是通過可編程中斷控制器(Programmable Interrupt Controller, PIC),在計(jì)算機(jī)中使用最多的就是?8259a?芯片。雖然現(xiàn)代計(jì)算機(jī)都已經(jīng)是?APIC(高級可編程中斷控制器) 了,但是由于?8259a?芯片是那么的經(jīng)典,大部分描述外部中斷的文章都會用它來舉例。每一片?8259a?可以提供?8?個中斷輸入引腳,兩片芯片級聯(lián)在一起,就可以提供?15?個中斷信號:這樣的話,兩片?
- 主片的輸出引腳 INT 連接到 CPU 的 INTR 引腳上;
- 從片的輸出引腳 INT 連接到主片的引腳 2 上;
8259a?芯片就可以向?CPU?提供?15?個中斷信號了,比如:鼠標(biāo)、鍵盤、串口、硬盤等等外設(shè)。1. 8259a 之所以稱作可編程,是因?yàn)樗膬?nèi)部有相關(guān)的寄存器。2. 可以通過指定的端口號,對這些寄存器進(jìn)行設(shè)置,讓 8 根 IRQ 中斷線上的信號,在送到 CPU 時,對應(yīng)不同的中斷號。另外,對于外部可屏蔽中斷,有?
2?層的屏蔽機(jī)制:
- 在 8259 芯片中,有中斷屏蔽寄存器,可以對 IRQ0 ~ IRQ7 輸入引腳進(jìn)行屏蔽;
- 在 CPU 內(nèi)部,也有一個標(biāo)志寄存器,可以對某一類中斷信號進(jìn)行屏蔽;
中斷號
在?x86?處理器中,一共支持?256?個中斷,每一個中斷都分配了一個中斷號,從?0?到?255。其中,0 ~ 31?號中斷向量被保留,用來處理異常和非屏蔽中斷(其中只有?2?號向量用于非屏蔽中斷,其余全部是異常)。當(dāng)?BIOS?或者操作系統(tǒng)提供了異常處理程序之后,當(dāng)一個異常產(chǎn)生時,就會通過中斷向量表找到響應(yīng)的異常處理程序,查找的過程馬上就會介紹到。從中斷號?32?開始,全部分配給外部中斷。比如:1. 系統(tǒng)定時器中斷 IRQ0,分配的就是 32 號中斷;2. Linux 的系統(tǒng)調(diào)用,分配的就是 128 號中斷;我們來分別看一下內(nèi)部中斷和外部中斷相關(guān)的中斷號:
8259a?可編程中斷控制器接入的中斷信號分配如下圖所示:8259a?是可編程的,假如我們通過配置寄存器,把?IRQ0?的中斷號設(shè)置為?32, 那么主片上?IRQ1 ~ IRQ7?所對應(yīng)的中斷號依次加?1,從片上?IRQ8~IRQ15?對應(yīng)的中斷號也是依次遞增。所以,有時候我們可以在代碼中斷看到下面的宏定義:中斷向量和中斷處理程序
當(dāng)一個中斷發(fā)生的時候,CPU?獲取到該中斷對應(yīng)的中斷號,下一步就是要確定調(diào)用哪一個函數(shù)來處理這個中斷,這個函數(shù)就稱作中斷服務(wù)程序(Interrupt Service Routine,ISR),有時候也稱作中斷處理程序、中斷處理函數(shù),本質(zhì)都一樣。中斷向,就是通過中斷號去查找處理程序的重要的橋梁!中斷向量的本質(zhì)
在?8086?中,一個中斷向量,就是一個?段地址:中斷處理函數(shù)偏移量?這樣的一對數(shù)據(jù),通過這個數(shù)據(jù),就可以定位到內(nèi)存中指定位置的那個中斷處理函數(shù)。非常類似于高級編程語言中的函數(shù)指針,就是用來指向一個函數(shù)的開始地址。8086?規(guī)定:256?個中斷向量,必須從內(nèi)存的?0?地址處開始存放。每一個中斷向量占用?4?個字節(jié)(2?個字節(jié)的段地址,2?個字節(jié)的偏移地址),256?個中斷一共占用了?1024?個字節(jié)的空間。之前的文章中,已經(jīng)介紹過相關(guān)的內(nèi)存模型,如下圖所示:2?號中斷被觸發(fā)了,CPU?就會到中斷向量表中查找?2?號中斷的中斷向量。因?yàn)槊恳粋€中斷向量占據(jù)?4?個字節(jié),那么?2?號中斷向量的開始地址就是?2 * 4 = 8,第?8?個字節(jié)。然后在第?8?個字節(jié)開始,取?4?個字節(jié)的內(nèi)容:0x1000:0x2000。2?號中斷的處理函數(shù),在段地址為 0x1000,偏移量為 0x2000 的位置處。那么?CPU?就按照?8086?的物理地址計(jì)算方式,得到中斷處理函數(shù)的物理地址為?0x12000?(段地址左移?4?位 偏移地址),于是就跳轉(zhuǎn)到該函數(shù)地址處去執(zhí)行。1. 由于 Linux 系統(tǒng)是運(yùn)行在保護(hù)模式,在這個模式下,當(dāng)發(fā)生中斷時,是通過中斷描述符來查找中斷處理函數(shù)的。2. 每一個中斷描述符,描述了一個中斷處理函數(shù)所在段的選擇子和偏移量,本質(zhì)上也是用來查找一個中斷處理函數(shù)。
中斷處理程序的安裝
既然通過中斷向量,找到了中斷處理程序,那么這些中斷處理程序都是誰放在內(nèi)存中的呢?如果您看過一些比較底層的計(jì)算機(jī)書籍,就能看到一般都會舉例:如何手動的把一個普通函數(shù)設(shè)置為一個中斷處理函數(shù)。操作步驟是:此時,如果發(fā)生了該中斷,你所提供的函數(shù)就作為中斷處理函數(shù)被執(zhí)行了。當(dāng)然了,在一個計(jì)算機(jī)系統(tǒng)中,
- 在代碼中,寫一個普通函數(shù);
- 把這個函數(shù)的指令碼,搬運(yùn)到內(nèi)存中的某一個位置;
- 把這個位置(段地址:偏移量),作為一個中斷向量,設(shè)置到中斷向量表中;
BIOS、操作系統(tǒng)和各種外設(shè),會自動為我們提供很多基本的中斷處理函數(shù)的。比如:BIOS?中就提供了軟中斷、內(nèi)部中斷、硬件中斷等處理函數(shù),這些函數(shù)是固化在?BIOS?的代碼中的(映射到?BIOS?所在的?ROM?芯片上),BIOS?只需要把這些處理函數(shù)的地址,寫入到中斷向量表中的相應(yīng)位置即可。在之前的文章中提到過,內(nèi)存中的某些位置是映射到外設(shè)的?ROM,在這些外設(shè)的?ROM?中也存在一些外設(shè)自帶的程序。BIOS?在啟動時,會掃描這些映射到外設(shè)的內(nèi)存空間,通過某些關(guān)鍵字信息,如果發(fā)現(xiàn)外設(shè)有自帶的程序,就會去執(zhí)行。這些外設(shè)程序一般是進(jìn)行一些自身的初始化,并填寫相關(guān)的中斷向量表,使它們指向外設(shè)自帶的中斷處理程序。對于操作系統(tǒng)來說就更不用說了,它會重新安排自己需要的中斷處理函數(shù),這部分內(nèi)容我們以后再一起學(xué)習(xí)、討論!中斷現(xiàn)場的保護(hù)和恢復(fù)
當(dāng)一個中斷發(fā)生的時候,肯定有一個正在執(zhí)行的程序被打斷。當(dāng)中斷處理函數(shù)執(zhí)行結(jié)束之后,這個被打斷的程序需要從剛才被打斷的地方繼續(xù)執(zhí)行(暫時先不要考慮從中斷返回點(diǎn),進(jìn)行多任務(wù)切換的事情)。而一個程序執(zhí)行的上下文環(huán)境,就是處理器中的各種寄存器內(nèi)容:代碼段寄存器?cs,指令指針寄存器?sp,標(biāo)志寄存器?FLAGS。但是,在中斷處理程序中,也需要使用這些寄存器。處理器中的這些寄存器,就是每一個程序執(zhí)行時上下文信息的存儲容器,當(dāng)然也包括終端處理程序!因此,在進(jìn)入中斷處理程序之前,CPU?會自動的把這些寄存器?push?到棧中保存起來,然后再跳轉(zhuǎn)到中斷處理程序中去執(zhí)行。當(dāng)中斷處理程序執(zhí)行結(jié)束后,CPU?會從棧中彈出這些內(nèi)容,恢復(fù)到相應(yīng)的寄存器中,于是被打斷的程序就可以繼續(xù)執(zhí)行了。總結(jié):中斷的本質(zhì)
從功能的角度看,中斷有?2?個作用:關(guān)于第?
- 提供執(zhí)行異步序列的機(jī)制;
- 給應(yīng)用程序提供進(jìn)入系統(tǒng)層的入口;
2?點(diǎn),以后在介紹到?Linux?中的?int 0x80?中斷就非常清楚了,也就是通過中斷,讓應(yīng)用層的程序有機(jī)會進(jìn)入到系統(tǒng)代碼中去執(zhí)行。因?yàn)閼?yīng)用層與操作系統(tǒng)層的代碼,是工作在不同的安全級別。為了系統(tǒng)的安全,Linux?操作系統(tǒng)提供了這樣的一個機(jī)制,讓低安全級別的應(yīng)用程序,進(jìn)入到高安全級別的操作系統(tǒng)代碼中去執(zhí)行,畢竟所有的硬件等系統(tǒng)資源都是由操作系統(tǒng)來統(tǒng)一管理的。我們再從中斷處理程序的安裝角度來看,中斷本質(zhì)上就是增加了一層間接性:通過固定位置的中斷向量表,讓中斷處理函數(shù)的實(shí)際地址可以被動態(tài)的放在任意位置。為什么這么做?假如操作系統(tǒng)想為某一個中斷提供處理函數(shù),那么這個處理函數(shù)的地址放在內(nèi)存中的什么位置比較合適?需要考慮?CPU, 內(nèi)存大小和布局等多種因素,非常復(fù)雜!而通過使用中斷向量表,就在一個固定位置處存放了很多個“指針”。當(dāng)中斷處理函數(shù)放在內(nèi)存中某個任意位置之后,讓“指針”指向這個函數(shù)的地址就可以了,從而達(dá)到解耦的目的。這樣的話,無論是發(fā)生硬件中斷,還是應(yīng)用層代碼通過中斷門來調(diào)用操作系統(tǒng)提供的函數(shù),只要觸發(fā)相應(yīng)的中斷就可以了,簡化了?CPU?的設(shè)計(jì)。- EOF -





