主頁 > 作業系統 > [自制作業系統] 第12回 實作中斷代碼

[自制作業系統] 第12回 實作中斷代碼

2022-07-07 12:47:32 作業系統

目錄
一、前景回顧
二、實作中斷框架
三、代碼實作
四、中斷的壓堆疊和出堆疊程序分析
五、運行測驗

 

一、前景回顧

  前面我們已經講解了中斷的基本知識,接下來要開始進行代碼的實操,代碼主要有兩塊,其中一塊是關于可編程中斷控制器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 0x18, ZERO
72 VECTOR 0x19, ZERO
73 VECTOR 0x1A, ZERO
74 VECTOR 0x1B, ZERO
75 VECTOR 0x1C, ZERO
76 VECTOR 0x1D, ZERO
77 VECTOR 0x1E, ERROR_CODE
78 VECTOR 0x1F, ZERO
79 VECTOR 0x20, ZERO   ;時鐘中斷對應的入口
80 VECTOR 0x21, ZERO    ;鍵盤中斷
81 VECTOR 0x22, ZERO    ;級聯
82 VECTOR 0x23, ZERO    ;串口2
83 VECTOR 0x24, ZERO    ;串口1
84 VECTOR 0x25, ZERO    ;并口2
85 VECTOR 0x26, ZERO    ;軟盤
86 VECTOR 0x27, ZERO    ;并口1
87 VECTOR 0x28, ZERO    ;實時時鐘
88 VECTOR 0x29, ZERO    ;重定向
89 VECTOR 0x2A, ZERO    ;保留
90 VECTOR 0x2B, ZERO    ;保留
91 VECTOR 0x2C, ZERO    ;ps/2 滑鼠
92 VECTOR 0x2D, ZERO   ;fpu 浮點單元例外
93 VECTOR 0x2E, ZERO   ;硬碟
94 VECTOR 0x2F, ZERO   ;保留
95 
96 
97 
98 
99     
kernel.S
 1 #ifndef  __KERNEL_GLOBAL_H
 2 #define  __KERNEL_GLOBAL_H
 3 #include "stdint.h"
 4 
 5 /************ GDT描述符屬性**************/
 6 #define RPL0 0
 7 #define RPL1 1
 8 #define RPL2 2
 9 #define RPL3 3
10 
11 #define TI_GDT 0
12 #define TI_LDT 1
13 
14 #define DESC_G_4K    1
15 #define DESC_D_32    1
16 #define DESC_L        0
17 #define DESC_AVL    0
18 #define DESC_P        1
19 #define DESC_DPL_0    0
20 #define DESC_DPL_1    1
21 #define DESC_DPL_2    2
22 #define DESC_DPL_3    3
23 
24 #define DESC_S_CODE      1
25 #define DESC_S_DATA        DESC_S_CODE
26 #define DESC_S_SYS        0
27 #define DESC_TYPE_CODE    8
28 
29 #define DESC_TYPE_DATA 2
30 #define DESC_TYPE_TSS  9
31 
32 #define SELECTOR_K_CODE       ((1 << 3) + (TI_GDT << 2) + RPL0)
33 #define SELECTOR_K_DATA       ((2 << 3) + (TI_GDT << 2) + RPL0)
34 #define SELECTOR_K_STACK      SELECTOR_K_DATA
35 #define SELECTOR_K_GS         ((3 << 3) + (TI_GDT << 2) + RPL0)
36 
37 /*用戶行程的段描述符*/
38 #define SELECTOR_U_CODE        ((5 << 3) + (TI_GDT << 2) + RPL3)
39 #define SELECTOR_U_DATA          ((6 << 3) + (TI_GDT << 2) + RPL3)
40 #define SELECTOR_U_STACK      SELECTOR_U_DATA
41 
42 
43 #define GDT_ATTR_HIGH              ((DESC_G_4K << 7) + (DESC_D_32 << 6) + (DESC_L << 5) + (DESC_AVL << 4))
44 #define GDT_CODE_ATTR_LOW_DPL3    ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_CODE << 4) + DESC_TYPE_CODE)
45 #define GDT_DATA_ATTR_LOW_DPL3    ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_DATA << 4) + DESC_TYPE_DATA)
46 
47 
48 
49 
50 
51 /******************** IDT描述符屬性**********************/
52 #define IDT_DESC_P          1          //1表示在記憶體中
53 #define IDT_DESC_DPL0       0   
54 #define IDT_DESC_DPL3       3  
55 #define IDT_DESC_32_TYPE   0xE
56 #define IDT_DESC_16_TYPE   0x6
57 
58 #define IDT_DESC_ATTR_DPL0 \
59         ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE)
60 #define IDT_DESC_ATTR_DPL3 \
61         ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE)
62 
63 
64 
65 /******************** TSS描述符屬性**********************/
66 #define     TSS_DESC_D 0
67 #define  TSS_ATTR_HIGH ((DESC_G_4K << 7) + (TSS_DESC_D << 6) + (DESC_L << 5) + (DESC_AVL << 4) + 0x0)
68 #define  TSS_ATTR_LOW  ((DESC_P << 7) + (DESC_DPL_0 << 5) + (DESC_S_SYS << 4) + DESC_TYPE_TSS)
69 
70 #define  SELECTOR_TSS  ((4 << 3) + (TI_GDT << 2) + RPL0)
71 
72 
73 /*定義GDT中描述符的結構*/
74 struct gdt_desc {
75     uint16_t limit_low_word;
76     uint16_t base_low_word;
77     uint8_t  base_mid_byte;
78     uint8_t  attr_low_byte;
79     uint8_t  limit_high_attr_high;
80     uint8_t  base_high_byte;
81 };
82 
83 
84 
85 #define EFLAGS_MBS       (1 << 1)   //此項必須設定
86 #define EFLAGS_IF_1      (1 << 9)   //開中斷
87 #define EFLAGS_IF_0      0          //關中斷
88 #define EFLAGS_IOPL_3    (3 << 12)
89 
90 #define EFLAGS_IOPL_0    (0 << 12)
91 
92 #define NULL  ((void *)0)
93 #define DIV_ROUND_UP(X, STEP)  ((X + STEP - 1) / (STEP))
94 
95 #define PG_SIZE 4096
96 
97 #endif
global.h

  此外還需要在project/kernel目錄下新建timer.c和timer.h檔案,因為我們本回測驗會用上定時器中斷,所以關于定時器8253的初始化以及中斷函式注冊就在這兩個檔案中實作了,定時器的介紹就請參閱原書《作業系統真象還原》P346~356頁,

 1 #include "timer.h"
 2 #include "io.h"
 3 #include "print.h"
 4 #include "interrupt.h"
 5 
 6 #define IRQ0_FREQUENCY         100
 7 #define INPUT_FREQUENCY     1193180
 8 #define COUNTER0_VALUE        INPUT_FREQUENCY / IRQ0_FREQUENCY
 9 #define COUNTER0_PORT        0x40
10 #define COUNTER0_NO         0
11 #define COUNTER_MODE        2
12 #define READ_WRITE_LATCH    3
13 #define PIT_COUNTROL_PORT    0x43
14 
15 
16 static void intr_timer_handler(uint8_t vec_nr) {
17     put_str("int vector: 0x");
18     put_int(vec_nr);
19     put_char('\n');
20 }
21 
22 static void frequency_set(uint8_t counter_port ,uint8_t counter_no,uint8_t rwl,uint8_t counter_mode,uint16_t counter_value)
23 {
24     outb(PIT_COUNTROL_PORT, (uint8_t) (counter_no << 6 | rwl << 4 | counter_mode << 1));
25     outb(counter_port, (uint8_t)counter_value);
26     outb(counter_port, (uint8_t)counter_value >> 8);
27 } 
28 
29 void timer_init(void)
30 {
31     put_str("timer_init start!\n");
32     frequency_set(COUNTER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);
33     register_handler(0x20, intr_timer_handler);
34     put_str("timer_init done!\n");
35 }
timer.c
1 #ifndef  __KERNEL_TIMER_H
2 #define  __KERNEL_TIMER_H
3 #include "stdint.h"
4 
5 void timer_init(void);
6 static void intr_timer_handler(uint8_t vec_nr);
7 
8 #endif
timer.h

   最后,我們在project/kernel目錄下新建init.c和init.h檔案,init.c中有一個init.all函式,該函式用來包含所有初始化函式,比如到目前為止的中斷初始化以及定時器初始化,后面還會有更多的初始化函式加入進來,

 1 #include "init.h"
 2 #include "print.h"
 3 #include "interrupt.h"
 4 #include "timer.h"
 5 
 6 void init_all(void)
 7 {
 8     put_str("init_all\n");
 9     idt_init();     //初始化中斷
10     timer_init();   //初始化定時器
11 }
init.c
1 #ifndef  __KERNEL_INIT_H
2 #define  __KERNEL_INIT_H
3 void init_all(void);
4 #endif
init.h

四、中斷的壓堆疊和出堆疊程序分析

  下面我們通過一個例子來分析一下整個中斷的壓堆疊和出堆疊程序,從而助于我們更好地了解整個中斷程序,

  這天CPU正在埋頭苦干,突然,外設定時器計數溢位了,我們的中斷控制器8259A接收到這一訊息,馬上火急火燎地跑來通知CPU(給予CPU當前發生中斷的中斷號:0x20),CPU知道這一訊息,腦子突然一轉,就這一瞬間的功夫,就做了兩件事:1、判斷是否有特權級轉移:當前被中斷的行程,也就是CPU正在做的事,處于0特權級,而中斷處理程式也是0特權級,所以沒有發生特權級轉移,(如果發生了特權級轉移,此時需要把低特權級的堆疊段選擇子SS和堆疊指標esp保存在堆疊中)2、在當前堆疊中壓入標志暫存器eflags、壓入回傳地址cs和eip(先壓入cs,后壓入eip,這個地址就是CPU正在執行的任務的地址,等CPU執行完中斷后需要繼續回來執行任務,所以需要把地址記下來,免得CPU找不到回來的路),如果對應的中斷有相應的錯誤碼,還會將錯誤碼壓入堆疊中,否則就結束(關于中斷錯誤碼,忘記的同學可以回看上一回),

  隨后,根據中斷號,CPU在IDT表索引找到了對應的中斷描述符,由該描述符尋找到進入中斷處理函式的函式地址,繼而跳轉過去,函式內容如下:

 1 intr%1entry:
 2     %2
 3     push ds
 4     push es
 5     push fs
 6     push gs
 7     pushad
 8 
 9     ;8259A相關設定
10     mov al, 0x20
11     out 0xa0, al
12     out 0x20, al
13 
14     push %1                       ;將中斷號壓堆疊
15     call [idt_table + %1*4]       ;呼叫中斷處理函式
16     jmp intr_exit                 ;退出中斷

  在該函式中,首先,如果前面已經壓入了錯誤碼,那么%2的作用就是執行nop,延時,對堆疊和暫存器都沒有影響,如果沒有壓入錯誤碼,就執行push 0的指令,這樣做的目的只是為了兼容兩種不同的壓堆疊方式,隨后便是將4個段暫存器ds、es、fs、gs和8個32位的通用暫存器壓堆疊,保護現場,最后通過push %1將中斷號壓入堆疊,通過呼叫call[idt_table + %1*4]進入真正的中斷處理函式,這里又會壓入當前函式的回傳地址cs和eip,所以此時的堆疊的情況如下左圖所示,

  中斷處理函式就是我們定義的intr_timer_handler函式,我們在該函式中列印一次中斷號0x20,隨后通過堆疊內的地址回傳,此時來到了jmp intr_exit命令,我們來看看intr_exit指的是什么,以及此時堆疊的情況,如上右圖,

1 static void intr_timer_handler(uint8_t vec_nr) {
2     put_str("int vector: 0x");
3     put_int(vec_nr);
4     put_char('\n');
5 }
1 intr_exit:
2     add esp, 4
3     popad
4     pop gs
5     pop fs
6     pop es
7     pop ds
8     add esp, 4
9     iretd

  首先通過add esp,4將堆疊頂指標加4,也就是跳過0x20這個地方,0x20是我們前面手動壓入的中斷號,隨后通過popad將8個32位通用暫存器彈出,再通過pop指令將gs、fs、es和彈出,add esp,4跳過堆疊中0的位置,這個0也是我們前面手動壓入的,此時堆疊的情況如下:

  這里還剩下iretd指令沒有執行了,這個指令的作用就是將eip、cs和eflags彈出,前提是當前堆疊頂為eip,也就是我們必須保證堆疊頂是eip,才能使該指令執行正確,那么此時我們堆疊頂的確是eip,該指令執行完畢后,通過前面壓入的cs和eip,也就是函式回傳地址,CPU又回去繼續執行前面被中斷所打斷的任務了,講到這里我們就將整個中斷壓堆疊和出堆疊的程序講解完畢了,

  啰嗦了這么多,總算結束了,這里還沒有涉及到有特權級轉換的情況,其實區別不會太大,等到了行程的章節再來補充,

五、運行測驗

  更新一下我們的main.c檔案:

 1 #include "print.h"
 2 #include "init.h"
 3 #include "timer.h"
 4 #include "interrupt.h"
 5 
 6 void main(void)
 7 {
 8     put_str("I am Kernel\n");
 9     init_all(); 
10     asm volatile("sti");
11     while (1);
12 }
main.c

  接下來通過這一長串的操作步驟將程式編譯鏈接下載到磁盤中去:

gcc -m32 -I project/kernel/ -I project/lib/kernel/ -c -fno-builtin project/kernel/timer.c -o project/kernel/timer.o
gcc -m32 -I project/kernel/ -I project/lib/kernel/ -c -fno-builtin project/kernel/main.c -o project/kernel/main.o 
gcc -m32 -I project/kernel/ -I project/lib/kernel/ -c -fno-builtin -o  project/kernel/init.o project/kernel/init.c
gcc -m32 -I ./project/kernel/ -I ./project/lib/kernel/ -c -fno-builtin -o ./project/kernel/interrupt.o ./project/kernel/interrupt.c
nasm -f elf -o ./project/lib/kernel/print.o ./project/lib/kernel/print.S
nasm -f elf -o ./project/kernel/kernel.o ./project/kernel/kernel.S
ld -m elf_i386 -Ttext 0xc0001500 -e main -o build/kernel.bin project/kernel/main.o project/kernel/init.o project/kernel/interrupt.o project/lib/kernel/print.o project/kernel/kernel.o project/kernel/timer.o
dd if=./build/kernel.bin of=./hd60M.img bs=512 count=200 seek=9 conv=notrunc

  啟動bochs,可以看到int vector:0x20的出現,這里是因為我們初始化定時器后,讓定時器在每次定時溢位后觸發中斷(定時器的中斷號為0x20),在定時器的中斷處理函式中,我們列印定時器的中斷號,這樣也就證明了我們的程式沒有問題,

  

   好了,本回到此結束了,可以看到代碼一多起來后,光是編譯下載都挺麻煩的,所以接下來我們需要改進一下,預知后事如何,請看下回分解,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/498513.html

標籤:嵌入式

上一篇:Teorex Inpaint for Mac(圖片去水印軟體)

下一篇:淺聊我和一些編程語言的緣分

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more