so_brave 发表于 2011-12-28 16:28

linux内核中断、异常 3.................


linux内核中断、异常 3.................







清楚了中断机制和内核中有关数据结构的初始化以后,我们就从中断请求的发生到CPU的相应,再到中断服务程序的调用与返回,沿着CPU所经过的路线走一遍。

当某个外设已经产生了依次中断请求后,该请求通过中断控制器i8259A到达CPU的“中断请求”引线INTR。由于中断时开着的,所以CPU在执行完当前指令后就来相应该次中断请求。

中断向量的设置和初始化主要在setup.s文件中设置了一部分(上面已经介绍),在start_kernel函数中init_IRQ函数最终调用函数


view plaincopy to clipboardprint?01.void __init native_init_IRQ(void)
02.{
03.    int i;
04.
05.    /* Execute any quirks before the call gates are initialised: */
06.    x86_init.irqs.pre_vector_init();
07.
08.    apic_intr_init();
09.
10.    /*
11.   * Cover the whole vector space, no vector can escape
12.   * us. (some of these will be overridden and become
13.   * 'special' SMP interrupts)
14.   *//*更新外部中断(IRQ)的IDT表项*/
15.    for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
16.      /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
17.      if (!test_bit(i, used_vectors))/*跳过系统调用(trap)使用过的槽位*/
18.            set_intr_gate(i, interrupt);
19.    }
20.
21.    if (!acpi_ioapic)
22.      setup_irq(2, &irq2);
23.
24.#ifdef CONFIG_X86_32   
25.    /*
26.   * External FPU? Set up irq13 if so, for
27.   * original braindamaged IBM FERR coupling.
28.   */
29.    if (boot_cpu_data.hard_math && !cpu_has_fpu)
30.      setup_irq(FPU_IRQ, &fpu_irq);
31.
32.    irq_ctx_init(smp_processor_id());
33.#endif   
34.}
void __init native_init_IRQ(void)
{
        int i;

        /* Execute any quirks before the call gates are initialised: */
        x86_init.irqs.pre_vector_init();

        apic_intr_init();

        /*
       * Cover the whole vector space, no vector can escape
       * us. (some of these will be overridden and become
       * 'special' SMP interrupts)
       *//*更新外部中断(IRQ)的IDT表项*/
        for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
                /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
                if (!test_bit(i, used_vectors))/*跳过系统调用(trap)使用过的槽位*/
                        set_intr_gate(i, interrupt);
        }

        if (!acpi_ioapic)
                setup_irq(2, &irq2);

#ifdef CONFIG_X86_32
        /*
       * 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);

        irq_ctx_init(smp_processor_id());
#endif
}可以看到这里将interrupt[]数组中的值设置为中断服务函数而i-FIRST_EXTERNAL_VECTOR中的i为中断号,interrupt[]数组在汇编中实现
view plaincopy to clipboardprint?01.ENTRY(interrupt)
02..text
03.    .p2align 5
04.    .p2align CONFIG_X86_L1_CACHE_SHIFT
05.ENTRY(irq_entries_start)
06.    RING0_INT_FRAME
07.vector=FIRST_EXTERNAL_VECTOR
08..rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
09.    .balign 32
10..rept 7
11.    .if vector < NR_VECTORS
12.      .if vector <> FIRST_EXTERNAL_VECTOR
13.    CFI_ADJUST_CFA_OFFSET -4
14.      .endif
15.1:pushl $(~vector+0x80)   /* Note: always in signed byte range */
16.    CFI_ADJUST_CFA_OFFSET 4
17.      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
18.    jmp 2f
19.      .endif
20.      .previous
21.    .long 1b
22.      .text
23.vector=vector+1
24.    .endif
25..endr
26.2:jmp common_interrupt
27..endr
28.END(irq_entries_start)
29.
30..previous
31.END(interrupt)
ENTRY(interrupt)
.text
        .p2align 5
        .p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
        RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
        .balign 32
.rept        7
    .if vector < NR_VECTORS
      .if vector <> FIRST_EXTERNAL_VECTOR
        CFI_ADJUST_CFA_OFFSET -4
      .endif
1:        pushl $(~vector+0x80)        /* Note: always in signed byte range */
        CFI_ADJUST_CFA_OFFSET 4
      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
        jmp 2f
      .endif
      .previous
        .long 1b
      .text
vector=vector+1
    .endif
.endr
2:        jmp common_interrupt
.endr
END(irq_entries_start)

.previous
END(interrupt)上面的代码相当于下面代码


view plaincopy to clipboardprint?
01.346 ENTRY(irq_entries_start)   
02.347 .rept NR_IRQS /*348-354行重复NR_IRQS次,会被gcc编译时展开,不需手写这么多行重复的代码 */
03.348 ALIGN   
04.349 1: pushl $vector-256 /*vector在354行递增 */
05.350 jmp common_interrupt /*所有的外部中断处理函数的统一部分,以后再讲述*/   
06.351 .data   
07.352 .long 1b /*存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同 */
08.353 .text   
09.354 vector=vector+1   
10.355 .endr /*与347行呼应*/   
11.356   
12.357 ALIGN13./*首先342行和352行都处于.data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ号是0 (从init_IRQ()的404行知道,它对应的中断向量是FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput,从而执行:14.pushl 0-256
15.jmp common_interrupt
16.的代码片断,进入到Linux内核安排好的中断入口路径 */
346 ENTRY(irq_entries_start)
347 .rept NR_IRQS /*348-354行重复NR_IRQS次,会被gcc编译时展开,不需手写这么多行重复的代码 */
348 ALIGN
349 1: pushl $vector-256 /*vector在354行递增 */
350 jmp common_interrupt /*所有的外部中断处理函数的统一部分,以后再讲述*/
351 .data
352 .long 1b /*存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同 */
353 .text
354 vector=vector+1
355 .endr /*与347行呼应*/
356
357 ALIGN/*首先342行和352行都处于.data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ号是0 (从init_IRQ()的404行知道,它对应的中断向量是FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput,从而执行:
pushl 0-256
jmp common_interrupt
的代码片断,进入到Linux内核安排好的中断入口路径 */
common_interrupt:


view plaincopy to clipboardprint?01.common_interrupt:
02.    /*将中断向量号减256。内核用负数表示所有的中断*/
03.    addl $-0x80,(%esp)/* Adjust vector into the [-256,-1] range */
04.    /*调用SAVE_ALL宏保存寄存器的值*/
05.    SAVE_ALL
06.    TRACE_IRQS_OFF
07.    /*保存栈顶地址*/
08.    movl %esp,%eax
09.    /*调用do_IRQ函数*/
10.    call do_IRQ
11.    /*从中断返回*/
12.    jmp ret_from_intr
13.ENDPROC(common_interrupt)
14.    CFI_ENDPROC
common_interrupt:
        /*将中断向量号减256。内核用负数表示所有的中断*/
        addl $-0x80,(%esp)        /* Adjust vector into the [-256,-1] range */
        /*调用SAVE_ALL宏保存寄存器的值*/
        SAVE_ALL
        TRACE_IRQS_OFF
        /*保存栈顶地址*/
        movl %esp,%eax
        /*调用do_IRQ函数*/
        call do_IRQ
        /*从中断返回*/
        jmp ret_from_intr
ENDPROC(common_interrupt)CFI_ENDPROC保存现场后调用do_IRQ函数进入c语言中执行中断函数


view plaincopy to clipboardprint?01.unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
02.{
03.    /*取得原来的寄存器*/
04.    struct pt_regs *old_regs = set_irq_regs(regs);
05.
06.    /* high bit used in ret_from_ code*/
07.    /*取得中断向量号,通过汇编代码中栈中保存
08.    的寄存器的顺序和前面c函数中的idt的初始化
09.    可以得知这里返回的值再取补
10.    正好是我们要的中断号*/
11.    unsigned vector = ~regs->orig_ax;
12.    unsigned irq;
13.    /*退出idle进程*/
14.    exit_idle();
15.    /*进入中断*/
16.    irq_enter();
17.    /*中断线号与设备的中断号之间对应关系
18.    ,由系统分派,分派表是一个per-cpu变量vector_irq*/
19.    irq = __get_cpu_var(vector_irq);
20.
21.    if (!handle_irq(irq, regs)) {/*处理*/
22.      ack_APIC_irq();/*应答apic*/
23.
24.      if (printk_ratelimit())
25.            pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
26.                __func__, smp_processor_id(), vector, irq);
27.    }
28.
29.    irq_exit();
30.
31.    set_irq_regs(old_regs);
32.    return 1;
33.}
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
        /*取得原来的寄存器*/
        struct pt_regs *old_regs = set_irq_regs(regs);

        /* high bit used in ret_from_ code*/
        /*取得中断向量号,通过汇编代码中栈中保存
        的寄存器的顺序和前面c函数中的idt的初始化
        可以得知这里返回的值再取补
        正好是我们要的中断号*/
        unsigned vector = ~regs->orig_ax;
        unsigned irq;
        /*退出idle进程*/
        exit_idle();
        /*进入中断*/
        irq_enter();
        /*中断线号与设备的中断号之间对应关系
        ,由系统分派,分派表是一个per-cpu变量vector_irq*/
        irq = __get_cpu_var(vector_irq);

        if (!handle_irq(irq, regs)) {/*处理*/
                ack_APIC_irq();/*应答apic*/

                if (printk_ratelimit())
                        pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
                                __func__, smp_processor_id(), vector, irq);
        }

        irq_exit();

        set_irq_regs(old_regs);
        return 1;
}view plaincopy to clipboardprint?01./*下面是处理函数。函数根据中断号,查找相应的desc结构*/
02.bool handle_irq(unsigned irq, struct pt_regs *regs)
03.{
04.    struct irq_desc *desc;
05.    int overflow;
06.
07.    overflow = check_stack_overflow();
08.
09.    desc = irq_to_desc(irq);/*取得irq对应的中断描述符*/
10.    if (unlikely(!desc))
11.      return false;
12.    /*如果是在中断栈上调用,则稍微复杂一点
13.    ,需要先构造一个中断栈,再调用handle_irq*/
14.    if (!execute_on_irq_stack(overflow, desc, irq)) {
15.      if (unlikely(overflow))
16.            print_stack_overflow();
17.      /*handle_irq函数指针,指向了handle_level_irq,
18.      或者是handle_edge_irq。不论是哪一种,
19.      中断电流处理函数在会调用handle_IRQ_event
20.      进一步处理,handle_IRQ_event函数的本质是
21.      遍历中断号上所有的action,调用其handler。
22.      这是在设备驱动初始化时向中断子系统注册的
23.      */
24.      desc->handle_irq(irq, desc);
25.    }
26.
27.    return true;
28.}
/*下面是处理函数。函数根据中断号,查找相应的desc结构*/
bool handle_irq(unsigned irq, struct pt_regs *regs)
{
        struct irq_desc *desc;
        int overflow;

        overflow = check_stack_overflow();

        desc = irq_to_desc(irq);/*取得irq对应的中断描述符*/
        if (unlikely(!desc))
                return false;
        /*如果是在中断栈上调用,则稍微复杂一点
        ,需要先构造一个中断栈,再调用handle_irq*/
        if (!execute_on_irq_stack(overflow, desc, irq)) {
                if (unlikely(overflow))
                        print_stack_overflow();
                /*handle_irq函数指针,指向了handle_level_irq,
                或者是handle_edge_irq。不论是哪一种,
                中断电流处理函数在会调用handle_IRQ_event
                进一步处理,handle_IRQ_event函数的本质是
                遍历中断号上所有的action,调用其handler。
                这是在设备驱动初始化时向中断子系统注册的
                */
                desc->handle_irq(irq, desc);
        }

        return true;
}由上面的注释知,我们直接看handle_IRQ_event函数


view plaincopy to clipboardprint?01.irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
02.{
03.    irqreturn_t ret, retval = IRQ_NONE;
04.    unsigned int status = 0;
05.    /*为CPU会禁止中断,这里将其打开,如果没有指定
06.    IRQF_DISABLED标志的话,它表示处理程序在中断禁止
07.    情况下运行*/
08.    if (!(action->flags & IRQF_DISABLED))
09.      local_irq_enable_in_hardirq();
10.
11.    do {/*遍历当前irq的action链表中的所有action,调用之*/
12.      trace_irq_handler_entry(irq, action);/*打开中断跟踪*/
13.      ret = action->handler(irq, action->dev_id);/*调用中断函数*/
14.      trace_irq_handler_exit(irq, action, ret);/*关闭中断跟踪*/
15.
16.      switch (ret) {
17.      case IRQ_WAKE_THREAD:
18.            /*
19.             * Set result to handled so the spurious check
20.             * does not trigger.
21.             */
22.            ret = IRQ_HANDLED;
23.
24.            /*
25.             * Catch drivers which return WAKE_THREAD but
26.             * did not set up a thread function
27.             */
28.            if (unlikely(!action->thread_fn)) {
29.                warn_no_thread(irq, action);
30.                break;
31.            }
32.
33.            /*
34.             * Wake up the handler thread for this
35.             * action. In case the thread crashed and was
36.             * killed we just pretend that we handled the
37.             * interrupt. The hardirq handler above has
38.             * disabled the device interrupt, so no irq
39.             * storm is lurking.
40.             */
41.            if (likely(!test_bit(IRQTF_DIED,
42.                         &action->thread_flags))) {
43.                set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
44.                wake_up_process(action->thread);
45.            }
46.
47.            /* Fall through to add to randomness */
48.      case IRQ_HANDLED:
49.            status |= action->flags;
50.            break;
51.
52.      default:
53.            break;
54.      }
55.
56.      retval |= ret;
57.      action = action->next;/*取得下一个action,如果有的话*/
58.    } while (action);
59.    /*如果指定了标志,则使用中断间隔时间为随机数产生器产生熵*/
60.    if (status & IRQF_SAMPLE_RANDOM)
61.      add_interrupt_randomness(irq);
62.    /*关闭中断,do_IRQ进入下一轮循环——等待新的中断到来*/
63.    local_irq_disable();
64.
65.    return retval;
66.}
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
        irqreturn_t ret, retval = IRQ_NONE;
        unsigned int status = 0;
        /*为CPU会禁止中断,这里将其打开,如果没有指定
        IRQF_DISABLED标志的话,它表示处理程序在中断禁止
        情况下运行*/
        if (!(action->flags & IRQF_DISABLED))
                local_irq_enable_in_hardirq();

        do {/*遍历当前irq的action链表中的所有action,调用之*/
                trace_irq_handler_entry(irq, action);/*打开中断跟踪*/
                ret = action->handler(irq, action->dev_id);/*调用中断函数*/
                trace_irq_handler_exit(irq, action, ret);/*关闭中断跟踪*/

                switch (ret) {
                case IRQ_WAKE_THREAD:
                        /*
                       * Set result to handled so the spurious check
                       * does not trigger.
                       */
                        ret = IRQ_HANDLED;

                        /*
                       * Catch drivers which return WAKE_THREAD but
                       * did not set up a thread function
                       */
                        if (unlikely(!action->thread_fn)) {
                                warn_no_thread(irq, action);
                                break;
                        }

                        /*
                       * Wake up the handler thread for this
                       * action. In case the thread crashed and was
                       * killed we just pretend that we handled the
                       * interrupt. The hardirq handler above has
                       * disabled the device interrupt, so no irq
                       * storm is lurking.
                       */
                        if (likely(!test_bit(IRQTF_DIED,
                                             &action->thread_flags))) {
                                set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
                                wake_up_process(action->thread);
                        }

                        /* Fall through to add to randomness */
                case IRQ_HANDLED:
                        status |= action->flags;
                        break;

                default:
                        break;
                }

                retval |= ret;
                action = action->next;/*取得下一个action,如果有的话*/
        } while (action);
        /*如果指定了标志,则使用中断间隔时间为随机数产生器产生熵*/
        if (status & IRQF_SAMPLE_RANDOM)
                add_interrupt_randomness(irq);
        /*关闭中断,do_IRQ进入下一轮循环——等待新的中断到来*/
        local_irq_disable();

        return retval;
}可知,在handle_event_IRQ函数中完成了具体的中断函数响应执行工作。也在这里我们看出,多个中断服务函数共享同一个中断号时,在每次中断到来时,在中断向量上的服务函数队列中所有的服务函数将执行一遍。而对于中断线程也会在这里得到执行。

到这里中断的初始化、中断服务的相应的分析完了,对于中断返回的退出,基本和异常的返回是一样的,我们从代码中可以看出


view plaincopy to clipboardprint?01.ret_from_exception:
02.    preempt_stop(CLBR_ANY)
03.ret_from_intr:
04.    GET_THREAD_INFO(%ebp)
05.      ....
ret_from_exception:
        preempt_stop(CLBR_ANY)
ret_from_intr:
        GET_THREAD_INFO(%ebp)....
至此,中断和异常的分析基本上就这样了,中间有很多细节是值得去理解的。包括汇编和c的实现技巧。对于软中断和任务队列将在后面给予介绍。

星期六的深夜68 发表于 2011-12-28 16:28

谢谢分享
页: [1]
查看完整版本: linux内核中断、异常 3.................