1. 程式人生 > >Bran的核心開發教程(bkerndev)-08 中斷服務程式(ISR)

Bran的核心開發教程(bkerndev)-08 中斷服務程式(ISR)

中斷服務程式(ISR)

  中斷服務程式(ISR)用於儲存當前處理器的狀態, 並在呼叫核心的C級中斷處理程式之前正確設定核心模式所需的段暫存器。而工作只需要15到20行彙編程式碼來處理, 包括呼叫C中的處理程式。我們還需要將IDT條目指向正確的ISR以正確處理異常。

  異常是導致處理器無法正常執行的特殊情況, 比如除以0結果是未知數或者非實數, 因此處理器會丟擲異常, 這樣核心就可以阻止程序或任務引起任何問題。如果處理器發現程式正嘗試訪問不允許其訪問的記憶體, 則會引起一般保護錯誤。當你設定記憶體頁時, 處理器將會產生頁面錯誤, 但這是可以恢復的: 你可以將記憶體頁對映到錯誤的地址(但這需要另開一篇教程來講解)。

  IDT的前32個條目與處理器可能產生的異常對應, 因此需要對其進行處理。某些異常會將另一個值壓入堆疊中: 錯誤程式碼, 該值為每個異常的特定程式碼。

Exception # Description Error Code?
0 Division By Zero Exception No
1 Debug Exception No
2 Non Maskable Interrupt Exception No
3 Breakpoint Exception No
4 Into Detected Overflow Exception No
5 Out of Bounds Exception No
6 Invalid Opcode Exception No
7 No Coprocessor Exception No
8 Double Fault Exception Yes
9 Coprocessor Segment Overrun Exception No
10 Bad TSS Exception Yes
11 Segment Not Present Exception Yes
12 Stack Fault Exception Yes
13 General Protection Fault Exception Yes
14 Page Fault Exception Yes
15 Unknown Interrupt Exception No
16 Coprocessor Fault Exception No
17 Alignment Check Exception (486+) No
18 Machine Check Exception (Pentium/586+) No
19 to 31 Reserved Exceptions No

  之前提到, 一些異常會錯誤碼壓入堆疊中, 為了降低複雜度, 我們為尚未壓入錯誤碼的ISR將偽錯誤碼0壓入堆疊中, 這樣可以保持統一的堆疊結構。為了跟蹤觸發的是哪個異常, 我們將中斷號也壓入堆疊。我們使用匯編操作碼"cli"來禁用中斷並防止觸發IRQ, 否則可能會導致核心衝突。為了節省核心空間, 生成較小的二進位制檔案, 我們讓每個ISR的存根(stub)跳轉到通用isr_common_stub函式。isr_common_stub用於將處理器的狀態儲存到堆疊上, 將當前堆疊地址壓入堆疊(為我們的C處理程式提供堆疊), 呼叫C中的fault_handler函式, 最後恢復堆疊的狀態。在"start.asm"預留的位置中新增下面的程式碼, 填寫所有的32個ISR:

start.asm

; 在之後的教程中, 我們將新增中斷
; 這裡是中斷服務程式(ISR)
global _isr0
global _isr1
global _isr2
global _isr3
global _isr4
global _isr5
global _isr6
global _isr7
global _isr8
global _isr9
global _isr10
global _isr11
global _isr12
global _isr13
global _isr14
global _isr15
global _isr16
global _isr17
global _isr18
global _isr19
global _isr20
global _isr21
global _isr22
global _isr23
global _isr24
global _isr25
global _isr26
global _isr27
global _isr28
global _isr29
global _isr30
global _isr31

;  0: 除以零異常
_isr0:
    cli
    push byte 0    ; 一個ISR佔位符, 會彈出一個為錯誤碼來保持一個統一的堆疊框架
    push byte 0
    jmp isr_common_stub

;  1: 除錯異常
_isr1:
    cli
    push byte 0
    push byte 1
    jmp isr_common_stub
    
;  2: 不可遮蔽的中斷異常
_isr2:
    cli
    push byte 0
    push byte 2
    jmp isr_common_stub

;  3: Int 3異常
_isr3:
    cli
    push byte 0
    push byte 3
    jmp isr_common_stub

;  4: INTO異常
_isr4:
    cli
    push byte 0
    push byte 4
    jmp isr_common_stub

;  5: 越界異常
_isr5:
    cli
    push byte 0
    push byte 5
    jmp isr_common_stub

;  6: 無效的操作碼異常
_isr6:
    cli
    push byte 0
    push byte 6
    jmp isr_common_stub

;  7: 協處理器不可用異常
_isr7:
    cli
    push byte 0
    push byte 7
    jmp isr_common_stub

;  8: 雙重故障異常(帶錯誤碼!)
_isr8:
    cli
    push byte 8        ; 注意我們不需要在此壓入一個值到堆疊中, 它已經壓入了一個。
                   ; 會彈出錯誤碼的異常可使用這類存根
    jmp isr_common_stub

;  9: 協處理器段溢位異常
_isr9:
    cli
    push byte 0
    push byte 9
    jmp isr_common_stub

; 10: 錯誤的TSS異常(帶錯誤碼!)
_isr10:
    cli
    push byte 10
    jmp isr_common_stub

; 11: 段不存在異常(帶錯誤碼!)
_isr11:
    cli
    push byte 11
    jmp isr_common_stub

; 12: 堆疊故障異常(帶錯誤碼!)
_isr12:
    cli
    push byte 12
    jmp isr_common_stub

; 13: 常規保護故障異常(帶錯誤碼!)
_isr13:
    cli
    push byte 13
    jmp isr_common_stub

; 14: 頁面錯誤異常(帶錯誤碼!)
_isr14:
    cli
    push byte 14
    jmp isr_common_stub

; 15: 保留異常
_isr15:
    cli
    push byte 0
    push byte 15
    jmp isr_common_stub

; 16: 浮點異常
_isr16:
    cli
    push byte 0
    push byte 16
    jmp isr_common_stub

; 17: 對齊檢查異常
_isr17:
    cli
    push byte 0
    push byte 17
    jmp isr_common_stub

; 18: 機器檢查異常
_isr18:
    cli
    push byte 0
    push byte 18
    jmp isr_common_stub

; 19: 保留
_isr19:
    cli
    push byte 0
    push byte 19
    jmp isr_common_stub

; 20: 保留
_isr20:
    cli
    push byte 0
    push byte 20
    jmp isr_common_stub

; 21: 保留
_isr21:
    cli
    push byte 0
    push byte 21
    jmp isr_common_stub

; 22: 保留
_isr22:
    cli
    push byte 0
    push byte 22
    jmp isr_common_stub

; 23: 保留
_isr23:
    cli
    push byte 0
    push byte 23
    jmp isr_common_stub

; 24: 保留
_isr24:
    cli
    push byte 0
    push byte 24
    jmp isr_common_stub

; 25: 保留
_isr25:
    cli
    push byte 0
    push byte 25
    jmp isr_common_stub

; 26: 保留
_isr26:
    cli
    push byte 0
    push byte 26
    jmp isr_common_stub

; 27: 保留
_isr27:
    cli
    push byte 0
    push byte 27
    jmp isr_common_stub

; 28: 保留
_isr28:
    cli
    push byte 0
    push byte 28
    jmp isr_common_stub

; 29: 保留
_isr29:
    cli
    push byte 0
    push byte 29
    jmp isr_common_stub

; 30: 保留
_isr30:
    cli
    push byte 0
    push byte 30
    jmp isr_common_stub

; 31: 保留
_isr31:
    cli
    push byte 0
    push byte 31
    jmp isr_common_stub

; 我們在這裡呼叫C函式
; 我們需要讓彙編器知道"_fault_handler"在另一個檔案中
extern _fault_handler

; 這是我們ISR的通用存根
; 它用於儲存處理器的狀態, 設定核心模式段, 呼叫C裡的故障處理程式
; 最後恢復堆疊框架
isr_common_stub:
    pusha
    push ds
    push es
    push fs
    push gs
    mov ax, 0x10   ; 載入核心資料段描述符
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov eax, esp   ; 將指向堆疊的指標壓入堆疊
    push eax
    mov eax, _fault_handler
    call eax       ; 特殊呼叫, 儲存"eip"暫存器的值
    pop eax
    pop gs
    pop fs
    pop es
    pop ds
    popa
    add esp, 8     ; 清除壓入的錯誤碼和ISR號
    iret           ; 將CS、EIP、EFLAGS、SS和ESP一同彈出

  建立一個新檔案, 命名為"isrs.c"。別忘了在"build.bat"檔案中新增一行GCC命令編譯該檔案。將檔案"isrs.o"新增到LD檔案列表中, 這樣才能將其連結到核心中。"isrs.c"檔案很簡單: 首先是常規的#include行, 宣告"start.asm"中每個ISR的原型, 將IDT條目指向正確的ISR, 最後建立一箇中斷處理程式來服務我們所有的異常。

isrs.c

#include <system.h>

/* 這裡是所有異常處理程式的原型: 
 * IDT的前32個條目由英特爾保留, 
 * 用於處理異常 */
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();

/* 我們將IDT的前32個條目設定為前32個ISR
 * 這裡我們無法使用for迴圈, 因為無法獲取與之對應的函式名
 * 我們將訪問標誌設定為0x8E, 代表條目存在, 並在Ring 0(核心級別)中執行 
 * 並將低5位設定為要求的"14", 用十六進位制的"E"表示 */
void isrs_install()
{
    idt_set_gate(0, (unsigned)isr0, 0x08, 0x8E);
    idt_set_gate(1, (unsigned)isr1, 0x08, 0x8E);
    idt_set_gate(2, (unsigned)isr2, 0x08, 0x8E);
    idt_set_gate(3, (unsigned)isr3, 0x08, 0x8E);
    idt_set_gate(4, (unsigned)isr4, 0x08, 0x8E);
    idt_set_gate(5, (unsigned)isr5, 0x08, 0x8E);
    idt_set_gate(6, (unsigned)isr6, 0x08, 0x8E);
    idt_set_gate(7, (unsigned)isr7, 0x08, 0x8E);

    idt_set_gate(8, (unsigned)isr8, 0x08, 0x8E);
    idt_set_gate(9, (unsigned)isr9, 0x08, 0x8E);
    idt_set_gate(10, (unsigned)isr10, 0x08, 0x8E);
    idt_set_gate(11, (unsigned)isr11, 0x08, 0x8E);
    idt_set_gate(12, (unsigned)isr12, 0x08, 0x8E);
    idt_set_gate(13, (unsigned)isr13, 0x08, 0x8E);
    idt_set_gate(14, (unsigned)isr14, 0x08, 0x8E);
    idt_set_gate(15, (unsigned)isr15, 0x08, 0x8E);

    idt_set_gate(16, (unsigned)isr16, 0x08, 0x8E);
    idt_set_gate(17, (unsigned)isr17, 0x08, 0x8E);
    idt_set_gate(18, (unsigned)isr18, 0x08, 0x8E);
    idt_set_gate(19, (unsigned)isr19, 0x08, 0x8E);
    idt_set_gate(20, (unsigned)isr20, 0x08, 0x8E);
    idt_set_gate(21, (unsigned)isr21, 0x08, 0x8E);
    idt_set_gate(22, (unsigned)isr22, 0x08, 0x8E);
    idt_set_gate(23, (unsigned)isr23, 0x08, 0x8E);

    idt_set_gate(24, (unsigned)isr24, 0x08, 0x8E);
    idt_set_gate(25, (unsigned)isr25, 0x08, 0x8E);
    idt_set_gate(26, (unsigned)isr26, 0x08, 0x8E);
    idt_set_gate(27, (unsigned)isr27, 0x08, 0x8E);
    idt_set_gate(28, (unsigned)isr28, 0x08, 0x8E);
    idt_set_gate(29, (unsigned)isr29, 0x08, 0x8E);
    idt_set_gate(30, (unsigned)isr30, 0x08, 0x8E);
    idt_set_gate(31, (unsigned)isr31, 0x08, 0x8E);
}

/* 這裡是一個簡單的字串陣列, 包含與每個異常對應的訊息
 * 我們通過這種方式來獲得對應的訊息: 
 * exception_message[interrupt_number] */
unsigned char *exception_messages[] =
{
    "Division By Zero",
    "Debug",
    "Non Maskable Interrupt",
    "Breakpoint",
    "Into Detected Overflow",
    "Out of Bounds",
    "Invalid Opcode",
    "No Coprocessor",

    "Double Fault",
    "Coprocessor Segment Overrun",
    "Bad TSS",
    "Segment Not Present",
    "Stack Fault",
    "General Protection Fault",
    "Page Fault",
    "Unknown Interrupt",

    "Coprocessor Fault",
    "Alignment Check",
    "Machine Check",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",

    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved",
    "Reserved"
};

/* 我們所有的異常處理中斷服務程式都將指向此函式, 這會告訴我們發生了什麼異常
 * 現在我們只是通過死迴圈來暫停系統
 * 當所有ISR被用作“鎖定”機制時,它們會禁用中斷,以防止IRQ的發生並破壞核心資料結構 */
void fault_handler(struct regs *r)
{
    /* 判斷是否是中斷號為0~31的錯誤 */
    if (r->int_no < 32)
    {
        /* 顯示發生的異常的描述
         * 本教程中我們簡單地使用一個死迴圈來暫停系統 */
        puts(exception_messages[r->int_no]);
        puts(" Exception. System Halted!\n");
        for (;;);
    }
}

  等一下, 在fault_handler函式的引數中有一個新的結構體struct regs我們還沒有定義。regs向C程式碼展示了堆疊的框架結構。還記得嗎, 我們在"start.asm"中我們將指向堆疊本身的指標壓入堆疊, 這樣我們就可以從處理程式中獲取錯誤碼和中斷號。這種設計方式讓我們能使用一個C程式來處理不同的ISR, 並可以確定發生的是哪個異常或中斷。

  在"system.h"中定義堆疊框架:

system.h

/* 這定義了ISR執行後的堆疊結構 */
struct regs
{
    unsigned int gs, fs, es, ds;      /* 這些段最後壓入 */
    unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax;  /* 通過"pusha"壓入棧中 */
    unsigned int int_no, err_code;
    unsigned int eip, cs, eflags, useresp, ss;   /* 由處理器自動壓入堆疊 */ 
};

  開啟"system.h"檔案, 新增reg結構體的定義和isrs_install函式原型, 以便我們在"main.c"中呼叫。最後, 在main函式中安裝IDT的後面呼叫isrs_install。現在可以在我們新的核心中測試一下我們的異常處理程式了。

  可選操作: 在"main.c"中新增一些測試程式碼, 該程式碼進行除以0操作:

main.c

int main()
{
    int i;
    
    gdt_install();
    idt_install();
    isrs_install();
    init_video();
    puts("Hello World!\n");

    i = 10 / 0;
    putch(i);

    for (;;);
    return 0;
}

當處理器遇到該錯誤, 將會產生"Divide By Zero"異常, 並在螢幕上列印。測試成功後, 你可以刪除這些測試程式碼。測試結果如下:

相關推薦

Bran核心開發教程(bkerndev)-08 中斷服務程式(ISR)

中斷服務程式(ISR)   中斷服務程式(ISR)用於儲存當前處理器的狀態, 並在呼叫核心的C級中斷處理程式之前正確設定核心模式所需的段暫存器。而工作只需要15到20行彙編程式碼來處理, 包括呼叫C中的處理程式。我們還需要將IDT條目指向正確的ISR以正確處理異常。   異常是導致處理器無法正常執行的特殊情況

Bran核心開發教程(bkerndev)-07 中斷描述符表(IDT)

中斷描述符表(IDT)   中斷描述符表(IDT)用於告訴處理器呼叫哪個中斷服務程式(ISR)來處理異常或彙編中的"int"指令。每當裝置完成請求並需要服務事, 中斷請求也會呼叫IDT條目。異常和ISR將在下一節進行詳細的說明。   每一項IDT都與GDT相似, 兩者都有一個基地址, 一個訪問標誌, 而且都長

Bran核心開發教程(bkerndev)-06 全域性描述符表(GDT)

全域性描述符表(GDT)   在386平臺各種保護措施中最重要的就是全域性描述符表(GDT)。GDT為記憶體的某些部分定義了基本的訪問許可權。我們可以使用GDT中的一個索引來生成段衝突異常, 讓核心終止執行異常的程序。現代作業系統大多使用"分頁"的記憶體模式來實現該功能, 它更具通用性和靈活性。GDT還定義了

微信公眾平臺開發教程-微信公眾服務號申請、認證(開通支付)-微信開發教程

微信公眾號服務號與訂閱號的區別 訂閱號: 1、每天可以發1次資訊,每次可以傳送8篇文章(資訊展示在微信公眾號摺疊檔案中) 2、不能申請微信支付功能 3、認證後才可以使用自定義選單功能 4、訂閱號適合:不需要支付功能,以為使用者提供諮詢資訊的企業。 服務號: 1、每月可以發4次資訊,每次可以傳送8

RT-Thread學習筆記(6)- RT-Thread中斷服務程式的書寫注意

在RT-Thread中,中斷服務程式的書寫和在裸機開發的寫法差不多,區別是加入一組API函式,如下: rt_interrupt_enter(); //通知作業系統此時進入中斷狀態 rt

第五章 中斷中斷服務程式

第五章  中斷和中斷服務程式 一,處理器與外圍裝置進行通訊有兩種方式:     1,輪詢(效率低下)     2,中斷 二,中斷原理    當我們在敲擊鍵盤的時候,鍵盤控制器會發送一箇中斷給處理器,告訴OS有中斷產生,處理器停下當前的工作,轉而由內  核呼叫中斷服務程式。(

微信公眾平臺開發教程(十) 訂閱號與服務號的區別

接口文檔 lpad 手機 全部 oauth2.0 spa 上傳 ima lsp 為了消除大家對訂閱號與服務號的疑問,特總結如下: 功能點 介紹 訂閱號 服務號 註冊 註冊賬號 個人信息 個人信息和企業相關信息 展示 在

NeuChar 平臺使用及開發教程(四):使用 NeuChar 的素材服務

  各類公眾號的功能之一就是為使用者提供各類圖文和多媒體的資訊,因此素材是必不可少的。   進入 Neural Cell 設定介面,點選右側【素材管理】按鈕,進入素材管理介面。       目前系統提供了文字、多圖文、圖片三種類型的素材,後續將會有更多型別提供。   點選按鈕

jfinalQ開發教程08-qiao-util.jar:多執行緒和定時任務

多執行緒 多執行緒是java面試中最愛問的一個問題,當然如果工作多年沒準備去面試,正好讓你手寫程式碼,那就只能呵呵了~ QThreadUtil com.uikoo9.util.function.QThreadUtil對java自帶的多執行緒做了封裝,其實java自帶多執行緒已經

微信公眾平臺開發教程-申請微信公眾號訂閱號(服務號)需要哪些材料

微信公眾號服務號與訂閱號的區別 訂閱號: 1、每天可以發1次資訊,每次可以傳送8篇文章(資訊展示在微信公眾號摺疊檔案中) 2、不能申請微信支付功能 3、認證後才可以使用自定義選單功能 4、訂閱號適合:不需要支付功能,以為使用者提供諮詢資訊的企業。 服務號: 1、每月可以發4次資訊,每次可以傳送8

微信公眾平臺開發教程-關於申請微信公眾號訂閱號(服務號)的材料和流程

手機 開發 展示 公眾平臺開發 自定義 聯系 客服 申請微信公眾號 公眾 微信公眾號服務號與訂閱號的區別 訂閱號: 1、每天可以發1次信息,每次可以發送8篇文章(信息展示在微信公眾號折疊文件中) 2、不能申請微信支付功能 3、認證後才可以使用自定義菜單功能 4、訂閱號適合:

小程序粉墨登場 --奉上開發教程及書籍合集

是不是 網易 機器 ive cab 好書推薦 教育 pro ini 微信小程序,簡稱CX,是一種不需要下載安裝即可使用的應用,它實現了應用“觸手可及”的夢想,用戶掃一掃或搜一下即可打開應用。 小程序處於內測階段。全面開放申請後,主體類型為個人、企業

一看就懂的手機APP開發教程

軟件開發 移動開發 界面 服務端 平臺 android 教程 效率 疑問 現在的移動互聯網屬於全民的狂歡時代,是每個人、每個用戶、每個企業的歡暢淋漓的時代,所以APP正在勢如破竹地開拓廣闊的市場。手機APP開發指的是專註於手機應用軟件開發與服務,是當前最為迫切的需求。無獨有

微信公眾號開發教程 微信小程序

微信開發 微信小程序PHP微信公眾平臺開發高級篇http://www.imooc.com/u/197650/courses?sort=publish微信小程序教程 。鏈接:http://pan.baidu.com/s/1slmAwDf 密碼:ciry微信公眾號開發教程 微信小程序

Magento 2開發教程 - 如何添加新產品屬性

資源 ati false bin 一個 magent rod options mod 添加產品屬性是一種在Magento 1 和 Magento 2最受歡迎的業務。 屬性是解決許多與產品相關的實際任務的有力方法。 這是一個相當廣泛的話題,但在這個視頻中,我們將討論添加一個下

Swift開發教程--怎樣自己定義TabBarItem的圖片顯示

mod ren onf 自己 cli bar tarray ++ 教程 在做項目的時候,假設使用系統的UITabBarController的時候,底部的tab自己定義圖片顯示是藍色和灰色的。這不是我們所想要的效果。 假設想顯示自己定義的按下和彈起的圖片效果。這個時候就須

學習 MeteoInfo二次開發教程(一)

mat 沒有 組件 資源管理 img 教程 layer 添加 bbs 來自氣象家園:http://bbs.06climate.com/forum.php?mod=viewthread&tid=6631 按照教程,沒有太大問題,有些是對c#操作不熟悉導致。 1.添加d

學習 MeteoInfo二次開發教程(三)

開發教程 cnblogs false raw class .dll dll inf legend 1.breakList的問題 ((PolygonBreak) aLS.breakList[0]).DrawFill=false; 新的類庫將LegendScheme的brea

微信小程序開發教程目錄

請求 模板消息 小程序開發 沒有 註冊 系列 pick 記錄 logs 本系列教程是自己在工作中使用到而記錄的,沒有順序之分 如有錯誤之處,請給與指正,也不希望誤導了別人 微信小程序開發教程目錄 微信小程序之註冊和入門 微信小程序之HTTPS請求 微信小程序開發之

netty開發教程(一)

prot eventloop 操作 公司 大量 read readline 獲得 github Netty介紹 Netty is an asynchronous event-driven network application framework for