天天看點

核心代碼閱讀(14) - 中斷中斷,異常和系統調用

今天周日天朗氣清惠風和暢。開始進入核心中斷代碼的學習。

中斷,異常和系統調用

3種機制:

1) 中斷(interrupt)

中斷向量: 外部硬體或者軟體設定

2) 陷阱 (trap)

中斷向量: 由彙編 int n, n是中斷向量

3) 異常 (exception)

中斷向量: 是在CPU内部硬體

X86 CPU中斷的硬體支援

x86保護模式的中斷機制

1) 中斷服務程式的入口變成一個描述項 - gate。
2) 發生CPU狀态的切換都要經過一道門。
3) 4種門
   task gate
   interrupt gate
   trap gate
   call gates
4) 允許在中斷發生後進行一次程序切換,這樣提供了一種機制:所有的中斷都可以交給一個程序來處理。      

中斷堆棧切換

運作級别不一緻時,會發生堆棧的切換:
1) 當CPU在使用者态時,發生中斷,則進行中斷的代碼會在一個新的棧上。
2) 當CPU在核心态時,發生中斷,則進行中斷的代碼會在原來的棧上。      

IDTR 中斷向量寄存器

1) 中斷服務程式的入口變成一個描述項 - gate。
2) 發生CPU狀态的切換都要經過一道門。
3) 4種門
   task gate
   interrupt gate
   trap gate
   call gates
4) 允許在中斷發生後進行一次程序切換,這樣提供了一種機制:所有的中斷都可以交給一個程序來處理。      

運作級别不一緻時,會發生堆棧的切換:
1) 當CPU在使用者态時,發生中斷,則進行中斷的代碼會在一個新的棧上。
2) 當CPU在核心态時,發生中斷,則進行中斷的代碼會在原來的棧上。      

IDTR + v(中斷向量号) -> 中斷門
GDTR + 中斷門(段選擇碼和偏移) -> 中斷服務程式的入口      

中斷向量表IDT的初始化

核心在初始化階段,完成對頁式記憶體管理的初始化後,開始初始化中斷trap_init和init_IRQ。

trap_init - CPU專用和系統調用的陷阱門設定

trap_init - 初始化系統保留的中斷

void __init trap_init(void)
     {
         set_trap_gate(0,&divide_error);
         set_trap_gate(1,&debug);
         set_intr_gate(2,&nmi);
         set_system_gate(3,&int3);        /* int3-5 can be called from all */
         set_system_gate(4,&overflow);
         set_system_gate(5,&bounds);
         set_trap_gate(6,&invalid_op);
         set_trap_gate(7,&device_not_available);
         set_trap_gate(8,&double_fault);
         set_trap_gate(9,&coprocessor_segment_overrun);
         set_trap_gate(10,&invalid_TSS);
         set_trap_gate(11,&segment_not_present);
         set_trap_gate(12,&stack_segment);
         set_trap_gate(13,&general_protection);
         set_trap_gate(14,&page_fault);
         set_trap_gate(15,&spurious_interrupt_bug);
         set_trap_gate(16,&coprocessor_error);
         set_trap_gate(17,&alignment_check);
         set_trap_gate(18,&machine_check);
         set_trap_gate(19,&simd_coprocessor_error);
         set_system_gate(SYSCALL_VECTOR,&system_call);
         set_call_gate(&default_ldt[0],lcall7);
         set_call_gate(&default_ldt[4],lcall27);
         /*
          * Should be a barrier for any external CPU state.
          */
         cpu_init();
     }      
1) 0号中斷就是除以0。
 2) 14号中斷就是 page_fault。
           每當系統在處理頁式映射進行不下去了就會産生 0xe 中斷, 系統必須在0xe中斷上進行缺頁服務.
 3) set_system_gate(SYSCALL_VECTOR,&system_call);
           #define SYSCALL_VECTOR                0x80
           0x80 系統調用中斷.
 4) set_trap_gate(unsigned int n, void *addr) { set_gate(idt_table+n,15,0,addr); }      
trap_init 之 中斷類型和權限DPL
struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };
     
     void set_intr_gate(unsigned int n, void *addr)
     {
         _set_gate(idt_table+n,14,0,addr);
     }
     static void __init set_trap_gate(unsigned int n, void *addr)
     {
         _set_gate(idt_table+n,15,0,addr);
     }    
     static void __init set_system_gate(unsigned int n, void *addr)
     {
         _set_gate(idt_table+n,15,3,addr);
     }      
1) idt_table 是一個全局的數組.
 2) 可以看到3種中斷的初始化都是調用 _set_gate函數.
 3) _set_gate(idt_table+n,15,0,addr);
           第一個參數是中斷類型, 15是trap, 14是中斷.
           第二個參數是權限, 0是最高的核心态, 3是使用者态.
 4) system_gate 的類型是trap, 權限是3, 允許使用者态調用INT n進入.
 5) 陷阱門和中斷門除了在DPL上不同外,還有就是trap是可中斷的, 中斷是不能中斷的(進入中斷服務子程式後會屏蔽中斷)。      

trap_init 之 _set_gate

#define _set_gate(gate_addr,type,dpl,addr) \
     do { \
       int __d0, __d1; \
       __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \
         "movw %4,%%dx\n\t" \
         "movl %%eax,%0\n\t" \
         "movl %%edx,%1" \
         :"=m" (*((long *) (gate_addr))), \
          "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
         :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
          "3" ((char *) (addr)),"2" (__KERNEL_CS << 16)); \
     } while (0)      
1) 對 idt_table 的設定采用彙編完成.
 2) 輸出部:
           %0 -> gate_addr,
           %1 -> gate_addr+1,
           %2 -> __d0 (%eax)
           %3 -> __dl (%edx)
 3) 輸入部:
           %4 -> ((short) (0x8000+(dpl<<13)+(type<<8)))
           "3" ((char *) (addr))  參數addr和%3(edx)綁定
           "2" (__KERNEL_CS << 16)) 參數_KERNEL_CS放入 %2(%eax)的高16位.
 4) 其中 _KERNEL_CS
           #define __KERNEL_CS        0x10      

init_IRQ 外設的中斷設定

void __init init_IRQ(void)
    {
        int i;
        for (i = 0; i < NR_IRQS; i++) {
                int vector = FIRST_EXTERNAL_VECTOR + i;
                if (vector != SYSCALL_VECTOR) 
                        set_intr_gate(vector, interrupt[i]);
        }
        set_intr_gate(FIRST_DEVICE_VECTOR, interrupt[0]);
        set_intr_gate(RESCHEDULE_VECTOR, reschedule_interrupt);
        set_intr_gate(INVALIDATE_TLB_VECTOR, invalidate_interrupt);
        set_intr_gate(CALL_FUNCTION_VECTOR, call_function_interrupt);
        
        outb_p(0x34,0x43);                /* binary, mode 2, LSB/MSB, ch 0 */
        outb_p(LATCH & 0xff , 0x40);        /* LSB */
        outb(LATCH >> 8 , 0x40);        /* MSB */
    }      
1) Linux中提供不同的中斷源共用中斷向量的機制。
2) 每個共同的中斷向量都對應一個數組 irq_desc。
3) 中斷産生時,使用具體裝置号到中斷向量對應的irq_desc數組中周遊查找。
4) #define FIRST_EXTERNAL_VECTOR        0x20
   #define SYSCALL_VECTOR                0x80
5) for (i = 0; i < NR_IRQS; i++)
   周遊所有的外部中斷向量NR_IRQS,依次設定外部中斷的服務程式。
6) int vector = FIRST_EXTERNAL_VECTOR + i;
   第一個外部裝置的中斷号 FIRST_EXTERNAL_VECTOR = 0x20,是以要
7) if (vector != SYSCALL_VECTOR)
   跳過 SYSCALL_VECTOR。
8) interrupt[i]
   中斷服務程式的函數數組。      

interrupt 數組 - c語言宏程式設計的藝術

#define IRQ(x,y) \
        IRQ##x##y##_interrupt
    #define IRQLIST_16(x) \
        IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \
        IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \
        IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \
        IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f)
    void (*interrupt[NR_IRQS])(void) = {
        IRQLIST_16(0x0),      
1) void (*interrupt[NR_IRQS])(void)
   函數指針數組,數組大小是NR_IRQS
2) IRQLIST_16(0x0)
   數組的初始化使用了GNU c的風格。
3) #define IRQLIST_16(x) IRQ(x,2)
   IRQLIST_16 被定義為了 IRQ(x, 2)。
4) #define IRQ(x,y) IRQ##x##y##_interrupt
   IRQ(x, y) 就是字元串拼接。比如 IRQ(0x0, 4) = IRQ0x04_interrupt
   也就是,最終interrupt數組的内容是 {IRQ0x00_interrupt ~ IRQ0x0f_interrupt}
   而函數 IRQ0x00_interrupt在哪裡定義的呢?      
IRQ0x04_interrupt 的定義
#define BI(x,y) \
        BUILD_IRQ(x##y)
      #define BUILD_16_IRQS(x) \
        BI(x,0) BI(x,1) BI(x,2) BI(x,3) \
        BI(x,4) BI(x,5) BI(x,6) BI(x,7) \
        BI(x,8) BI(x,9) BI(x,a) BI(x,b) \
        BI(x,c) BI(x,d) BI(x,e) BI(x,f)
        
      BUILD_16_IRQS(0x0)      
1) BUILD_16_IRQS(0x0)
     最終會被宏展開為 BUILD_IRQ(0x00), BUILD_IRQ(0x00) ~ BUILD_IRQ(0x0f)
  而 BUILD_IRQ 是和硬體相關的,在 include/asm-i386/hw_irq.h中定義      
#define IRQ_NAME2(nr) nr##_interrupt(void)
      #define IRQ_NAME(nr) IRQ_NAME2(IRQ##nr)
      #define BUILD_IRQ(nr) \
         asmlinkage void IRQ_NAME(nr); \
             __asm__( \
            "\n"__ALIGN_STR"\n" \
            SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
            "pushl $"#nr"-256\n\t" \
            "jmp common_interrupt");      
展開後:      
asmlinkage void IRQ0x04_interrupt();
      __asm__( \
          "\n" \
          ""IRQ0x04_interrupt: \n\t" \
            "pushl $"#nr"-256\n\t" \
            "jmp common_interrupt");      
1) 可以看出 IRQ0x04_interrupt 不是真正的函數,隻是一個symbol。
2) 做兩件事情:
   a) 把中斷向量pushl到棧上。
   b) 調用 common_interrupt。      

第二次總結一下c語言的宏程式設計

1) c語言宏程式設計可以實作循環的邏輯。通過枚舉循環的兩個次元。
    IRQ(0x0), IRQ(0x1), 而每個IRQ也是枚舉。
2) 宏程式設計的思想是:盡量避免枯燥的類似的字元串。消除備援。      

繼續閱讀