当处于实模式下时,IDT 被初始化并由 BIOS 程序所使用。然而,一旦 Linux
开始接管,IDT 就被移到 RAM 的另一个区域,并进行第二次初始化,因为 Linux
不使用任何 BIOS
程序,而使用自己专门的中断服务程序(例程)(interrupt service
routine,ISR)。中断和异常处理程序很像常规的 C 函数
IDT的格式和GDT和LDT相似,表中每一项对应一个中断(interrupt or
exception)向量,每个向量由8个字节组成,共256项。idtr使IDT可以位于内存的任何地方,它指定IDT的线性基地址及其限制(最大长度)。在允许中断之前,必须用lidt汇编指令来初始化idtr。
/* all the set up before the call gates are initialised */ //8259初始化,初始化了irq_desc[]数组 pre_intr_init_hook();
/* * Cover the whole vector space, no vector can escape * us. (some of these will be overridden and become * 'special' SMP interrupts) */ /* * 通过替换setup_idt() 所建立的中断门来更新IDT * interrupt 数组第n 项中存放IRQn 的中断处理程序的地址 * 这里不包括128(0x80) 号中断向量相关的中断门,因为它用于系统调用的编程异常 */ //FIRST_EXTERNAL_VECTOR:第一个可用号,前面部份均为系统保留 for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) { int vector = FIRST_EXTERNAL_VECTOR + i; if (i >= NR_IRQS) break; //跳过系统调用号 if (vector != SYSCALL_VECTOR) //调用了set_intr_gate(vector, interrupt[i])为第n条中断线设置的中断处理函数为interrupt[n- FIRST_EXTERNAL_VECTOR]. set_intr_gate(vector, interrupt[i]); }
/* setup after call gates are initialised (usually add in * the architecture specific gates) */ intr_init_hook();
/* * Set the clock to HZ Hz, we already have a valid * vector now: */ setup_pit_timer();
/* * External FPU? Set up irq13 if so, for * original braindamaged IBM FERR coupling. */ if (boot_cpu_data.hard_math && !cpu_has_fpu) setup_irq(FPU_IRQ, &fpu_irq);
/* * Sanity-check: shared interrupts must pass in a real dev-ID, * otherwise we'll have trouble later trying to figure out * which interrupt is which (messes up the interrupt freeing * logic etc). */ if ((irqflags & SA_SHIRQ) && !dev_id) return -EINVAL; if (irq >= NR_IRQS) return -EINVAL; if (!handler) return -EINVAL;
if (desc->handler == &no_irq_type) return -ENOSYS; /* * Some drivers like serial.c use request_irq() heavily, * so we have to be careful not to interfere with a * running system. */ /* * 如果中断可生成随机熵,则初始化随机熵机制 */ if (new->flags & SA_SAMPLE_RANDOM) { /* * This function might sleep, we want to call it first, * outside of the atomic block. * Yes, this might clear the entropy pool if the wrong * driver is attempted to be loaded, without actually * installing a new handler, but is this really a problem, * only the sysadmin is able to do this. */ rand_initialize_irq(irq); }
/* * The following block of code has to be executed atomically */ spin_lock_irqsave(&desc->lock,flags); /** * 检查是否已经有设备在使用这个IRQ了。 */ p = &desc->action; /** * 有设备在使用了。 */ if ((old = *p) != NULL) { /* Can't share interrupts unless both agree to */ /** * 如果有设备在使用这个IRQ线,就再次检查它是否允许共享IRQ。 * 在这里,仅仅检查第一个挂接到IRQ上的设备是否允许共享就行了。 * 其实,第一个设备允许共享就代表这个IRQ上的所有设备允许共享。 */ if (!(old->flags & new->flags & SA_SHIRQ)) { /** * IRQ线不允许共享,那就打开中断,并返回错误码。 */ spin_unlock_irqrestore(&desc->lock,flags); return -EBUSY; }
/* add new interrupt at end of irq queue */ /** * 在这里,我们已经知道设备上挂接了设备,那就循环,找到最后一个挂接的设备 * 我们要插入的设备应该挂接到这个设备的后面。 */ do { p = &old->next; old = *p; } while (old); /** * IRQ上有设备,并且运行到这里了,表示IRQ允许共享。 */ shared = 1; }
/** * do_IRQ执行与一个中断相关的所有中断服务例程. */ fastcall unsignedintdo_IRQ(struct pt_regs *regs) { /* high bits used in ret_from_ code */ int irq = regs->orig_eax & 0xff; #ifdef CONFIG_4KSTACKS unionirq_ctx *curctx, *irqctx; u32 *isp; #endif
/** * irq_enter增加中断嵌套计数 */ irq_enter(); #ifdef CONFIG_DEBUG_STACKOVERFLOW /* Debugging check for stack overflow: is there less than 1KB free? */ { long esp;
/* * this is where we switch to the IRQ stack. However, if we are * already using the IRQ stack (because we interrupted a hardirq * handler) we can't do that and just have to keep using the * current stack (which is the irq stack already after all) */ /** * 当前在使用内核栈,而不是硬中断请求栈.就需要切换栈 * * curctx 和irqctx 相等,说明内核已经在使用硬件中断请求栈, * 这种情况发生在内核处理另外一个中断时又产生了中断请求的时候 * 不相等就要切换内核栈 */ if (curctx != irqctx) { int arg1, arg2, ebx;
/* * do_IRQ handles all normal device IRQ's (the special * SMP cross-CPU interrupts have their own specific * handlers). */ fastcall unsignedint __do_IRQ(unsignedint irq, struct pt_regs *regs) { irq_desc_t *desc = irq_desc + irq; structirqaction * action; unsignedint status;
/* * No locking required for CPU-local interrupts: */ desc->handler->ack(irq); action_ret = handle_IRQ_event(irq, regs, desc->action); if (!noirqdebug) note_interrupt(irq, desc, action_ret); desc->handler->end(irq); return1; }
/** * 虽然中断是关闭的,但是还是需要使用自旋锁保护desc */ spin_lock(&desc->lock); /** * 如果是旧的8259A PIC,ack就是mask_and_ack_8259A,它应答PIC上的中断并禁用这条IRQ线.屏蔽IRQ线是为了确保在这个中断处理程序结束前, * CPU不进一步接受这种中断的出现. * do_IRQ是以禁止本地中断运行,事实上,CPU控制单元自动清eflags寄存器的IF标志.因为中断处理程序是通过IDT中断门调用的. * 不过,内核在执行这个中断的中断服务例程之前可能会重新激活本地中断. * 在使用APIC时,应答中断信赖于中断类型,可能是ack,也可能延迟到中断处理程序结束(也就是应答由end方法去做). * 无论如何,中断处理程序结束前,本地APIC不进一步接收这种中断,尽管这种中断可能会被其他CPU接受. */ desc->handler->ack(irq); /* * REPLAY is when Linux resends an IRQ that was dropped earlier * WAITING is used by probe to mark irqs that are being tested */ /** * 初始化主IRQ描述符的几个标志.设置IRQ_PENDING标志.也清除IRQ_WAITING和IRQ_REPLAY * 这几个标志可以很好的解决中断重入的问题. * IRQ_REPLAY标志是"挽救丢失的中断"所用.在此不详述. */ status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING); status |= IRQ_PENDING; /* we _want_ to handle it */
/* * If the IRQ is disabled for whatever reason, we cannot * use the action we have. */ action = NULL; /** * IRQ_DISABLED和IRQ_INPROGRESS被设置时,什么都不做(action==NULL) * 即使IRQ线被禁止,CPU也可能执行do_IRQ函数.首先,可能是因为挽救丢失的中断,其次,也可能是有问题的主板产生伪中断. * 所以,是否真的执行中断代码,需要根据IRQ_DISABLED标志来判断,而不仅仅是禁用IRQ线. * IRQ_INPROGRESS标志的作用是:如果一个CPU正在处理一个中断,那么它会设置它的IRQ_INPROGRESS.这样,其他CPU上发生同样的中断 * 就可以检查是否在其他CPU上正在处理同种类型的中断,如果是,就什么都不做,这样做有以下好处: * 一是使内核结构简单,驱动程序的中断服务例程式不必是可重入的.二是可以避免弄脏当前CPU的硬件高速缓存. */ if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) { action = desc->action; /* * 确定我们要处理了,就设置IRQ_INPROGRESS 标志, * 去除IRQ_PENDING 标志表示确认我们要处理这个中断了 */ status &= ~IRQ_PENDING; /* we commit to handling */ status |= IRQ_INPROGRESS; /* we are handling it */ } desc->status = status;
/* * If there is no IRQ handler or it was disabled, exit early. * Since we set PENDING, if another processor is handling * a different instance of this same irq, the other processor * will take care of it. */ /** * 当前面两种情况出现时,不需要(或者是不需要马上)处理中断.就退出 * 或者没有相关的中断服务例程时,也退出.当内核正在检测硬件设备时就会发生这种情况. */ if (unlikely(!action)) goto out;
/* * Edge triggered interrupts need to remember * pending events. * This applies to any hw interrupts that allow a second * instance of the same irq to arrive while we are in do_IRQ * or in the handler. But the code here only handles the _second_ * instance of the irq, not the third or fourth. So it is mostly * useful for irq hardware that does not mask cleanly in an * SMP environment. */ /** * 这里是需要循环处理的,并不是说调用一次handle_IRQ_event就行了. */ for (;;) { irqreturn_t action_ret;
out: /* * The ->end() handler has to deal with interrupts which got * disabled while the handler was running. */ /** * 现在准备退出了,end方法可能是应答中断(APIC),也可能是通过end_8259A_irq方法重新激活IRQ(只要不是伪中断). */ desc->handler->end(irq); /** * 好,工作已经全部完成了,释放自旋锁吧.注意两个锁的配对使用方法. */ spin_unlock(&desc->lock);
/* * If we're in an interrupt or softirq, we're done * (this also catches softirq-disabled code). We will * actually run the softirq once we return from * the irq or softirq. * * Otherwise we wake up ksoftirqd to make sure we * schedule the softirq soon. */ /** * in_interrupt是判断是否在中断上下文中。 * 程序在中断上下文中,表示:要么当前禁用了软中断,要么处在硬中断嵌套中,此时都不用唤醒ksoftirqd内核线程。 */ if (!in_interrupt()) wakeup_softirqd(); }
/* * 唤醒本地CPU的ksoftirqd内核线程 */ staticinlinevoidwakeup_softirqd(void) { /* Interrupts are disabled: no need to stop preemption */ structtask_struct *tsk = __get_cpu_var(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING) wake_up_process(tsk); }