INTERRUPT
中斷是硬件和軟件交互的一種機(jī)制,可以說(shuō)整個(gè)操作系統(tǒng),整個(gè)架構(gòu)都是由中斷來(lái)驅(qū)動(dòng)的。中斷的機(jī)制分為兩種,中斷和異常,中斷通常為
設(shè)備觸發(fā)的異步事件,而異常是
執(zhí)行指令時(shí)發(fā)生的同步事件。本文主要來(lái)說(shuō)明
外設(shè)觸發(fā)的中斷,總的來(lái)說(shuō)
一個(gè)中斷的起末會(huì)經(jīng)歷設(shè)備,中斷控制器,CPU 三個(gè)階段:設(shè)備產(chǎn)生中斷信號(hào),中斷控制器翻譯信號(hào),CPU 來(lái)實(shí)際處理信號(hào)。本文用
的實(shí)例來(lái)講解
多處理器下的
中斷機(jī)制,從頭至尾的來(lái)看一看,中斷經(jīng)歷的三個(gè)過(guò)程。其中第一個(gè)階段設(shè)備如何產(chǎn)生信號(hào)不講,超過(guò)了操作系統(tǒng)的范圍,也超過(guò)了我的能力范圍。各種硬件外設(shè)有著自己的執(zhí)行邏輯,有各種形式的中斷觸發(fā)機(jī)制,比如邊沿觸發(fā),電平觸發(fā)等等??偟膩?lái)說(shuō)就是向中斷控制器發(fā)送一個(gè)中斷信號(hào),中斷控制器再作翻譯發(fā)送給
,
再執(zhí)行中斷服務(wù)程序?qū)χ袛噙M(jìn)行處理。
中斷控制器
說(shuō)到中斷控制器,是個(gè)什么東西?中斷控制器可以看作是中斷的代理,外設(shè)是很多的,如果沒(méi)有一個(gè)中斷代理,外設(shè)想要給
發(fā)送中斷信號(hào)來(lái)處理中斷,那只能是外設(shè)連接在
的管腳上,
的管腳是很寶貴的,不可能拿出那么多管腳去連接外設(shè)。所以就有了中斷控制器這個(gè)中斷代言人,所有的
外設(shè)連接其上,發(fā)送中斷請(qǐng)求時(shí)就向中斷控制器發(fā)送信號(hào),中斷控制器再通知 CPU,如此便解決了上述問(wèn)題。中斷控制器有很多,前文講過(guò)
PIC
,
PIC
只用于單處理器,對(duì)于如今的多核
多處理器時(shí)代,
PIC
無(wú)能為力,所以出現(xiàn)了更高級(jí)的中斷控制器
APIC
,
APIC
(
) 高級(jí)可編程中斷控制器,
APIC
分成兩部分 LAPIC
和 IOAPIC
,前者 LAPIC
位于 內(nèi)部,每個(gè) 都有一個(gè) LAPIC
,后者 IOAPIC
與外設(shè)相連。外設(shè)發(fā)出的中斷信號(hào)經(jīng)過(guò)
IOAPIC
處理之后發(fā)送給
LAPIC
,再由
LAPIC
決定是否交由
進(jìn)行實(shí)際的中斷處理。
可以看出每個(gè)
上有一個(gè)
LAPIC
,
IOAPIC
是系統(tǒng)芯片組一部分,各個(gè)中斷消息通過(guò)總線發(fā)送接收。關(guān)于
APIC
的內(nèi)容很多也很復(fù)雜,詳細(xì)描述的可以參考
開(kāi)發(fā)手冊(cè)卷三,本文不探討其中的細(xì)節(jié),只在上層較為抽象的層面講述,理清
APIC
模式下中斷的過(guò)程。計(jì)算機(jī)啟動(dòng)的時(shí)候要先對(duì)
APIC
進(jìn)行初始化,后續(xù)才能正確使用,前面說(shuō)過(guò)初始化就是設(shè)置一些寄存器,這部分我在再談中斷(APIC)有所講解,本文關(guān)于寄存器這一塊不會(huì)再詳述,可以先看一看。下面來(lái)看看
APIC
在一種較為簡(jiǎn)單的工作模式下的初始化過(guò)程:
IOAPIC
初始化 IOAPIC
就是設(shè)置 IOAPIC
的寄存器,
IOAPIC
寄存器一覽:
所以有了以下定義:
#define?REG_ID?????0x00??//?Register?index:?ID
#define?REG_VER????0x01??//?Register?index:?version
#define?REG_TABLE??0x10??//?Redirection?table?base??重定向表
但是這些寄存器是不能直接訪問(wèn)的,需要通過(guò)另外兩個(gè)映射到內(nèi)存的寄存器來(lái)讀寫上述的寄存器。
內(nèi)存映射的兩個(gè)寄存器
這兩個(gè)寄存器是內(nèi)存映射的,
IOREGSEL
,地址為
;
IOWIN
,地址為
。
IOREGSEL
用來(lái)指定要讀寫的寄存器,然后從
IOWIN
中讀寫。也就是常說(shuō)的
index/data
訪問(wèn)方式,或者說(shuō)
,用
index
端口指定寄存器,從
data
端口讀寫寄存器,
data
端口就像是所有寄存器的窗口。
而所謂內(nèi)存映射
,就是把這些寄存器看作內(nèi)存的一部分,讀寫內(nèi)存,就是讀寫寄存器,可以用訪問(wèn)內(nèi)存的指令比如 mov
來(lái)訪問(wèn)寄存器。還有一種是 IO端口映射
,這種映射方式是將外設(shè)的 IO端口(外設(shè)的一些寄存器)
看成一個(gè)獨(dú)立的地址空間,訪問(wèn)這片空間不能用訪問(wèn)內(nèi)存的指令,而需要專門的 in/out
指令來(lái)訪問(wèn)。通過(guò)
IOREGSEL
和
IOWIN
既可以訪問(wèn)到
IOAPIC
所有的寄存器,所以結(jié)構(gòu)體
如下定義:
struct?ioapic?{
??uint?reg;???????//IOREGSEL
??uint?pad[3];????//填充12字節(jié)
??uint?data;??????//IOWIN
};
填充
字節(jié)是因?yàn)?
IOREGSEL
在
,長(zhǎng)度為 4 字節(jié),
IOWIN
在
,兩者中間差了
2 字節(jié),所以填充
字節(jié)補(bǔ)上空位方便操作。通過(guò)
IOREGSEL
?選定寄存器,然后從
IOWIN
中讀寫相應(yīng)寄存器,因此也能明白下面兩個(gè)讀寫函數(shù):
static?uint?ioapicread(int?reg)
{
??ioapic->reg?=?reg;????//選定寄存器reg
??return?ioapic->data;??//從窗口寄存器中讀出寄存器reg數(shù)據(jù)
}
static?void?ioapicwrite(int?reg,?uint?data)
{
??ioapic->reg?=?reg;????//選定寄存器reg
??ioapic->data?=?data;??//向窗口寄存器寫就相當(dāng)于向寄存器reg寫
}
這兩個(gè)函數(shù)就是根據(jù)
來(lái)讀寫
IOAPIC
的寄存器。下面來(lái)看看
IOAPIC
寄存器分別有些什么意義,了解了之后自然就知道為什么要這樣那樣的初始化了。下面只說(shuō)
中涉及到的寄存器,其他的有興趣見(jiàn)文末鏈接。
IOAPIC 寄存器
ID RegisterVersion Register- 索引為 1
- 表示版本,
- 表示重定向表項(xiàng)最多有幾個(gè),這里就是 23(從 0 開(kāi)始計(jì)數(shù))
重定向表項(xiàng)IOAPIC
有 24 個(gè)管腳,每個(gè)管腳都對(duì)應(yīng)著一個(gè) 64 位的重定向表項(xiàng)(也相當(dāng)于 64 位的寄存器),保存在
,重定向表項(xiàng)的格式如下所示:
這是
大佬在他的
中總結(jié)出來(lái)的,很全面也很復(fù)雜,這里有所了解就好,配合著下面的初始化代碼對(duì)部分字段作相應(yīng)的解釋。
IOAPIC 初始化
#define?IOAPIC??0xFEC00000???//?Default?physical?address?of?IO?APIC
void?ioapicinit(void)
{
??int?i,?id,?maxintr;
??ioapic?=?(volatile?struct?ioapic*)IOAPIC;??????//IOREGSEL的地址
??maxintr?=?(ioapicread(REG_VER)?>>?16)?