實驗3前篇——X86的中斷管理

       實驗3主要內容是用戶進程的創建,運行,銷燬;從操作系統的角度來看用戶進程,可以看作是動態加載可運行的程序,然後進行不斷地動態切換,這裏我們當然也可以從虛擬機的角度來考察操作系統——操作系統其實是在硬件上爲用戶進程創建虛擬機,每個虛擬機都是單獨運行在整個硬件平臺上的;而從用戶進程看操作系統,則是固定的程序部分,並且提供硬件接口的程序。當我們需要理解該實驗的內容時,可以想象一下,程序運行的流程——它總是被加載到內存中,然後將PC(程序計數器)指向程序進入點運行之,但是正常程序流程只有順序,判斷,循環的流程,所以最終只能在程序寫的一段代碼中反覆執行或者退出;當然可以動態加載用戶進程,所以對於每個進程都應該運行流程是一樣的。但是這樣的話,其中一個進程運行,另外的進程就無法運行了,所以這裏在程序運行之外還需要很重要的一種機制保證程序能夠被切換,它就是中斷。而且對於硬件處理,也需要異步來提高處理效率,所以操作系統就使用中斷來作爲基本工具實現進程的切換,而對於中斷的實現則是依賴於處理器構架,從而我們爲了更好的理解實驗3的內容,就需要對x86處理器的中斷有個全面而深入的理解。

    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位保護模式下的中斷處理。

    對於中斷向量我們可以從qemubiosdump出來(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門可以分爲3Task GateTrap GateInterrupt 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開發手冊中有很多的內容,對於我們需要了解的部分主要是關於堆棧切換的選用,它會使用第0TSS段設置的堆棧,所以我們需要理解任務管理結構,先看基本結構圖:

    如上圖所示,任務管理器的基本結構體被任務寄存器(TR)指向的GDT表項(TSS描述符),通過讀取設置的TSS段地址得到TSS段的信息。

    TR寄存器的操作指令爲:LTR/STR。

    TSS描述符:

   TSS段:

    如上如所示,當堆棧切換時,切換到指令執行權限爲0時,使用堆棧SS0與ESP0.

    一葉說:中斷管理是每個處理器構架都會涉及的基本處理器功能,當然它也爲進程間切換與運行模式切換提供了最基本的服務。而對於操作系統來說,爲了區分內核部分與用戶進程部分,需要用不同執行權限來分割,在不同的執行權限的切換也需要藉助中斷,這樣也爲用戶進程調用內核服務提供了一種基本結構——系統調用,但從系統效率來說,執行中斷開銷是很大的,所以處理器也會提供其他快速執行系統調用的方式來實現。處理器執行的任務管理與中斷處理的流程,都需要一系列的數據結構來描述,當然也會對這些結構進行動態配置,從而需要實現必要的接口,用於系統調用與其他部分使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章