[自製操作系統] 第12回 實現中斷代碼

来源:https://www.cnblogs.com/Lizhixing/archive/2022/07/05/16437788.html
-Advertisement-
Play Games

目錄 一、前景回顧 二、實現中斷框架 三、代碼實現 四、中斷的壓棧和出棧過程分析 五、運行測試 一、前景回顧 前面我們已經講解了中斷的基本知識,接下來要開始進行代碼的實操。代碼主要有兩塊,其中一塊是關於可編程中斷控制器8259A的代碼,另一塊主要是整個中斷的代碼。 二、實現中斷框架 IDT:中斷描述 ...


目錄
一、前景回顧
二、實現中斷框架
三、代碼實現
四、中斷的壓棧和出棧過程分析
五、運行測試

 

一、前景回顧

  前面我們已經講解了中斷的基本知識,接下來要開始進行代碼的實操。代碼主要有兩塊,其中一塊是關於可編程中斷控制器8259A的代碼,另一塊主要是整個中斷的代碼。

二、實現中斷框架

  

  IDT:中斷描述符表。

  gate_desc:中斷描述符。

  intr_entry_table:中斷處理入口函數表。該數組存儲了所有中斷處理入口函數的地址,其核心是通過call [idt_table + %1*4] 的方式在idt_table表中調用中斷處理函數。

  idt_table:中斷處理函數表,該數組存儲的才是真正的中斷處理函數地址。

  general_intr_handler:通用中斷處理函數。

  register_handler:中斷註冊函數,外設(例如定時器timer)通過調用該函數來註冊自定義中斷處理函數(如intr_timer_handler)。

  接下來我們照著上面的流程圖來詳細地講解整個中斷框架的組成以及中斷相關代碼的實現。

  首先我們看到IDT,也就是中斷描述符表,中斷描述符表是有多個中斷描述符組成的,如下圖所示:

  所以我們通過結構體來構造這麼一個描述符:

1 /*中斷門描述符結構體*/
2 struct gate_desc {
3     uint16_t func_offet_low_word; 
4     uint16_t selector;
5     uint8_t dcount;                 //此項為雙字計數欄位,是門描述符中的第4位元組
6     uint8_t attribute;
7     uint16_t func_offet_high_word;
8 };

  中斷向量號通過在IDT中索引得到對應的中斷描述符,解析後便可以得到中斷處理函數的所在地址,隨後CPU便跳轉執行該函數。按理說跳轉執行中斷函數就好了,怎麼又多了一個intr_entry_table表出來?其實我們需要知道,CPU跳轉執行中時,因為我們還會返回,所以需要進行上下文保護,也就是保護當前的寄存器環境,只有保護好了環境資源才可以跳轉過去執行函數。又因為使用彙編來編寫中斷函數比較繁瑣,可讀性差,所以我們在C語言環境下進行中斷函數編寫。總的來說,當CPU拿到中斷向量號後,在IDT中索引得到中斷處理函數地址(intr%1entry)(這個其實並不是真正的中斷處理函數地址,嚴格意義上來說應該是進入中斷處理函數的函數,在這個函數中進行上下文環境保護,隨後才跳轉執行真正的中斷處理函數),來看intr%1entry的構成:

intr%1entry:
    %2
    push ds
    push es
    push fs
    push gs
    pushad

    ;8259A相關設置
    mov al, 0x20
    out 0xa0, al
    out 0x20, al

    push %1                       ;將中斷號壓棧
    call [idt_table + %1*4]       ;調用中斷處理函數
    jmp intr_exit                 ;退出中斷

  如上所示,進入該函數後,這個%2是什麼呢?它是一個巨集定義,本質上就是起了一個占位作用。因為CPU在進入到中斷後會向棧中壓入部分寄存器環境,這是CPU自動完成的,不需要我們手動編寫,保存的寄存器名稱及順序是:

  1、如果發生特權級轉移,此時要把低特權級的棧段選擇子ss及棧指針esp保存到棧中。

  2、壓入標誌寄存器eflags。

  3、壓入返回地址cs和eip。

  4、如果此中斷沒有相應的錯誤碼,至此,CPU把寄存器壓棧的工作就完成了,如果有錯誤碼,CPU在壓入eip之後還會壓入錯誤碼。如下圖所示:

  所以為了保證棧頂指針的一致,在有錯誤碼壓入時,%2其實就是一個nop指令,撒也不做。如果沒有錯誤碼壓入,那麼%2就是push 0的指令,這樣就能保證棧頂指針的一致性(詳細代碼請看後續代碼實現)。這裡我就說這麼多,詳情請查看原書《操作系統真象還原》p320~323頁。

  隨後通過push和pushad指令將當前環境下的8個通用寄存器和4個段寄存器給保存起來,隨後是8259A相關設置,這裡就多說了。然後是將中斷號壓棧。最後通過call指令調用中斷處理函數(這次真是中斷處理函數了,如假包換),中斷處理函數我們是使用C語言來編寫的,因為這樣便於閱讀和修改。我們將所有的中斷處理函數存放在idt_table中,為方便查詢,並且後面註冊中斷處理函數時,只需要往idt_table中插入我們編寫的中斷處理函數就好了。可以看到idt_table中預設的中斷處理函數都是general_intr_handler,我們通過register_handler函數給定時器註冊了名為intr_timer_handler的中斷處理函數。最後中斷處理函數執行完畢後通過jmp intr_exit函數就完成環境恢復,且回到中斷發生時的地址處繼續執行代碼。

三、代碼實現

  因為涉及到對於可編程中斷控制器8259A的埠的讀寫,所以我們在project/lib/kernel目錄下新建一個名為io.h的文件,在該文件中定義對埠的讀寫函數。

 1 /**************     機器模式   ***************
 2      b -- 輸出寄存器QImode名稱,即寄存器中的最低8位:[a-d]l。
 3      w -- 輸出寄存器HImode名稱,即寄存器中2個位元組的部分,如[a-d]x。
 4 
 5      HImode
 6          “Half-Integer”模式,表示一個兩位元組的整數。 
 7      QImode
 8          “Quarter-Integer”模式,表示一個一位元組的整數。 
 9 *******************************************/ 
10 
11 #ifndef  __LIB_IO_H
12 #define  __LIB_IO_H
13 #include "stdint.h"
14 
15 /* 向埠port寫入一個位元組*/
16 static inline void outb(uint16_t port, uint8_t data) {
17 /*********************************************************
18  a表示用寄存器al或ax或eax,對埠指定N表示0~255, d表示用dx存儲埠號, 
19  %b0表示對應al,%w1表示對應dx */ 
20    asm volatile ( "outb %b0, %w1" : : "a" (data), "Nd" (port));    
21 /******************************************************/
22 }
23 
24 /* 將addr處起始的word_cnt個字寫入埠port */
25 static inline void outsw(uint16_t port, const void* addr, uint32_t word_cnt) {
26 /*********************************************************
27    +表示此限制即做輸入又做輸出.
28    outsw是把ds:esi處的16位的內容寫入port埠, 我們在設置段描述符時, 
29    已經將ds,es,ss段的選擇子都設置為相同的值了,此時不用擔心數據錯亂。*/
30    asm volatile ("cld; rep outsw" : "+S" (addr), "+c" (word_cnt) : "d" (port));
31 /******************************************************/
32 }
33 
34 /* 將從埠port讀入的一個位元組返回 */
35 static inline uint8_t inb(uint16_t port) {
36    uint8_t data;
37    asm volatile ("inb %w1, %b0" : "=a" (data) : "Nd" (port));
38    return data;
39 }
40 
41 /* 將從埠port讀入的word_cnt個字寫入addr */
42 static inline void insw(uint16_t port, void* addr, uint32_t word_cnt) {
43 /******************************************************
44    insw是將從埠port處讀入的16位內容寫入es:edi指向的記憶體,
45    我們在設置段描述符時, 已經將ds,es,ss段的選擇子都設置為相同的值了,
46    此時不用擔心數據錯亂。*/
47    asm volatile ("cld; rep insw" : "+D" (addr), "+c" (word_cnt) : "d" (port) : "memory");
48 /******************************************************/
49 }
50 
51 #endif
io.h

  在project/kernel目錄下新建名為interrupt.c、interrupt.h、kernel.S、global.h文件。 

  1 #include "interrupt.h"
  2 #include "stdint.h"
  3 #include "global.h"
  4 #include "io.h"
  5 #include "print.h"
  6 
  7 #define IDT_DESC_CNT 0x81               //目前支持的中斷數
  8 
  9 #define PIC_M_CTRL  0x20                //主片的控制埠是0x20
 10 #define PIC_M_DATA  0x21                //主片的數據埠是0x21
 11 #define PIC_S_CTRL  0xa0                //從片的控制埠是0xa0
 12 #define PIC_S_DATA  0xa1                //從片的數據埠是0xa1
 13 
 14 
 15 /*中斷門描述符結構體*/
 16 struct gate_desc {
 17     uint16_t func_offet_low_word; 
 18     uint16_t selector;
 19     uint8_t dcount;                 //此項為雙字計數欄位,是門描述符中的第4位元組
 20     uint8_t attribute;
 21     uint16_t func_offet_high_word;
 22 };
 23 
 24 /*定義IDT表*/
 25 static struct gate_desc idt[IDT_DESC_CNT];
 26 extern intr_handler intr_entry_table[IDT_DESC_CNT];
 27 
 28 char *intr_name[IDT_DESC_CNT];                //用於保存異常的名字
 29 intr_handler idt_table[IDT_DESC_CNT];         //定義中斷處理程式數組
 30 
 31 /*通用的中斷處理函數,一般用在異常出現時的處理*/
 32 static void general_intr_handler(uint8_t vec_nr)
 33 {
 34     if (vec_nr == 0x27 || vec_nr == 0x2f) {
 35         return ;
 36     }
 37 
 38     /*將游標置為0,從屏幕左上角清出一片列印異常信息的區域,方便閱讀*/
 39     set_cursor(0);
 40     int cursor_pos = 0;
 41     while (cursor_pos < 320) {
 42         put_char(' ');
 43         cursor_pos++;
 44     }
 45 
 46     set_cursor(0);
 47     put_str("!!!!!!!!!!!exception message begin!!!!!!!!!");
 48     set_cursor(88);
 49     put_str(intr_name[vec_nr]);
 50     
 51     //如果為pagefault,將缺失的地址列印出來並且懸停
 52     if (vec_nr == 14) { 
 53         int page_fault_vaddr = 0;
 54         asm volatile ("movl %%cr2, %0": "=r" (page_fault_vaddr)); //cr2存放造成pagefault的虛擬地址
 55         put_str("\npage fault addr is: ");put_int(page_fault_vaddr);
 56     }
 57     put_str("!!!!!!!!!!!exception message end!!!!!!!!!!");
 58     //能進入中斷處理程式就表示已經處於關中斷的情況下,不會出現進程調度的情況,因此下麵的死迴圈可以一直執行
 59     while (1);
 60 }
 61 
 62 /*完成一般中斷處理函數註冊及異常名稱註冊*/
 63 static void exception_init(void)
 64 {
 65     int i;
 66     for (i = 0; i < IDT_DESC_CNT; i++) {
 67         idt_table[i] = general_intr_handler;
 68         intr_name[i] = "unknow";
 69     }
 70     intr_name[0] = "#DE Divide Error";
 71     intr_name[1] = "#DB Debug Exception";
 72     intr_name[2] = "NMI Interrupt";
 73     intr_name[3] = "#BP Breakpoint Exception";
 74     intr_name[4] = "#OF Overflow Exception";
 75     intr_name[5] = "#BR BOUND Range Exceeded Exception";
 76     intr_name[6] = "#UD Invalid Opcode Exception";
 77     intr_name[7] = "#NM Device Not Available Exception";
 78     intr_name[8] = "#DF Double Fault Exception";
 79     intr_name[9] = "Coprocessor Segment Overrun";
 80     intr_name[10] = "#TS Invalid TSS Exception";
 81     intr_name[11] = "#NP Segment Not Present";
 82     intr_name[12] = "#SS Stack Fault Exception";
 83     intr_name[13] = "#GP General Protection Exception";
 84     intr_name[14] = "#PF Page-Fault Exception";
 85     // intr_name[15] 第15項是intel保留項,未使用
 86     intr_name[16] = "#MF x87 FPU Floating-Point Error";
 87     intr_name[17] = "#AC Alignment Check Exception";
 88     intr_name[18] = "#MC Machine-Check Exception";
 89     intr_name[19] = "#XF SIMD Floating-Point Exception";
 90 }
 91 
 92 /* 初始化可編程中斷控制器8259A */
 93 static void pic_init(void) {
 94    /* 初始化主片 */
 95    outb(PIC_M_CTRL, 0x11);   // ICW1: 邊沿觸發,級聯8259, 需要ICW4.
 96    outb(PIC_M_DATA, 0x20);   // ICW2: 起始中斷向量號為0x20,也就是IR[0-7] 為 0x20 ~ 0x27.
 97    outb(PIC_M_DATA, 0x04);   // ICW3: IR2接從片. 
 98    outb(PIC_M_DATA, 0x01);   // ICW4: 8086模式, 正常EOI
 99 
100    /* 初始化從片 */
101    outb(PIC_S_CTRL, 0x11);    // ICW1: 邊沿觸發,級聯8259, 需要ICW4.
102    outb(PIC_S_DATA, 0x28);    // ICW2: 起始中斷向量號為0x28,也就是IR[8-15] 為 0x28 ~ 0x2F.
103    outb(PIC_S_DATA, 0x02);    // ICW3: 設置從片連接到主片的IR2引腳
104    outb(PIC_S_DATA, 0x01);    // ICW4: 8086模式, 正常EOI
105 
106 
107    /*打開時鐘中斷*/
108    outb(PIC_M_DATA, 0xfe);
109    outb(PIC_S_DATA, 0xff);
110 
111    put_str("pic_init done\n");
112 }
113 
114 
115 /*創建中斷門描述符*/
116 static void make_idt_desc(struct gate_desc *p_gdesc, uint8_t attr, intr_handler function)
117 {
118     p_gdesc->func_offet_low_word = (uint32_t)function & 0x0000FFFF;
119     p_gdesc->selector = SELECTOR_K_CODE;
120     p_gdesc->dcount = 0;
121     p_gdesc->attribute = attr;
122     p_gdesc->func_offet_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
123 }
124 
125 /*初始化中斷描述符表*/
126 static void idt_desc_init(void)
127 {       
128     int i = 0;
129     for (i = 0; i <IDT_DESC_CNT; i++) {
130         make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
131     }
132 
133     put_str("ide_desc_init done\n");
134 }
135 
136 /*完成中斷有關的所有初始化工作*/
137 void idt_init(void)
138 {
139     put_str("idt_init start\n");
140     idt_desc_init();
141     exception_init();
142     pic_init();
143 
144     /*載入idt*/
145     uint64_t idt_operand = (sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16);
146     asm volatile("lidt %0" : : "m"(idt_operand));
147     put_str("idt_init done\n");
148 } 
149 
150 
151 /*在中斷處理程式數組第vector_no個元素中註冊安裝中斷處理程式*/
152 void register_handler(uint8_t vector_no, intr_handler function)
153 {
154     idt_table[vector_no] = function;
155 }
interrupt.c
1 #ifndef  __KERNEL_INTERRUPT_H
2 #define  __KERNEL_INTERRUPT_H
3 #include "stdint.h"
4 
5 typedef void* intr_handler;
6 void register_handler(uint8_t vector_no, intr_handler function);
7 void idt_init(void);
8 #endif
interrupt.h
 1 [bits 32]
 2 %define ERROR_CODE nop
 3 %define ZERO push 0
 4 
 5 extern put_str        ;聲明外部函數
 6 extern idt_table      ;聲明外部中斷函數數組
 7 
 8 section .data
 9 global intr_entry_table
10 intr_entry_table:
11 
12 %macro VECTOR 2
13 section .text
14 intr%1entry:
15     %2
16     push ds
17     push es
18     push fs
19     push gs
20     pushad
21 
22     ;8259A相關設置
23     mov al, 0x20
24     out 0xa0, al
25     out 0x20, al
26 
27     push %1                       ;將中斷號壓棧
28     call [idt_table + %1*4]       ;調用中斷處理函數
29     jmp intr_exit                 ;退出中斷
30 
31 section .data
32         dd intr%1entry
33 %endmacro
34 
35 section .text
36 global intr_exit
37 intr_exit:
38     add esp, 4
39     popad
40     pop gs
41     pop fs
42     pop es
43     pop ds
44     add esp, 4
45     iretd
46 
47 VECTOR 0x00, ZERO
48 VECTOR 0x01, ZERO
49 VECTOR 0x02, ZERO
50 VECTOR 0x03, ZERO
51 VECTOR 0x04, ZERO
52 VECTOR 0x05, ZERO
53 VECTOR 0x06, ZERO
54 VECTOR 0x07, ZERO
55 VECTOR 0x08, ERROR_CODE
56 VECTOR 0x09, ZERO
57 VECTOR 0x0A, ERROR_CODE
58 VECTOR 0x0B, ERROR_CODE
59 VECTOR 0x0C, ERROR_CODE
60 VECTOR 0x0D, ERROR_CODE
61 VECTOR 0x0E, ERROR_CODE
62 VECTOR 0x0F, ZERO
63 VECTOR 0x10, ZERO
64 VECTOR 0x11, ERROR_CODE
65 VECTOR 0x12, ZERO
66 VECTOR 0x13, ZERO
67 VECTOR 0x14, ZERO
68 VECTOR 0x15, ZERO
69 VECTOR 0x16, ZERO
70 VECTOR 0x17, ZERO
71 VECTOR 0x

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 發佈一個用於測試 Webservice 和資料庫連接的工具。 ...
  • 來源:blog.csdn.net/dabusiGin/article/details/105483426 錯誤的結論 在網上搜索HashMap中變數modCount的作用時,大部分的解釋都是這樣: Fail-Fast 機制 我們知道 java.util.HashMap 不是線程安全的,因此如果在使用 ...
  • 使用背景: 項目中需要用的富文本框去上傳視頻,圖片的話大部分都是可以的。相對來說,國外的富文本框很成熟。但鑒於文檔是英語,這裡使用了百度的富文本框。 採用的api的方式,調用介面進行上傳文件。話不多說,開擼! 準備: 創建一個.net mvc的項目。下載百度富文本框.net 版本的js文件。 創建項 ...
  • 一:背景 1. 講故事 前幾天有位朋友在 B站 加到我,說他的程式出現了 線程數 爆高的問題,讓我幫忙看一下怎麼回事,截圖如下: 說來也奇怪,這些天碰到了好幾起關於線程數無緣無故的爆高,不過那幾個問題比這一篇要複雜的多,主要涉及到非托管層面,分享這一篇的目的主要是它很有代表性,很有必要。 閑話不多說 ...
  • 效果 概述 最近有個小需求要用雙滑塊表示一個取值範圍,於是就簡單做了個用戶控制項,在此記錄下. 使用矩形Rectangle表示範圍,橢圓Ellipse表示滑塊,使用Canvas控制滑塊的左右移動. 橢圓的滑鼠按下事件里強制獲取滑鼠事件焦點,避免移動過快或移出控制項範圍時,滑塊就不跟著跑了.橢圓的滑鼠抬起 ...
  • BackgroundWorker: 1.定義:是.net里用來執行多線程任務的控制項,它允許編程者在一個單獨的線程上執行一些操作。 2.使用場景:耗時的操作(如下載和資料庫事務)長時間運行時可能會導致用戶界面 (UI) 始終處於停止響應狀態。如果我們需要能進行響應的用戶界面,而且面臨與這類操作相關的長 ...
  • 網路的概念: 電腦網路:一組電腦或網路設備通過媒介(有線或無線)相連,按照一定的規則進行通信的集合。 網路作用範圍分類: 廣域網:WAN--Wide Area Network 城域網:MAN Metropolitan Area Network 區域網:LAN Local Area Network ...
  • 初識 DEV C++ 首先小衚衕學自己並不是科班出身,不是電腦或者互聯網相關專業的的學生,我本科期間是車輛工程專業,偏機械一點。 本科期間對編程語言的唯一認識就是C,其實也不算是,準確的是DEV C++,但大家都懂得,本科的通識課是可以忽略不計的。 再之後就到了研究生學校這邊,考研的時候就想著能跳 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...