今天周日天朗氣清惠風和暢。開始進入核心中斷代碼的學習。
中斷,異常和系統調用
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,÷_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) 宏程式設計的思想是:盡量避免枯燥的類似的字元串。消除備援。