
由于内核开发人员,只能掌控自己实现的代码,无法预测用户的代码,所以,整个"大程序"在往"生长部分"跳转的同时,一定会降低CPU权限,跳转回内核代码的同时,也必须提升CPU权限。有一种方法是,在软件层保证,每个中断处理函数,都在特权指令之前,执行提升CPU权限的指令,但这样,就增加了内核实现的负担,同时也会让中断处理函数的代码看起来很臃肿。
所以,硬件层保证了这一点,回忆一下Linux内核笔记001介绍过的段描述符:

如果type字段的二进程值为100/101/110/111,硬件层就会按照"门"的含义解释:


利用"门",应用程序中执行一条指令(int/call/jmp),就可以同时完成"向内核代码跳转"和"提升CPU权限"两个动作,具体过程稍后详细学习,这里先根据上述内容,做个小小的展开,这样稍后深入到细节分析时,就不会思维混乱了:
① 内核代码,一部分在刚开机时执行,没有进程上下文,一部分在创建进程后,被进程"调用"执行,有进程上下文;
② 用户态是通过"门",切换到内核态,根据触发条件,分为中断、异常、陷阱。时钟中断,跟当时的进程上下文无关,CPU异常处理函数内部,可能又会出现缺页等异常,或者发生时钟中断,就会出现"中断嵌套"的情况。


struct tss_struct {
unsigned short back_link,__blh;
unsigned long esp0;
unsigned short ss0,__ss0h;
unsigned long esp1;
unsigned short ss1,__ss1h;
unsigned long esp2;
unsigned short ss2,__ss2h;
unsigned long __cr3;
unsigned long eip;
unsigned long eflags;
unsigned long eax,ecx,edx,ebx;
unsigned long esp;
unsigned long ebp;
unsigned long esi;
unsigned long edi;
unsigned short es, __esh;
unsigned short cs, __csh;
unsigned short ss, __ssh;
unsigned short ds, __dsh;
unsigned short fs, __fsh;
unsigned short gs, __gsh;
unsigned short ldt, __ldth;
unsigned short trace, bitmap;
unsigned long io_bitmap[IO_BITMAP_SIZE+1];
/*
* pads the TSS to be cacheline-aligned (size is 0x100)
*/
unsigned long __cacheline_filler[5];
};#define INIT_TSS { \
0,0, /* back_link, __blh */ \
sizeof(init_stack) + (long) &init_stack, /* esp0 */ \
__KERNEL_DS, 0, /* ss0 */ \
0,0,0,0,0,0, /* stack1, stack2 */ \
0, /* cr3 */ \
0,0, /* eip,eflags */ \
0,0,0,0, /* eax,ecx,edx,ebx */ \
0,0,0,0, /* esp,ebp,esi,edi */ \
0,0,0,0,0,0, /* es,cs,ss */ \
0,0,0,0,0,0, /* ds,fs,gs */ \
__LDT(0),0, /* ldt */ \
0, INVALID_IO_BITMAP_OFFSET, /* tace, bitmap */ \
{~0, } /* ioperm */ \
}

start_kernel() | // 最前面19个中断向量,由硬件使用,比如缺页异常时,CPU自动穿过14号向量对应的门 |- trap_init() | |- set_trap_gate() | | | // 15:1111 => D:1,type:111,陷阱门 | | | // DPL:0 => 防止用户态使用INT指令穿过该门 | | |- _set_gate(idt_table+n,15,0,addr) | |- set_intr_gate() | | | // 14:1110 => D:1,type:110,中断门(与陷阱门的唯一区别是:不会中断嵌套) | | | // 用于外设中断 | | |- _set_gate(idt_table+n,14,0,addr) | |- set_system_gate() | | | // DPL:3 => 用户态可以使用INT指令穿过该门(系统调用) | | | // 用户态执行系统调用,最终就是通过"INT 80",切换到内核态 | | |- _set_gate(idt_table+n,15,3,addr) | |- set_call_gate() | | | // 12:1100 => D:1,type:100,调用门 | | | // iBCS、Solaris/x86支持通过调用门进入系统调用,为了保证在这些系统中编译的程序,可以在Linux上执行 | | |- _set_gate(a,12,3,addr) |- init_IRQ() // 本篇笔记最后一节,详细分析




init_IRQ()
|- init_ISA_irqs()
| | // 初始化i8259A中断控制器
| |- init_8259A()
| |- for (0->15)
| | |- irq_desc[i].handler = &i8259A_irq_type
| /*
| * interrupt函数指针数组:
| * void (*interrupt[NR_IRQS])(void) = {
| * IRQLIST_16(0x0),
| * }; |
| * |- IRQ(0x0,0)
| * | |- IRQ0x00_interrupt
| * |- ..
| * |- IRQ(0x0,f)
| * |- IRQ0x0f_interrupt
| */
| /*
| * IRQ0xXX_interrupt()函数定义:
| * BUILD_16_IRQS(0x0)
| * |- BI(0x0,0)
| * | |- BUILD_IRQ(0x00)
| * | | |- IRQ_NAME(0x00)
| * | | |- IRQ_NAME2(IRQ0x00)
| * | | |- IRQ0x00_interrupt(void)
| * | |- asmlinkage void IRQ0x00_interrupt(void)
| * | | __asm__( \
| * | | ".align 0"
| * | | "IRQ0x00_interrupt:"
| * | | "pushl $0x00-256"
| * | | "jmp common_interrupt");
| * |- ..
| * |- BI(0x0,f)
| * |- asmlinkage void IRQ0x0f_interrupt(void)
| */
| // 设置20~35号中断向量对应的门,处理函数分别为IRQ0x00_interrupt(), .., IRQ0x0f_interrupt()
|- for (0->15)
| |- set_intr_gate()