linux中的中断与异常分析

      广义的中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

   中断描述符表是一个系统表,它与每一个中断或者异常向量相联系 每个向量在表中有相应的中断或者异常处理程序的入口地址。 每个描述符8个字节,共256项,占用空间2KB 。内核启动中断前,必须初始化IDT,然后把IDT的基地址装载到idtr寄存器中 int指令允许用户进程发出一个中断信号,其值可以是0-255的任意一个向量 。所以,为了防止用户用int指令非法模拟中断和异常,IDT的初始化时要很小心的设置特权级,可以通过把相应描述符的DPL字段设置成0来实现。 然而用户进程有时必须要能发出一个编程异常。为了做到这一点,只要把相应的中断或陷阱门描述符的特权级设置成3。

中断门

用户态的进程不能访问的一个Intel中断门(特权级为0),所有的中断都通过中断门激活,并全部在内核态 。
陷阱门

用户态的进程不能访问的一个Intel陷阱门(特权级为0),大部分linux异常处理程序通过陷阱门激活。

系统门

用户态的进程可以访问的一个Intel陷阱门(特权级为3),通过系统门来激活4个linux异常处理程序,它们的向量是3,4,5和128。因此,在用户态下可以发布int3,into,bound和int $0x80四条汇编指令。

异常 

Intel公司保留0-31号中断向量用来处理异常事件:当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,异常的处理程序由操作系统提供。 

其中2号为非屏蔽中断 ,根据异常时保存在内核堆栈中的eip的值可以进一步分为:

  • 故障(fault):eip=引起故障的指令的地址 通常可以纠正,处理完异常时,该指令被重新执行 例如缺页异常 。
  • 陷阱(trap):eip=随后要执行的指令的地址。
  • 异常中止(abort):eip=??? 发生严重的错误。eip值无效,只有强制终止受影响的进程。

中断 (狭义)

 0-31号中断向量被用作异常处理,则剩下32-255号中断向量共224个中断向量可以使用。这224个中断向量的分配方式即除了128号向量0x80 (SYSCALL_VECTOR)用作系统调用总入口之外,其他都用在外部硬件中断源上,包括可编程中断控制器8259A的15个irq(如下图所示);事实上,当没有定义CONFIG_X86_IO_APIC时,其他223(除0x80外)个中断向量,只利用了从32号开始的15个,其它208个空着未用。 

IRQ号与I/O设备之间的对应关系是在初始化每个设备驱动程序时建立的。系统初始化时,调用init_IRQ()函数用新的中断门替换临时中断门来更新IDT。

 中断程序入口操作为:

  1. 将中断向量入栈
  2. 保存所有其他寄存器
  3. 调用do_IRQ
  4. 跳转到ret_from_intr

do_IRQ(与体系结构无关)

每一个中断号具有一个中断描述符(0-224),使用action链表连接共享同一个中断号的多个设备和中断 。

irqaction的定义

struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    cpumask_t mask;
    const char *name;
    void *dev_id;
    struct irqaction *next;
    int irq;
    struct proc_dir_entry *dir;
};

irq_chip的定义 (中断控制器处理例程)


struct irq_chip {
    const char    *name;
    unsigned int    (*startup)(unsigned int irq);  //中断开始
    void        (*shutdown)(unsigned int irq);    //中断关闭
    void        (*enable)(unsigned int irq);      //中断使能
    void        (*disable)(unsigned int irq);    //中断禁用

    void        (*ack)(unsigned int irq);
    void        (*mask)(unsigned int irq);
    void        (*mask_ack)(unsigned int irq);
    void        (*unmask)(unsigned int irq);
    void        (*eoi)(unsigned int irq);

    void        (*end)(unsigned int irq);
    void        (*set_affinity)(unsigned int irq, cpumask_t dest);
    int        (*retrigger)(unsigned int irq);
    int        (*set_type)(unsigned int irq, unsigned int flow_type);
    int        (*set_wake)(unsigned int irq, unsigned int on);

    /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
    void        (*release)(unsigned int irq, void *dev_id);
#endif
    /*
     * For compatibility, ->typename is copied into ->name.
     * Will disappear.
     */
    const char    *typename;
};

 do_IRQ()函数的等价代码:


	int irq = ~regs->orig_ax;		//取得对应的中断向量
	irq_desc[irq]->handle_irq(irq, desc);	//调用中断处理句柄,对8259,就是handle_level_irq
	mask_ack_irq(desc, irq);		//应答PIC的中断,并禁用这条IRQ线。(为串行处理同类型中断)
	handle_IRQ_event(irq,&regs,irq_desc[irq].action);//调用handle_IRQ_event()执行中断服务例程,例如timer_interrupt
	irq_desc[irq].handler->end(irq);	//句知PIC重新激活这条IRQ线,允许处理同类型中断
	处理下半部分				//6

	

 一个中断服务例程实现一种特定设备的操作, handle_IRQ_evnet()函数依次调用这些设备例程 ,这个函数本质上执行了如下核心代码:   


do{
    action->handler(irq,action->dev_id,regs);
    action = action->next;
}while (action);

上下半部分机制 

我们把中断处理切为两半。中断处理程序是上半部——接受中断,他就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到下半部去,在合适的时机,下半部分会被执行。

下半部分三种方法

  • 软中断
  • tasklet
  • 工作队列

猜你喜欢

转载自blog.csdn.net/qq_34080360/article/details/89068957