X86的中斷管理的主要內容也是以Intel軟件開發手冊作爲參考,但是遺憾的是沒有程序代碼可以編寫用於實現我們的理解,當然在實驗3以及之後的內容中就會涉及到。本節我們將從兩個部分來描述中斷——8086中斷與32位保護模式下的中斷,對於前者我們已經在前面的實驗得到很好的使用與介紹,所以我們只需要簡單的介紹之,而對於保護模式下的中斷纔是我們介紹的重點。
爲了詳實而具體介紹本文內容,我們先複習一下一些重要的概念,如下:
1.中斷源——能夠引起中斷原因或提出中斷請求的設備和異常故障;可以分爲外部中斷與內部中斷
2.中斷模式——對於外部中斷來說,連接到處理器中斷線上的觸發方式——沿觸發或者電平觸發。以及可屏蔽中斷與不可屏蔽中斷。
3.中斷優先級——如果在響應一箇中斷,執行中斷處理的過程中,又有新的中斷事件發生而發出了中斷請求,應該如何處理也取決於中斷事件的優先級
4.中斷號——每一箇中斷源都對應一箇中斷號,用於識別不同的中斷事件
5.中斷屏蔽——通過設置相應的中斷屏蔽位,禁止響應某個中斷
6.中斷服務——對中斷的響應的程序
一)8086中斷
當系統啓動時,bios接管了整個系統的控制,處理器處於8086的狀態;而bios對外(指的是對基於bios運行的程序)提供接口的主要方式就是中斷,基本上能夠使用系統所有的服務。而8086對中斷的處理是很簡單的,對於每個中斷源對應一箇中斷號,每個中斷號有一組中斷向量(cs:ip)對應着每個中斷服務的入口(存在於內存的最低1024個字節中)。詳細的終端服務表如下:
INT 00 - CPU-generated - DIVIDE ERROR(除0)
INT 01 - CPU-generated - SINGLE STEP; (80386+) - DEBUGGING EXCEPTIONS(單步調試)
INT 02 - external hardware - NON-MASKABLE INTERRUPT(外部中斷,不可屏蔽中斷)
INT 03 - CPU-generated - BREAKPOINT(斷點)
INT 04 - CPU-generated - INTO DETECTED OVERFLOW(算術溢出)
INT 05 - PRINT SCREEN; CPU-generated (80186+) - BOUND RANGE EXCEEDED(訪問越界)
INT 06 - CPU-generated (80286+) - INVALID OPCODE(非法操作)
INT 07 - CPU-generated (80286+) - PROCESSOR EXTENSION NOT AVAILABLE(協處理器訪問不到)
INT 08 - IRQ0 - SYSTEM TIMER; CPU-generated (80286+)( 系統時鐘)
INT 09 - IRQ1 - KEYBOARD DATA READY; CPU-generated (80286,80386)(鍵盤數據ok)
INT 0A - IRQ2 - LPT2/EGA,VGA/IRQ9; CPU-generated (80286+)(EGA/VGA的中斷)
INT 0B - IRQ3 - SERIAL COMMUNICATIONS (COM2); CPU-generated (80286+)(串口2)
INT 0C - IRQ4 - SERIAL COMMUNICATIONS (COM1); CPU-generated (80286+)(串口1)
INT 0D - IRQ5 - FIXED DISK/LPT2/reserved; CPU-generated (80286+)(一般保護異常)
INT 0E - IRQ6 - DISKETTE CONTROLLER; CPU-generated (80386+)(軟盤控制器)
INT 0F - IRQ7 - PARALLEL PRINTER(並口打印)
INT 10 - VIDEO; CPU-generated (80286+)(視頻)
INT 11 - BIOS - GET EQUIPMENT LIST; CPU-generated (80486+)(對齊檢測)
INT 12 - BIOS - GET MEMORY SIZE(查看系統內存)
INT 13 - DISK(磁盤)
INT 14 - SERIAL(串口)
INT 15 - CASSETTE
INT 16 - KEYBOARD(鍵盤)
INT 17 - PRINTER(打印機)
INT 18 - DISKLESS BOOT HOOK (START CASSETTE BASIC)
INT 19 - SYSTEM - BOOTSTRAP LOADER
INT 1A - TIME(時鐘)
INT 1B - KEYBOARD - CONTROL-BREAK HANDLER
INT 1C - TIME - SYSTEM TIMER TICK(系統時鐘)
INT 1D - SYSTEM DATA - VIDEO PARAMETER TABLES
INT 1E - SYSTEM DATA - DISKETTE PARAMETERS
INT 1F - SYSTEM DATA - 8x8 GRAPHICS FONT
INT 20 - DOS 1+ - TERMINATE PROGRAM
如上只是描述了一些常用的,其他的見附件的資源。對於我們瞭解上表的原因是爲了對比32位保護模式下的中斷處理。
對於中斷向量我們可以從qemu的bios中dump出來(dump binary int 0 1024),然後用hexdump -x打開得到下圖,當中斷髮生就會跳入到設置cs:ip地址執行代碼,當然也可以通過寄存器來傳遞若干參數:
中斷優先級:可以參考32位保護模式下的優先級。
中斷屏蔽:處理器中斷的屏蔽位——EFLAGS.IF位。對於外部中斷,由外部中斷控制器的寄存器來屏蔽對應的中斷。處理中斷的指令:
CLI(關中斷), STI(開中斷), PUSHF與 POPF(通過堆棧修改EFLAGS.iF), IRET(中斷返回)
當介紹完了中斷概念在8086的模式下的實現後,我們還要提的是intel工程師寫的bios接口的函數(在linux源碼中找到arch/x86/boot/bioscall.S):
輸入:int_no爲中斷號
ireg爲中斷髮生前所有寄存器的指針,用於傳遞中斷參數
oreg爲中斷髮生後的所有寄存器的指針,用於獲取中斷結果
void intcall(u8 int_no, const struct biosregs *ireg, struct biosregs *oreg);//實現的功能爲c語言程序代碼提供bios的應用接口(中斷)。
它的具體實現是以彙編代碼形式實現,詳細如下,當然用了巧妙的處理方式(因爲int指令之後只能跟常數表示中斷號):
/*
* "Glove box" for BIOS calls. Avoids the constant problems with BIOSes
* touching registers they shouldn't be.
*/
/*
*arg1-->eax--中斷號
*arg2-->edx--輸入的寄存器組指針
*arg3-->ecx--輸出的寄存器組指針
*/
.code16
.section ".inittext","ax"
.globl intcall
.type intcall, @function
intcall:
/* Self-modify the INT instruction. Ugly, but works. */
cmpb %al, 3f
je 1f
movb %al, 3f/*中斷號通過寄存器EAX傳遞過來,修改int指令的操作數*/
jmp 1f /* Synchronize pipeline */
1:
/* Save state,注意這個保存的過程跟我們定義的struct biosregs是一致的 */
pushfl
pushw %fs
pushw %gs
pushal
/* Copy input state to stack frame ,拷貝我們傳遞的參數到棧頂*/
subw $44, %sp
movw %dx, %si
movw %sp, %di
movw $11, %cx
rep; movsd
/* Pop full state from the stack,將傳入的參數導入到所有寄存器中 */
popal
popw %gs
popw %fs
popw %es
popw %ds
popfl
/* Actual INT ,執行對應中斷*/
.byte 0xcd /* INT opcode */
3: .byte 0
/* Push full state to the stack ,再將所有中斷之後的狀態保存到堆棧中*/
pushfl
pushw %ds
pushw %es
pushw %fs
pushw %gs
pushal
/* Re-establish C environment invariants,重新創建c語言執行環境,因爲中斷有可能將運行狀態給破壞了 */
cld
movzwl %sp, %esp
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
/* Copy output state from stack frame ,拷貝中斷之後的狀態到輸出寄存器組地址*/
movw 68(%esp), %di /* Original %cx == 3rd argument */
andw %di, %di
jz 4f
movw %sp, %si
movw $11, %cx
rep; movsd
4: addw $44, %sp
/* Restore state and return,返回程序*/
popal
popw %gs
popw %fs
popfl
retl
.size intcall, .-intcall
如以上的流程分析,可以看出堆棧在數據保存與傳遞過程中起了很核心作用。在用戶進程的實現與調度過程中有很多使用,所以需要對過程有很好的理解。我們再從這個函數的功能來分析一下,它爲c語言提供了調用bios服務的接口;所以這對我們理解整個系統有了一個基本而核心的概念。系統的服務是通過中斷來提供,而程序訪問系統服務,需要通過寄存器與堆棧調用中斷來實現。這對於我們操作系統的實現的啓示是,應用程序訪問操作系統的服務也是通過中斷的。
另外結構體struct biosregs 定義如下:
struct biosregs {
union {
struct {
u32 edi;
u32 esi;
u32 ebp;
u32 _esp;
u32 ebx;
u32 edx;
u32 ecx;
u32 eax;
u32 _fsgs;
u32 _dses;
u32 eflags;
};
struct {
u16 di, hdi;
u16 si, hsi;
u16 bp, hbp;
u16 _sp, _hsp;
u16 bx, hbx;
u16 dx, hdx;
u16 cx, hcx;
u16 ax, hax;
u16 gs, fs;
u16 es, ds;
u16 flags, hflags;
};
struct {
u8 dil, dih, edi2, edi3;
u8 sil, sih, esi2, esi3;
u8 bpl, bph, ebp2, ebp3;
u8 _spl, _sph, _esp2, _esp3;
u8 bl, bh, ebx2, ebx3;
u8 dl, dh, edx2, edx3;
u8 cl, ch, ecx2, ecx3;
u8 al, ah, eax2, eax3;
};
};
};
如上實現的好處是,通過聯合的方式,簡單的進行整數的分割而不使用位操作。注意寄存器的順序一定要根據如下順序(PUSHF)來EAX, ECX, EDX, EBX, ESP (original value), EBP, ESI, and EDI。
二)32位保護模式下的中斷與異常——詳情見Intel開發手冊第6章
32位保護模式中斷與異常的概念:
1.中斷:在程序運行中隨機出現,主要是對硬件事件的響應或者軟件通過INT指令調用
2.異常:在程序運行過程中處理器檢測到的程序錯誤。它根據不同的嚴重程度主要分爲故障,陷阱,中止。
中斷與異常源:詳情見下表,它的主要來源於外部硬件中斷,程序運行錯誤,硬件檢測,程序中斷指令:
外部硬件中斷被“local APIC”所管理控制。當local APIC被禁用時,INTR中斷——系統從外部中斷控制器讀取中斷向量(比如:8259A),NMI——被用於中斷向量2描述。對於軟件指令執行的中斷,不受EFLAGS.IF影響。
在上表中有Error Code的一列,主要用於反應在產生中斷時,所發生的錯誤信息,它會被放到當前堆棧上。
中斷號:對於0-31號被處理器所使用,外部中斷使用32-255的號。
中斷優先級,詳情見下表:
中斷服務:對於中斷服務的獲取,一般需要設置中斷服務進入點,然後調用中斷流程。
1.設置服務進入點——IDT(中斷描述表,類似GDT),基本結構如下:
如上圖所示,IDT由中斷描述寄存器(IDTR)指向,加載與讀取它的指令爲LIDT/SIDT.
對於每個IDT門(Gate)的描述具體如下圖示:
如上圖所示:IDT門可以分爲3類Task Gate,Trap Gate,Interrupt Gate。
1.調用中斷流程——主要有兩個過程:其一爲調用中斷進入點程序,其二爲堆棧現場保存。
A)調用中斷進入點程序:
當中斷髮生之後,系統會根據配置的中斷向量訪問IDT,然後通過IDT門中的段選擇符,從GDT或者LDT中讀取程序的基地址,最後與IDT門設置的偏移量(offset)相加得到中斷程序進入點。
B)堆棧現場保存:
當中斷髮生在同一個執行指令權限時,不需要進行堆棧切換,直接將中斷髮生時的EFLAGS,CS,EIP,ErrorCode依次壓入堆棧,保存運行的現場,當然也是爲了IRET返回做好準備。
當中斷髮生在不同的執行指令權限時,需要進行堆棧切換——首先需要選擇執行堆棧(切換之後的堆棧),該堆棧是由第0個TSS(任務狀態段)段獲得,然後將之前的堆棧(SS,ESP)壓入執行堆棧,最後再將EFLAGS,CS,EIP,ErrorCode依次壓入堆棧。而在此時執行IRET時,也需要切換堆棧,而且也能在不同的指令執行權限中跳轉。操作系統一般使用兩個執行權限——0與3,內核部分運行在0級,而用戶進程運行在3級。從0級切換到3級,都是使用IRET進行,而由3級到0級就由中斷執行。
當我們在理解中斷內容時會有另外一個概念會值得我們理解——任務管理,所以我們也需要介紹與我們實現相關的部分。
三)任務管理——第7章
任務管理在intel開發手冊中有很多的內容,對於我們需要了解的部分主要是關於堆棧切換的選用,它會使用第0個TSS段設置的堆棧,所以我們需要理解任務管理結構,先看基本結構圖:
如上圖所示,任務管理器的基本結構體被任務寄存器(TR)指向的GDT表項(TSS描述符),通過讀取設置的TSS段地址得到TSS段的信息。
TR寄存器的操作指令爲:LTR/STR。
TSS描述符:
TSS段:
如上如所示,當堆棧切換時,切換到指令執行權限爲0時,使用堆棧SS0與ESP0.
一葉說:中斷管理是每個處理器構架都會涉及的基本處理器功能,當然它也爲進程間切換與運行模式切換提供了最基本的服務。而對於操作系統來說,爲了區分內核部分與用戶進程部分,需要用不同執行權限來分割,在不同的執行權限的切換也需要藉助中斷,這樣也爲用戶進程調用內核服務提供了一種基本結構——系統調用,但從系統效率來說,執行中斷開銷是很大的,所以處理器也會提供其他快速執行系統調用的方式來實現。處理器執行的任務管理與中斷處理的流程,都需要一系列的數據結構來描述,當然也會對這些結構進行動態配置,從而需要實現必要的接口,用於系統調用與其他部分使用。