下面的分析是基于3.6.10核心版本。
中斷描述符
硬中斷産生之後通過門描述符來尋找中斷處理程式的入口。中斷描述符表的每個表項由8位元組組成,叫做一個門描述符。主要包括三種門描述符:陷阱門、中斷門和系統門。
這裡看下中斷門描述符的結構:
中斷門描述主要包括中斷如理程式所在的段選擇符和段内偏移及一些标志位。低4位元組的bit16~bit31為段選擇符,bit0~15為段内偏移量的低16位,高4位元組的bit31~16為段内偏移量的高16位。
中斷描述符初始化
◆在arch/x86/kernel/head_64.S中
.section .bss, "aw", @nobits .align L1_CACHE_BYTES ENTRY(idt_table) .skip IDT_ENTRIES * 16 |
上面的彙編代碼的意思是變量idt_table在.bss段内,預留白間大小為256*16位元組,并且每個位元組的值都為0。
◆在arch/x86/kernel/cpu/common.c中
struct desc_ptr idt_descr = { NR_VECTORS * 16 - 1, (unsigned long) idt_table }; |
IDT的大小為256*16位元組,位址在idt_table
◆在arch/x86/kernel/cpu/common.c的cpu_init函數中
load_idt((const struct desc_ptr *)&idt_descr); |
将IDT表加載到IDTR寄存器中。中斷描述符表IDT可以駐留線上性位址空間的任何地方,使用IDTR寄存器定位IDT表的位置。
下面看下初始化的整個流程:
1、 在start_kernel函數中會調用trap_init函數來初始化陷阱門和系統調用門,init_IRQ函數來初始化中斷門描述符,調用early_irq_init初始化與硬體無關的中斷門相關事項,在後面會分析此函數。
2、 init_IRQ函數通過x86_init操作集的intr_init調用native_init_IRQ函數
apic_intr_init(); i = FIRST_EXTERNAL_VECTOR; for_each_clear_bit_from(i, used_vectors, NR_VECTORS) { set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]); } |
apic_intr_init用來初始化與APIC中斷控制器相關的中斷門描述符的。FIRST_EXTERNAL_VECTOR值為0x20,IDT表中外部中斷向量從32開始。在前面trap_init函數中對所有的陷阱門描述符和系統調用門描述符都設定了used_vectors數組中的相應位,剩下沒有設定的就是用作外部中斷的中斷門描述符了。Interrupt數組儲存了這些外部中斷描述符門的處理程式的位址。
3、alloc_intr_gate函數首先調用alloc_system_vector設定中斷向量對應在used_vectors數組中的位,然後調用set_intr_gate函數設定一個中斷描述符表項。
static inline void set_intr_gate(unsigned int n, void *addr) { BUG_ON((unsigned)n > 0xFF); _set_gate(n, GATE_INTERRUPT, addr, 0, 0, __KERNEL_CS); } |
首先判斷中斷向量号是否大于255,然後調用_set_gate函數。
static inline void _set_gate(int gate, unsigned type, void *addr, unsigned dpl, unsigned ist, unsigned seg) { pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); write_idt_entry(idt_table, gate, &s); } |
pack_gate函數主要是填充中斷門描述符表項的8位元組的相應位。中斷處理程式位址填充為段内偏移,段選擇符為__KERNEL_CS,特權級DPL為0,使用者态程式不能通路中斷門。所有中斷程式的通路都必須經過中斷門,這樣中斷的處理就限制在核心态了。
中斷入口
在前面中斷描述符初始化中看到一般外部硬中斷的處理程式位址存放在Interrupt數組中。下面看下Interrupt:(在arch/x86/kernel/entry_64.S中)
.section .init.rodata,"a" ENTRY(interrupt) .section .entry.text .p2align 5 .p2align CONFIG_X86_L1_CACHE_SHIFT ENTRY(irq_entries_start) INTR_FRAME vector=FIRST_EXTERNAL_VECTOR .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7 .balign 32 .rept 7 .if vector < NR_VECTORS .if vector <> FIRST_EXTERNAL_VECTOR CFI_ADJUST_CFA_OFFSET -8 .endif 1: pushq_cfi $(~vector+0x80) //将中斷向量号取反加上0x80後壓棧 .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6 jmp 2f .endif .previous //使彙編器傳回到前面的資料段進行彙編 .quad 1b //存儲着标簽1處的位址 .section .entry.text vector=vector+1 .endif .endr 2: jmp common_interrupt .endr CFI_ENDPROC END(irq_entries_start) .previous END(interrupt) .previous |
1、 變量Interrupt數組位于.init.rodata資料段中,變量irq_entries_start位于.entry.text代碼段中。
2、 下面來看下irq_entries_start幹了些什麼。宏INTR_FRAME用來初始化中斷時的棧幀狀态。Vector初始值為FIRST_EXTERNAL_VECTOR(即為32),在外循環中會重複(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7 = (256 – 32 + 6) / 7 = 32次。
3、 内循環重複7次,宏pushq_cfi使用pushq指令将(~vector+0x80)的值壓棧,然後做一些檢查之後就跳轉到common_interrupt函數。
上面的代碼相當于是把NR_VECTORS-FIRST_EXTERNAL_VECTOR個中斷處理程式的位址存在變量Interrupt數組開始的資料段中,處理程式的代碼存儲在變量irq_entries_start開始的代碼段中。common_interrupt函數是這些中斷處理程式的公共的入口,隻是每個中斷壓棧的參數中斷向量号不同而已。
common_interrupt函數分析:
common_interrupt: XCPT_FRAME addq $-0x80,(%rsp) interrupt do_IRQ ret_from_intr: |
common_interrupt函數使用宏interrupt來調用do_IRQ函數,當然在進入do_IRQ函數之前,做了壓棧儲存現場的操作,以及設定gs寄存器值等。
進入do_IRQ函數之後的處理流程這裡就不分析了。