linux内核中断、异常、系统调用的分析以及实践
linux内核中断、异常、系统调用的分析以及实践
2010年12月03日
报告内容
中断是由间隔定时器和和I/O设备产生的。
异常则是由程序的错误产生,或者由内核必须处理的异常条件产生。第一种情况下,内核通过发送一个信号来处理异常;第二种情况下,内核执行恢复异常需要的所有步骤,或对内核服务的一个请求。
中断和异常改变处理器执行的指令顺序,通常与CPU芯片内部或外部硬件电路产生的电信号相对应。它们提供了一种特殊的方式,使处理器转而去执行正常控制流之外的代码。
中断是异步的,由硬件随机产生,在程序执行的任何时候可能出现。异常是同步的,在(特殊的或出错的)指令执行时由CPU控制单元产生。
每个中断和异常由0~255之间的一个数(8位)来标识,Intel称其为中断向量(vector)。非屏蔽中断的向量和异常的向量是固定的,可屏蔽中断的向量可以通过对中断控制器的编程来改变。
Linux对中断描述符进行了如下分类:
1.中断门
用户态的进程不能访问的一个中断门(特权级为0),所有的中断都通过中断门激活,并全部在内核态。由set_intr_gate()函数负责在IDT表项中插入一个中断门。
2.系统门
用户态的进程可以访问的一个陷阱门(特权级为3),通过系统门来激活4个linux异常处理程序,它们的向量是3,4,5和128。因此,在用户态下可以发布int3,into,bound和int $0x80四条汇编指令。由set_system_gate ()函数负责在IDT表项中插入一个系统门。
3.陷阱门
用户态的进程不能访问的一个陷阱门(特权级为0),大部分linux异常处理程序通过陷阱门激活。由set_trap_gate ()函数负责在IDT表项中插入一个陷阱门。
三个门均调用了_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" \ //将转化后的dpl值存进dx寄存器
"movl %%eax,%0\n\t" \ //将eax寄存器的值存进gate_addr,即idt_table+n处
"movl %%edx,%1" \
:"=m" (*((long *) (gate_addr))), \
"=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
:"i" ((short) (0x8000+(dpl中的每一项,这个动作是在初始化系统时,由arch/i386/kernel/head.S中的Startup_32()函数完成。
setup_idt:
lea ignore_int,%edx
movl $(__KERNEL_CS 中的start_kernel()函数中调用trap_init()和init_IRQ(),来分别的用有意义的陷阱和中断处理程序替换这个空的处理程序。
void __init init_IRQ(void)
{
……
/*
* NR_IRQS=224
* FIRST_EXTERNAL_VECTOR定义为0x20,十进制32
* SYSCALL_VECTOR定义为0x80,中断号为128的中断向量
*/
for (i = 0; i lock); //自旋锁,在单处理系统中没有作用
/*
*应答PIC的中断,并禁用这条IRQ线。(为串行处理同类型中断)
*/
desc->handler->ack(irq); //应答
status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING); //设置IRQ线状态
status |= IRQ_PENDING; /* we _want_ to handle it */
action = NULL; //在真正开始工作之前,检查相关标志位
if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {
action = desc->action;
status &= ~IRQ_PENDING; /* we commit to handling */
status |= IRQ_INPROGRESS; /* we are handling it */
}
desc->status = status;
if (!action) //action为null则跳出
goto out;
for (;;) {
spin_unlock(&desc->lock); //释放中断自旋锁
handle_IRQ_event(irq, ®s, action); //在循环中执行中断服务例程
spin_lock(&desc->lock); //执行完一次则上锁
if (!(desc->status & IRQ_PENDING))
break;
//若PENDING标志位清0,则循环结束,中断不进一步传递给另一个CPU
desc->status &= ~IRQ_PENDING;
}
desc->status &= ~IRQ_INPROGRESS; //清除IRQ_INPROGERSS标志位
out:
desc->handler->end(irq);
/*
*调用主IRQ描述符的end方法,单处理系统上相应的
*end_8259A_irq()函数重新激活IRQ线,允许处理同类型中断
*/
spin_unlock(&desc->lock); //为do_IRQ释放自旋锁
if (softirq_pending(cpu)) //检查下半部分是否执行
do_softirq();
return 1;
}
handle_IRQ_evnet()函数依次调用这些设备例程:
int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction * action)
{
……
irq_enter(cpu, irq);
/*调用irq_enter方法增加正执行CPU的irq_stat元素的__local_irq_count字段*/
……
if (!(action->flags & SA_INTERRUPT))
__sti(); //如果SA_INTERRUPT标志被清0,用sti指令打开本地中断
do {
status |= action->flags;
action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action); //循环执行每个中断的中断服务例程
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
__cli(); //用cli指令打开本地中断
irq_exit(cpu, irq);
/*调用irq_exit方法减少正执行CPU的irq_stat元素的__local_irq_count字段*/
return status;
}
异常:
异常可以是由程序错误产生,或者由内核必须处理的异常条件产生的。linux下发生异常,内核会自动产生一个异常中断。在这异常中断处理程序中会判断异常来自用户程序或者内核,如果是发生在用户程序,内核通过发送一个信号来处理异常,,再根据异常信号的回调函数通知用户程序发生异常。如果发生在内核里面,那么内核执行恢复异常需要的所有步骤(例如缺页),或对内核服务的一个请求,即会搜索内核模块的异常结构表,找到相应的处理调用地址,修改异常中断的返回地址为异常处理的地址,中断返回的时候程序就跳到异常处理程序处理执行了。
异常处理有一个标准的结构,由三部分组成:
1.在内核态堆栈中保存大多数寄存器的内容
2.调用C语言的函数
3.通过ret_from_exception()从异常处理程序退出
为了利用异常,必须对IDT进行初始化,使得每个被确认的异常都有一个异常处理程序。Trap_init()函数是将一些最终值插入到IDT表中的非屏蔽中断及异常表项中。这是由set_trap_gate和set_system_gate宏实现该IDT表项的初始化。
void __init trap_init(void)
{
……
set_trap_gate(0,÷_error); // set_trap_gate()函数设置陷阱门
set_trap_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_gate(3,&int3); // set_system_gate()函数设置系统门
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_intr_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);
// SYSCALL_VECTOR=0x80,即十进制128
……
}
在/arch/i386/kernel/entry.S中我们可以看到每个为异常处理程序定义入口,如page_fault的入口如下:
ENTRY(page_fault)
pushl $ SYMBOL_NAME(do_page_fault) //执行的实体