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, i