補充:事件不僅包含中斷和異常,還包含系統調用,這個屬于用戶主動請求的事件。
上一節,只有一個溢出異常,那么,如果很多異常、中斷呢?(中斷向量表)
另外,之前0號地址只能存儲兩條指令,如果需要更多指令怎么辦?(地址的位置以及對應程序大小應該更靈活)
注意,中斷服務程序包含(保存現場,調用處理方法(主體),恢復現場)
我們在遇到中斷之后,需要執行的步驟,我們簡化一下
- CPU做一些硬件處理工作(識別中斷源,關中斷,當前指令(或下一條指令)地址壓棧,FLAGS寄存器壓棧)
- 找到處理中斷的程序地址(這里的程序是中斷服務程序)
- 執行中斷服務程序(包含保護現場,執行處理程序(這個才是主體部分),恢復現場,返回原程序)
- 繼續執行原有程序
在不同的時代,中斷的處理都是這個過程,只不過每個過程具體的執行發生了變化,且越來越復雜,我們依次看一下。
1 時代1:UNIVAC時代,僅有固定地址中斷處理
在最初的計算機時代,只有很少的內部中斷和外部中斷。
內個時候的中斷服務程序,是固定的地址,并且程序的大小也相對受限。
例如出現了算數溢出,那么就會跳轉到地址0執行中斷服務程序。
不僅如此,這個時候的中斷服務程序幾乎是
- 固定地址
- 固定大小
因此靈活性很差,不過由于很少,所以還好。
到了后來,中斷越來越多,因此就有了中斷向量表,下面看看8086時代吧。
2 時代2:8086時代,實模式 + 中斷向量表
這個時代,中斷已經較多了,管理中斷的方式也更加靈活了。
8086采用的是實模式,也就是說,進入CPU指令中的地址,就是實際的物理地址。
在8086的1MB內存中,有專門的中斷向量表區,從地址0開始的1K字節,它用于存放中斷服務程序的地址,也就是存放CS:IP
。
每對CS:IP
占用4個字節,因此1KB的空間,最多可以支持256種中斷。
CS:IP
,CS在前,IP在后,因此CS在高地址,IP在低地址,又因為是小端模式,因此IP的高字節是高位,低字節是低位;CS同理。
這樣一來,實際的中斷服務程序的位置,是根據這些CS:IP
的值確定的,而它們在內存中是可以修改的,因此
- 中斷服務程序的位置可變
- 程序的大小可改
并且這些中斷服務程序的位置是任意的,只要能夠與中斷向量表對應上即可。
同時,不同的中斷服務程序,位置沒有關聯,放哪里都行。在實模式的8086之下,只需要CS:IP
就可以確定實際內存的位置。
在這個時代,中斷服務程序的位置了大小更加靈活,變成了間接獲取,因為加了一個中斷向量表。
注意,這個時候有5種類型的中斷,并不是5個中斷,這個時候涉及到的內部中斷有4個,外部中斷有1個,而這個外部中斷,是8259A芯片發出的,該芯片外面可以連接很多個外設。
我們來看看這個時代中斷處理的示意圖。
3 時代3:80386時代,保護模式 + 中斷向量表
這個時代就有點復雜了,引入了保護模式,同時增加了一些中斷類型,這也是Linux 0.11內核對應的CPU。
圈住的部分是80386支持的中斷類型,
int0 ~ int16
,其中int15
未定義,可以在手冊中查到。
3.1 保護模式下的尋址方式
首先,保護模式下,依然是CS:EIP
的形式,但是由于EIP已經足夠尋址4GB,因此CS
不再作為位數擴展的功能了,它的功能發生了改變。
在保護模式下,段寄存器依然是16位,它們變成了段選擇子寄存器,先從最簡方式描述地址的生成方法:
- 通過
段選擇子寄存器
找到8字節大小的段描述符
- 根據
段描述符的內容
獲取段基址(32位)
段基址
和EIP
組合(==應該就是相加吧?==基址 + 偏移地址),得到地址- 注意: 目前可以知到這個地址是給CPU看的,它應該是虛擬地址,現在先不管,先當成通過這個地址就能夠訪問到內存的指定位置。
是不是比實模式復雜多了,下面,進一步展開細節。
通過段選擇子寄存器(此后均以CS
舉例說明),怎么找到對應的段描述符?
- 計算機啟動進入實模式
- 填好GDT
- 設置好GDT的初始地址放入GDTR寄存器中
在保護模式下, CS + GDTR
獲取對應的描述符(0~8191),這樣,我們就獲取了代碼段的描述符了。
接下來我們看看這個描述符
- 這個描述符有
8192
個,也就是2^13
,而一個描述符有2^3 = 8
個字節,因此一共占用了2^16
個內存單元,也就是64KB,CS寄存器是16位的,這是它能夠訪問的極限,這樣,通過GDTR基址 + CS偏移
的方式,能夠訪問到每一個描述符表。 - 再看每一個描述符的結構
它有8個字節的大小,其中有四個字節是段基址,就是這個段基址與EIP組合,形成最終的要訪問內存的(虛擬)地址。其他的自己還涉及到權限以及界限,先不管是干啥的。
段描述符的內容是什么,怎么獲取段基址
上面已經說明了。
3.2 保護模式下的中斷操作
上面一小節,經歷了一系列復雜過程,終于說明了保護模式下如何尋址,真的很復雜啊……下面說明一下中斷操作過程。其實最主要說明的還是找中斷服務程序位置這個過程。
在保護模式下,也有一個IDTR中斷描述符表寄存器,還有一個IDT中斷描述符。
這個IDT同樣是支持256種類型的中斷的,每個描述符表項占8個字節,因此一共占用2KB。
IDTR提供的是IDT的基址,然后CPU獲取中斷號之后,根據中斷號 * 8 + IDTR
定位對應的描述符。
對于中斷描述符
- 字節0167四個字節對應的是32位地址,也就是
EIP
的值 - 字節23對應的是
CS
的值 - 有了
CS:EIP
就可以通過上一小節的方式找到對應的地址,從而找到中斷服務程序入口地址
(這個過程其實與CS:IP
類似,只不過麻煩了點)
折騰了這么一大圈,終于找到中斷服務程序了……
3.3 小結
這個太復雜了,我們簡單總結一下吧。
首先,保護模式下的尋址方式更復雜了,引入了全局描述符表,雖然依然是CS:EIP
,但是其計算方式更加復雜了。
其次,保護模式下的中斷處理更復雜了
- 以前固定位置的中斷向量表,變成了任意位置的中斷描述符表
IDT
,通過IDTR
和中斷類型號
計算得到中斷描述符 - 再通過中斷描述符的內容獲取
CS:EIP
,再獲取對應中斷服務程序的入口地址
我們可以看到,模式越來越復雜,間接程度越來越高,設置的自由度提高,安全性也提高了。
思想:太固定死板怎么辦?加個固定的中轉站!通過改變中轉站,來實現靈活地分配目標
這幾個時代的發展過程,可以說中轉站越來越多,越來越復雜,靈活度越來越高。
小結
本篇內容,貫徹的是中斷處理的找中斷服務程序的過程。
可以看到
- UNIVAC時代非常直接地找到地址
- 8086時代提供了固定位置中斷向量表,間接地找到
CS:IP
,尋址方式是直接的 - 80386時代提供了任意位置的中斷描述符表,間接地獲取
CS:EIP
,尋址方式也是間接的,需要通過全局描述符表GDT獲取段基址,從而獲取內存地址
不管怎樣,時代的發展使得根據中斷類型號,找到中斷服務程序這個過程越來越復雜,靈活度也越來越高。
后面會介紹中斷處理的其他過程,最后結合Linux 0.11內核源代碼,使得軟硬件對應上。