今天周日天朗气清惠风和畅。开始进入内核中断代码的学习。
中断,异常和系统调用
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) 宏编程的思想是:尽量避免枯燥的类似的字符串。消除冗余。