今天周日天朗气清惠风和畅。开始进入内核中断代码的学习。
中断,异常和系统调用
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 中断向量寄存器
X86 CPU中断的硬件支持
x86保护模式的中断机制
1) 中断服务程序的入口变成一个描述项 - gate。 2) 发生CPU状态的切换都要经过一道门。 3) 4种门 task gate interrupt gate trap gate call gates 4) 允许在中断发生后进行一次进程切换,这样提供了一种机制:所有的中断都可以交给一个进程来处理。
中断堆栈切换
运行级别不一致时,会发生堆栈的切换: 1) 当CPU在用户态时,发生中断,则处理中断的代码会在一个新的栈上。 2) 当CPU在内核态时,发生中断,则处理中断的代码会在原来的栈上。
IDTR 中断向量寄存器
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) 宏编程的思想是:尽量避免枯燥的类似的字符串。消除冗余。