深刻理解 x86/x64 的中斷體系

  1. 實模式下的中斷機制
  2. 保護模式下的中斷機制

 

1. 實模式下的中斷機制

x86 processor 在加電後被初始化爲 real mode 也稱爲 real-address mode,關於實模式請詳見文章:http://www.mouseos.com/arch/001.htmlhtml

processor 執行的第一條指針在 0xFFFFFFF0 處,這個地址通過 North Bridge(北橋)和 South ridge(南橋)芯片配合解碼,最終會訪問到固化的 ROM 塊,同時,通過別名機制映射在地址空間低端,實際上等於 ROM 被映射到地址空間最高端低端位置。web

此時在系統的內存裏其實並不存在 BIOS 代碼,ROM BIOS 的一部分職責是負責安裝 BIOS 代碼進入系統內存。windows

jmp far f000:e05b

典型是這條指令就是 0xFFFFFFF0 處的 ROM BIOS 指令,執行後它將跳到 0x000FE05B 處,這條指令的做用很大:數據結構

  • 更新 CS.base 使 processor 變成純正的 real mode
  • 跳轉到低端內存,使之進入 1M 低端區域

前面說過,此時內存中也不存在 BIOS,也就是說 IVT(中斷向量表)也是不存在的,中斷系統此時是不可用的,那麼由 ROM BIOS 設置 IVT 。oop

1.1 中斷向量表(IVT)

IDTR.base 被初始化爲 0,ROM BIOS 將不會對 IDTR.base 進行更改,所以若是實模式 OS 不更改 IDTR.base 的值,這意味着 IVT 在 0 的位置上,典型的如: DOS 操做系統。測試

保護模式下 IDTR.base 將向再也不是中斷向量表,而是中斷描述符表。再也不稱爲 IVT 而是 IDT。那是由於:spa

  • 在實模式下,DITR.base 指向的表格項直接給出中斷服務例程(Interrupt Service Routine)的入口地址。
  • 在保護模式下,並不直接給出入口地址,而是門描述符(Interrupt/Trap/Task gate),從這些門描述符間接取得中斷服務例程入口。

在 x86/x64 體系中容許有 256 箇中斷存在,中斷號從 0x00 - 0xff,共 256 箇中斷,如圖:操作系統

上面這個圖是實模式下的 IVT 表,每一個向量佔據 4 個字節,中斷服務例程入口是以 segment:offset 形式提供的,offset 在低端,segment 在高端,整個 IVT 表從地址 0x0 - 0x3FF,佔據了 1024 個字節,即 1K bytes設計

1.2 改變中斷向量表地址

事實上,咱們徹底能夠在實模式下更改 IVT 的地址,下面的代碼做爲示例:指針


; ****************************************************************
; * boot.asm for interrupt demo(real mode) on x86                *
; *                                                              * 
; * Copyright (c) 2009-2011                                      *
; * All rights reserved.                                         *
; * mik                                                          *
; * visit web site : www.mouseos.com                             *
; * bug send email : mik@mouseos.com                             *
; *                                                              *
; *                                                              *
; * version 0.01 by mik                                          *  
; ***************************************************************


BOOT_SEG        equ 0x7c00        ; boot module load into BOOT_SEG


;----------------------------------------------------------
; Now, the processor is real mode 
;----------------------------------------------------------

        bits 16

        org BOOT_SEG                   ; for int 19
        
start:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, BOOT_SEG
        
        mov si, msg1
        call printmsg

        sidt [old_IVT]                        ; save old IVT

        mov cx, [old_IVT]
        mov [new_IVT], cx                ; limit of new IVT

        mov dword [new_IVT+2], 0x8000        ; base of new IVT

        mov si, [old_IVT+2]
        mov di, [new_IVT+2]

        rep movsb                       

        lidt [new_IVT]                        ; set new IVT
        
        mov si, msg2
        call printmsg


        jmp $


       

;-----------------------------------
; printmsg() - print message
;-----------------------------------
printmsg:
        mov ah, 0x0e
        xor bh, bh

print_loop:
        lodsb
        test al,al
        jz done
        int 0x10
        jmp print_loop

done:        
        ret


       

old_IVT         dw 0                        ; limit of IVT
                dd 0                        ; base of IVT

new_IVT         dw 0                        ; limit of IVT
                dd 0                        ; base of IVT


        
msg1            db 'Hi, print message with old IVT', 10,13, 0
msg2            db 'Now,pirnt message with new IVT', 13, 10, 0


        
times 510-($-$$) db 0
        
        dw 0xaa55
        
; end of boot.asm

在 vmware 上這段代碼的執行結果如圖:

這段代碼在實模式下將 IVT 表複製到 0x8000 位置上,而後將 IVT 地址設爲 0x8000 上,這樣徹底能夠正常工做。正如代碼上看到的,我作:

  1. 使用 sidt 指令取得 IDTR 寄存器的值,即 IVT 的 limit 和 base 值,保存在 old_IVT 裏
  2. 設置 new_IVT 值,limit 等於 old_IVT 的 limit,base 設爲 0x8000
  3. 將 IVT 表複製到 0x8000 處
  4. 使用 lidt 指令加載 IDTR 寄存器,即設 IVT 表在 0x8000 

1.3 設置本身的中斷服務例程

在中斷向量表裏還有許多空 vector 是未使用的,咱們能夠在這些空白的向量裏設置本身的中斷服務例程,典型的如: DOS 操做系統中使用了 0x21 號向量做爲 DOS 提供給用戶的系統調用!

在這裏我將展現,使用 0x40 向量做爲本身的中斷服務例程向量,我所須要作的是:

  1. 寫一個本身的中斷服務例程,在本例中的 my_isr
  2. 設置向量 0x40 的 segment 和 offset 值
  3. 調用 int 0x40 進行測試

中斷服務例程 my_isr 很簡單,僅僅是打印信息:

;------------------------------------------------
; our Interrupt Service Routine: vector 0x40
;-------------------------------------------------
my_isr:
        mov si, msg3
        call printmsg
        iret

接下來設置 vector 0x40 的 segment 和 offset:

        mov ax, cs
        mov bx, [new_IVT+2]  ; base of IVT
        mov dword [bx+0x40*4], my_isr ; set offset 0f my_isr
        mov [bx+0x40*4+2], ax  ; set segmet of my_isr

記住 segment 在高位,offset 在低位,這個 segment 是咱們當前的 CS,offset 是咱們的 ISR 地址,直接寫入 IVT 表中就能夠了

如今咱們能夠測試了:

        int 0x40

結果以下:

咱們的 ISR 能正常工做了,我提供了完整的示例源碼和磁盤映像下載:interrupt_demo


2. 保護模式下的中斷機制

引入保護模式後,情形變得複雜多了,實施了權限控制機制,爲了支持權限的控制增添了幾個重要的數據結構,下面是與中斷相關的結構:

  • gate descriptor(門描述符):用於描述和控制 Interrupt Service Routine 的訪問,中斷可以使用的 gate 包括:
    • Interrupt-gate descriptor(中斷門描述符)
    • Trap-gate descriptor(陷井門描述符)
    • Task-gate descriptor(任務門描述符)-- 在 64 位模式下無效
  • interrupt descriptor table(中斷描述符表):用於存在 gate descriptor 的表格

在 32 位保護模式下,每一個 gate descriptor 是 8 bytes 寬,在 64 位模式下 gate descriptor 被擴充爲 16 bytes, 同時 64 位模式下不存在 task gate descriptor,所以在 64 位下的 IDT 只容許存放 interrupt/trap gate descriptor。

當咱們執行調用中斷時,processor 會在 IDT 表中尋找相應的 descriptor,並經過了權限檢查轉入相應的 interrupt service routine(大多數時候被稱爲 interrupt handler),在中斷體系裏分爲兩種引起方式:

  • 由硬件引起請求
  • 由軟件執行調用

然而軟件上發起的中斷調用還可分爲:

  • 引起 exception(異常) 執行 interrupt handler
  • 軟件請求執行 system service(系統服務),而執行 interrupt handler

硬件引起的中斷請求還可分爲:

  • maskable interrupt(可屏蔽中斷)
  • non-maskable interrupt(不可屏蔽中斷)

不管何種方式,進入 interrupt handler 的途徑都是同樣的。

2.1 查找 interrupt handler 入口

在發生中斷時,processor 在 IDTR.base 裏能夠獲取 IDT 的地址,根據中斷號在 IDT 表裏讀取 descriptor,descriptor 的職責是給出 interrupt handler 的入口地址,processor 會檢查中斷號(vector)是否超越 IDT 的 limit 值。

上圖是 interrupt handler 的定位查找圖。在 32 位模式下的過程是:

  1. 從 IDTR.base 獲得 IDT 表地址
  2. 從 IDTR.base + vector * 8(每一個 descriptor 爲 8 bytes)處讀取 8 bytes 寬的 escriptor
  3. 對 descriptor 進行分析檢查,包括:
    • descriptor 類型的檢查
    • IDT limit 的檢查
    • 訪問權限的檢查
  4. 從 gate descriptor 裏讀取 selector
  5. 判斷 code segment descriptor 是存放在 GDT 表仍是 LDT 表
  6. 使用 selector 從 descriptor table 讀取 code segment descriptor,固然這裏也要通過對 code segment descriptor 的檢查
    • descriptor 類型檢查
    • GDT limit 檢查
    • 訪問權限的檢查
  7. 從 code segment descriptor 中讀取 code segment base 值
  8. 從 gate descriptor 裏讀取 interrupt handler 的 offset 值
  9. 得取 interrupt handler 的入口地址:base + offset,轉入執行 interrupt handler

它的邏輯用 C 描述,相似下面:

long IDT_address;                             /* address of IDT */
long DT_address;                              /* GDT or LDT */
DESCRIPTOR gate_descriptor;                   /* gate descriptor */
DESCRIPTOR code_descriptor;                   /* code segment descriptor */
short selector;                               /* code segment selector */


IDT_address = IDTR.base;                      /* get address of IDT */
gate_descriptor = IDT_address + vector * 8;   /* get descriptor */

selector = gate_descriptor.selector;
DT_address = selector.TI ? LDTR.base : GDTR.base;                      /* address of GDT or LDT */
code_descriptor = GDT_address + selector * 8;                          /* get code segment descriptor */
interrupt_handler = code_descriptor.base + gate_descripotr.offset;     /* interrupt handler entry */

((*(void))interrupt_handler)();                  /* do interrupt_handler() */

上面的 C 代碼顯示了 processor 定位 interrupt handler 的邏輯過程,爲了清楚展現這個過程,這裏省略了各類的檢查機制

2.2 IDT 表中 descriptor 類型的檢查

processor 會對 IDT 表中的 descriptor 類型進行檢查,這個檢查發生在:

當讀取 IDT 表中的 descriptor 時

在 IDT 中的 descriptor 類型要屬於:

  • S = 0:屬於 system 類 descriptor
  • descriptor 的 type 域應屬於:
    • 1110:32-bit interrupt gate
    • 1111:32-bit trap gate
    • 0101:task gate
    • 0110:16-bit interrupt gate
    • 0111:16-bit trap gate

非上述所說的類型,都將會產生 #GP 異常。當 descriptor 的 S 標誌爲 1 時,表示這是個 user 的 descriptor,它們是:code/data segment descriptor。能夠看到在 32 位保護模式下 IDT 容許存在 interrupt/trap gate 以及 task gate

2.3 使用 16-bit gate descriptor

在 32 位保護模式下 interrupt handler 也能使用 16-bit gate descriptor,包括:

  • 16-bit interrupt gate
  • 16-bit trap gate

這是一個比較特別的現象,假如使用 16-bit gate 來構建中斷調用機制,實際上等於 interrupt handler 會從 32-bit 模式切換到 16-bit 模式執行。只要構建環境要素正確這樣切換執行固然是沒問題的。

這個執行環境要素須要注意的是:當使用 16-bit gate 時,也要相應使用 16-bit code segment descriptor。也就是在 gate descriptor 中的 selector 要使用 16-bit code segment selector。下面我寫了個使用 16-bit gate 構建 interrupt 調用的例子:

; set IDT vector
        mov eax, BP_handler
        mov [IDT+3*8], ax                       ; set offset 15-0
        mov word [IDT+3*8+2], code16_sel        ; 16-bit code selector
        mov word [IDT+3*8+4], 0xc600            ; DPL=3, 16-bit interrupt gate
        shr eax, 16
        mov [IDT+3*8+8], ax                     ; offset 31-16

上面這段代碼將 vector 3 設置爲使用 16-bit interrupt gate,而且使用了 16-bit selector

下面是個人 interrupt handler 代碼:

        bits 16

;-----------------------------------------------------
; INT3 BreakPoint handler for 16-bit interrupt gate
;-----------------------------------------------------
BP_handler:
       jmp do_BP_handler
BP_msg     db 'I am a 16-bit breakpoint handler on 32-bit proected mode',0

do_BP_handler:
       mov edi, 10
       mov esi, BP_msg
       call printmsg16

       iret

這個 interrupt handler 很簡單,只是打印一條信息而已,值得注意的是,這裏須要使用 bits 16 來指示編譯爲 16 位代碼。

那麼這樣咱們就可使用 int3 指令來調用這個 16-bit 的 interrupt handler,執行結果如圖:

完整的源代碼和軟盤映像下載:interrupt_demo1.rar

2.4 IDT 表的 limit 檢查

在 IDT 表中查找索引 gate descriptor 時,processor 也會對 IDT 表的 limit 進行檢查,這個檢查的邏輯是:

gate_descriptor = IDTR.base + vector * 8;                  /* get gate descriptor */

if ((gate_descriptor + sizeof(DESCRIPTOR) - 1) > (IDTR.base + IDTR.limit))
{
        /* failure: #GP exception */
}

咱們看看下面這個圖:

當咱們設:

  • IDTR.base = 0x10000
  • IDTR.limit = 0x1f

那麼 IDT 表的有效地址範圍是:0x10000 - 0x1001f,也就是:IDTR.base + IDTR.limit 這表示:

  • vector 0:0x10000 - 0x10007
  • vector 1:0x10008 - 0x1000f
  • vector 2: 0x10010 - 0x10017
  • vector 3: 0x10018 - 0x1001f

上面是這 4 個 vector 的有效範圍,所以:當設 IDTR.limit = 0x1e 時,若是訪問 vector 3 時(調用中斷3)processor 檢測到訪問 IDT 越界而出錯!

所以:訪問的 vector 地址在 IDTR.base 到 IDTR.base + IDTR.limit(含)以外,將會產生 #GP 異常。

2.5 請求訪問 interrupt handler 時的權限檢查

訪問權限的檢查是 x86/x64 體系中保護措施中很是重要的一環,它控制着訪問者是否有權限進行訪問,在訪問 interrupt handler 過程權限控制中涉及 3 個權限類別

  • CPL:當前 processor 所處的權限級別
  • DPLg:表明 DPL of gate,也就是 IDT 中 gate descriptor 所要求的訪問權限級別
  • DPLs:表明 DPL of code segment,也就是 interrupt handler 的目標 code segment 所要求的訪問權限級別

CPL 權限級別表明着訪問者的權限,也就是說當前正在執行代碼的權限,要理解權限控制的邏輯,你須要明白下面兩點:

  1. 要調用 interrupt handler 那麼首先你必需要有權限去訪問 IDT 表中的 gate,這表示:CPL 的權限必須不低於 DPLg (gate 所要求的權限),這樣你纔有權限去訪問 gate
  2. interrupt handler 會在高權限級別裏執行,也就是說 interrupt handler 會在特權級別裏運行。這表示:interrupt handler 裏的權限至少不低於訪問者的權限

在調用 interrupt handler 中並不使用 selector 來訪問 gate 而是使用使用 vector 來訪問 gate,所以中斷權限控制中並不使用 RPL 權限類別,咱們能夠得知中斷訪問權限控制的要素:

  • CPL <= DPLg
  • CPL >= DPLs

需同時知足上面的兩個式子,在比較表達式中數字高的權限低,數字低的權限高!用 C 描述爲:

DPLg = gate_descriptor.DPL;               /* DPL of gate */
DPLs = code_descriptor.DPL;               /* DPL of code segment */

if ((CPL <= DPLg) && (CPL >= CPLs)) 
{
     /* pass */
} else {
     /* failure: #GP exception */
}

2.5.1 gate 的權限設置

對於 gate 的權設置,咱們應考慮 interrupt handler 調用上的兩個原則:

  • interrupt handler 開放給用戶調用
  • interrupt handler 在系統內部使用

由這兩個原則產生了 gate 權根設置的兩個設計方案:

  • gate 的權限設置爲 3 級:這樣能夠給用戶代碼有足夠的權限去訪問 gate
  • gate 的權限設置爲 0 級:只容許內核代碼訪問,用戶無權經過這個 gate 去訪問 interrupt handler

這是現代操做系統典型的 gate 權限設置思路,絕大部分的 gate 都設置爲高權限,僅有小部分容許用戶訪問。很明顯:系統服務例程的調用入口應該設置爲 3 級,以供用戶調用

下面是很典型的設計:

  • #BP 異常:BreakPoint(斷點)異常的 gate 應該設置爲 3 級,使得用戶程序可以使用斷點調試程序。
  • 系統調用:系統調用是 OS 提供給用戶訪問 OS 服務的接口,所以 gate 必須設置爲 3 級。

系統調用在每一個 OS 實現上多是不一樣的,#BP 異常一定是 vector 3,所以對於 vector 3 所使用的 gate 必須使用 3 級權限。

下面是在 windows 7 x64 操做系統上的 IDT 表的設置:

<bochs:2> info idt
Interrupt Descriptor Table (base=0xfffff80004fea080, limit=4095):
IDT[0x00]=64-Bit Interrupt Gate target=0x0010:fffff80003abac40, DPL=0
IDT[0x01]=64-Bit Interrupt Gate target=0x0010:fffff80003abad40, DPL=0
IDT[0x02]=64-Bit Interrupt Gate target=0x0010:fffff80003abaf00, DPL=0
IDT[0x03]=64-Bit Interrupt Gate target=0x0010:fffff80003abb280, DPL=3
IDT[0x04]=64-Bit Interrupt Gate target=0x0010:fffff80003abb380, DPL=3
IDT[0x05]=64-Bit Interrupt Gate target=0x0010:fffff80003abb480, DPL=0

... ...

IDT[0x29]=64-Bit Interrupt Gate target=0x0010:fffff80003bf2290, DPL=0
IDT[0x2a]=64-Bit Interrupt Gate target=0x0010:fffff80003bf22a0, DPL=0
IDT[0x2b]=64-Bit Interrupt Gate target=0x0010:fffff80003bf22b0, DPL=0
IDT[0x2c]=64-Bit Interrupt Gate target=0x0010:fffff80003abca00, DPL=3
IDT[0x2d]=64-Bit Interrupt Gate target=0x0010:fffff80003abcb00, DPL=3

IDT[0x2e]=64-Bit Interrupt Gate target=0x0010:fffff80003bf22e0, DPL=0
IDT[0x2f]=64-Bit Interrupt Gate target=0x0010:fffff80003b09590, DPL=0
IDT[0x30]=64-Bit Interrupt Gate target=0x0010:fffff80003bf2300, DPL=0

上面的粗體顯示 interrupt gate 被設置爲 3 級,在 windows 7 x64 下 vector 0x2c 和 0x2d 被設置爲系統調用接口。實際上這兩個 vector 的入口雖然不一樣,可是代碼是同樣的。你能夠經過 int 0x2c 和 int 0x2d 請求系統調用。

那麼對於系統內部使用的 gate 咱們應該保持與用戶的隔離,絕大部分 interrupt handler 的 gate 權限都是設置爲 0 級的。

2.5.2 interrupt handler 的 code segment 權限設置

前面說過:interrupt handler 的執行權限應該至少不低於調用者的權限,意味着 interrupt handler 須要在高權限級別下運行。不管是系統提供給用戶的系統服務例程仍是系統內部使用的 interrupt handler 咱們都應該將 interrupt handler 設置爲 0 級別的運行權限(最高權限),這樣才能保證 interrupt handler 能訪問系統的所有資源。

在權限檢查方面,要求 DPLs 權限(interrupt handler 的執行權限)要高於或等於調用者的權限,也就是 CPL 權限,當數字上 DPLs 要小於等於 CPL(DPLs <= CPL)。

2.6 使用 interrupt gate

使用 interrupt gate 來構造中斷調用機制的,當 processor 進入 interrupt handler 執行前,processor 會將 eflags 值壓入棧中保存而且會清 eflags.IF 標誌位,這意味着進入中斷後不容許響應 makeable 中斷(可屏蔽中斷)。它的邏輯 C 描述爲:

*(--esp) = eflags;                           /* push eflags */

if (gate_descriptor.type == INTERRUPT_GATE)
        eflags.IF = 0;                       /* clear eflags.IF */

interrupt handler 使用 iret 指令返回時,會將棧中 eflags 值出棧以恢復原來的 eflags 值。

下面是 interrupt gate 的結構圖:

能夠看到 interrupt gate 和 trap gate 的結構是徹底同樣的,除了以 type 來區分 gate 外,interrupt gate 的類型是:

  • 1110:32-bit interrupt gate
  • 0110:16-bit interrupt gate

32 位的 offset 值提供了 interrupt handler 的入口偏移地址,這個偏移量是基於 code segment 的 base 值,selector 域提供了目標 code segment 的 selector,用來在 GDT 或 LDT進行查找 code segment descriptor。這些域的使用描述爲:

if (gate_descriptor.selector.TI == 0)
        code_descriptor = GDTR.base + gate_descriptor.selector * 8;        /* GDT */
else
        code_descriptor = LDTR.base + gate_descriptor.selector * 8;        /* LDT */


interrupt_handler = code_descriptor.base + gate_descriptor.offset;         /* interrupt handler entry */

注得注意的是:在 interrupt gate 和 trap gate 中的 selector 它的 RPL 是不起做用的,這個 selector.RPL 將被忽略

在 OS 的實現中大部分的 interrupt handler 都是使用 interrupt gate 進行構建的。在 windows 7 x64 系統上所有都使用 interrupt gate 並無使用 trap gate

2.7 使用 trap gate

trap gate 在結構上與 interrupt gate 是徹底同樣的,參見節 2.6 的那圖,trap gate 與 interrupt gate 不一樣的一點是:使用 trap gate 的,processor 進入 interrupt handler 前並不改變 eflags.IF 標誌,這意味着在 interrupt handler 裏將容許可屏蔽中斷的響應。

*(--esp) = eflags;                                               /* push eflags */

if (gate_descriptor.type == TRAP_GATE) {
                                                                /* skip: do nothing */
} else if (gate_descriptor.type == INTERRUPT_GATE){
         eflags.IF = 0;                                         /* clear eflags.IF */       
} else if (gate_descriptor.type == TASK_GATE) {
         ... ...
}

2.8 使用 task gate

在使用 task gate 的情形下變得異常複雜,你須要爲 new task 準備一個 task 信息的 TSS,然而你必須事先要設置好當前的 TSS 塊,也就是說,系統中應該有兩個 TSS 塊:

  • current TSS
  • TSS of new task

當前的 TSS 是系統初始化設置好的,這個 TSS 的做用是:當發生 task 切換時保存當前 processor 的狀態信(當前進程的 context 環境),新任務的 TSS 是經過 task gete 進行切換時使用的 TSS 塊,這個 TSS 是存放新任務的入口信息。

tss_desc        dw 0x67                ; seletor.SI = 3
                dw TSS
                dd 0x00008900

tss_gate_desc   dw 0x67                ; selector.SI = 4
                dw TSS_TASKGATE         
                dd 0x00008900

在上面的示例代碼中,設置了兩個 TSS descriptor,一個供系統初始化使用(tss_desc),另外一個是爲新任務而設置(tss_task_gate),代碼中必須設置兩個 TSS 塊:

  • TSS
  • TSS_TASKGATE

TSS 塊的內容是什麼在這個示例中可有可無,然而 TSS_TASKGATE 塊中應該設置新任務的入口信息,其中包括:eip 和 cs 值,之後必要的 DS 與 SS 寄存器值,還有 eflags 和 GPRs 值,下面的代碼正是作這項工做:

; set TSS for task-gate
         mov dword [TSS_TASKGATE+0x20], BP_handler32                ; tss.EIP
         mov dword [TSS_TASKGATE+0x4C], code32_sel                  ; cs
         mov dword [TSS_TASKGATE+0x50], data32_sel                  ; ss
         mov dword [TSS_TASKGATE+0x54], data32_sel                  ; ds
         mov dword [TSS_TASKGATE+0x38], esp                         ; esp
         pushf
         pop eax
         mov dword [TSS_TASKGATE+0x24], eax                         ; eflags

我將新任務的入口點設爲 BP_handler32(),這個是 #BP 斷點異常處理程序,保存當前的 eflags 值做爲新任務的 eflags 值。

咱們必須爲 task gate 設置相應的 IDT 表項,正以下面的示例代碼:

; set IDT vector: It's a  #BP handler

         mov word [IDT+3*8+2], tss_taskgate_sel                  ; tss selector
         mov dword [IDT+3*8+4], 0xe500                           ; type = task gate

示例代碼中,我爲 vector 3(#BP handler)設置爲 task-gate descirptor,當發生 #BP 異常時,就會經過 task-gate 進行任務切換到咱們的新任務(BP_handler32)。

; load IDT into IDTR
         lidt [IDT_POINTER]


; load TSS
         mov ax, tss_sel
         ltr ax

固然咱們應該先設置好 IDT 表和加載當前的 TSS 塊,這個 TSS 塊就是咱們所定義的第1個 TSS descirptor (tss_desc),這個 TSS 塊裏什麼內容都沒有,設置它的目的是爲切換到新任務時,保存當前任務的 context 環境,以便執行完新任務後切換回到原來的任務。

        db 0xcc                         ; throw BreakPoint

如今咱們就能夠測試咱們的 BP_handler32(),經過 INT3 指令引起 #BP 異常,這個異常經過 task-gate 進行切換。

咱們的 BP_handler32 代碼是這樣的:

;-----------------------------------------------------
; INT3 BreakPoint handler for 32-bit interrupt gate
;-----------------------------------------------------
BP_handler32:
       jmp do_BP_handler32
BP_msg32    db 'I am a 32-bit breakpoint handler with task-gate on 32-bit proected mode',0

do_BP_handler32:

       mov edi, 10
       mov esi, BP_msg32
       call printmsg

       clts                   ; clear CR0.TS flag

       iret

它只是簡單的顯示一條信息,在這個 BP_handler32 中,咱們應該要清 CR0.TS 標誌位,這個標誌位是經過 TSS 進行任務切換時,processor 自動設置的,然而 processsor 不會清 CR0.TS 標誌位,須要代碼中清除。

2.8.1 任務切換的情形

在本例中,咱們來看看當進行任務切換時發生了什麼,processor 會設置一些標誌位:

  • 置 CR0.TS = 1
  • 置 eflags.NT = 1

設置 CR0.TS 標誌位表示當前發生過任務切換,processor 只會置位,而不會清位,事實上,你應該使用 clts 指令進行清位工做。設置 eflags.NT 標誌位表示當前任務是在嵌套層內,它指示當進行中斷返回時,需切換回原來的任務,所以,請注意:

當執行 iret 指令時,processor 會檢查 eflags.NT 標誌是否置位

當 eflags.NT 被置位時,processor 執行另外一個任務切換工做,從 TSS 塊的 link 域中取出原來的 TSS selector 從而切換回原來的任務。這不像 ret 指令,它不會檢查 eflags.NT 標誌位。

processor 也會對 TSS descriptor 作一些設置標誌,當進入新任務時,processor 會設置 new task 的 TSS descriptor 爲 busy,當切換回原任務時,會置回這個任務的 TSS descriptor 爲 available,同時 processor 會檢查 TSS 中的 link 域的 TSS selector(原任務的 TSS)是否爲 busy,若是不爲 busy 則會拋出 #TS 異常。

固然發生切換時 processor 會保存當前的 context 到 current TSS 塊中,所以:

  • 切換到目標任務時,processor 會保存當前的任務 context 到 TSS,讀取目標任務的 TSS,加載相應的信息,而後進入目標任務
  • 目標任務執行完後,切換回原任務時,processor 會保存目標任務的 context 到目標任務的 TSS 中,從目標任務的 TSS 塊讀取 link(原任務的 TSS selector),加載相應的信息,返回到原任務

當從目標任務返回時,processor 會清目標任務的 eflags.NT = 0,如前所述目標任務的 TSS descriptor 也會被置爲 available

版權 mik 全部,轉載請註明出處

 

from: http://www.mouseos.com/arch/interrupt.html

相關文章
相關標籤/搜索