zoukankan      html  css  js  c++  java
  • 中断——中断处理程序的进入与退出 (基于3.16-rc4)

    上一篇博文我们分析了中断描述符表的中断门初始化过程,并且在interrupt数组中初始化过程中,可以看到每个中断处理程序都会跳入common_interrupt中。下面我们分析下common_interrupt汇编片段(arch/x86/kernel/entrt_32.S)

     1     .p2align CONFIG_X86_L1_CACHE_SHIFT
     2 common_interrupt:
     3     ASM_CLAC
     4     addl $-0x80,(%esp)    /* Adjust vector into the [-256,-1] range */
     5     SAVE_ALL
     6     TRACE_IRQS_OFF
     7     movl %esp,%eax
     8     call do_IRQ
     9     jmp ret_from_intr
    10 ENDPROC(common_interrupt)
    11     CFI_ENDPROC 

    第5行SAVE_ALL也是一个汇编片段(宏),用来将当前多个寄存器压栈,因为在do_IRQ中可能会用到这些寄存器。第8行调用了do_IRQ函数,接下来我们分析do_IRQ函数(arch/x86/kernel/irq.c)。

     1 __visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
     2 {
     3     struct pt_regs *old_regs = set_irq_regs(regs);
     4 
     5     /* high bit used in ret_from_ code  */
     6     unsigned vector = ~regs->orig_ax;
     7     unsigned irq;
     8 
     9     irq_enter();
    10     exit_idle();
    11 
    12     irq = __this_cpu_read(vector_irq[vector]);
    13 
    14     if (!handle_irq(irq, regs)) {
    15         ack_APIC_irq();
    16 
    17         if (irq != VECTOR_RETRIGGERED) {
    18             pr_emerg_ratelimited("%s: %d.%d No irq handler for vector (irq %d)
    ",
    19                          __func__, smp_processor_id(),
    20                          vector, irq);
    21         } else {
    22             __this_cpu_write(vector_irq[vector], VECTOR_UNDEFINED);
    23         }
    24     }
    25 
    26     irq_exit();
    27 
    28     set_irq_regs(old_regs);
    29     return 1;
    30 }

    第12行的vector_irq数组保存了中断向量号和中断线号(中断号)的对应关系,利用__this_cpu_read函数获得当前中断向量号所对应的中断号。第14行中handle_irq函数,使用中断号irq作为参数,进入该中断号所对应的中断服务例程中,下面分析下handle_irq函数(arch/x86/kernel/irq_32.c)。

     1 bool handle_irq(unsigned irq, struct pt_regs *regs)
     2 {
     3     struct irq_desc *desc;
     4     int overflow;
     5 
     6     overflow = check_stack_overflow();
     7 
     8     desc = irq_to_desc(irq);
     9     if (unlikely(!desc))
    10         return false;
    11 
    12     if (user_mode_vm(regs) || !execute_on_irq_stack(overflow, desc, irq)) {
    13         if (unlikely(overflow))
    14             print_stack_overflow();
    15         desc->handle_irq(irq, desc);
    16     }
    17 
    18     return true;
    19 }

    第8行获取到中断号irq所对应的struct irq_desc结构体指针,内核使用struct irq_desc类型结构体数组来存放所有的中断服务例程,中断号irq作为数组元素下标,如下所示(kernel/irq/irqdesc.c)。

    1 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    2     [0 ... NR_IRQS-1] = {
    3         .handle_irq    = handle_bad_irq,
    4         .depth        = 1,
    5         .lock        = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    6     }
    7 };

    再来看下struct irq_desc结构体(include/linux/irqdesc.h)。

     1 struct irq_desc {
     2     struct irq_data        irq_data;
     3     unsigned int __percpu    *kstat_irqs;
     4     irq_flow_handler_t    handle_irq;
     5 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
     6     irq_preflow_handler_t    preflow_handler;
     7 #endif
     8     struct irqaction    *action;    /* IRQ action list */
     9     unsigned int        status_use_accessors;
    10     unsigned int        core_internal_state__do_not_mess_with_it;
    11     unsigned int        depth;        /* nested irq disables */
    12     unsigned int        wake_depth;    /* nested wake enables */
    13     unsigned int        irq_count;    /* For detecting broken IRQs */
    14     unsigned long        last_unhandled;    /* Aging timer for unhandled count */
    15     unsigned int        irqs_unhandled;
    16     atomic_t        threads_handled;
    17     int            threads_handled_last;
    18     raw_spinlock_t        lock;
    19     struct cpumask        *percpu_enabled;
    20 #ifdef CONFIG_SMP
    21     const struct cpumask    *affinity_hint;
    22     struct irq_affinity_notify *affinity_notify;
    23 #ifdef CONFIG_GENERIC_PENDING_IRQ
    24     cpumask_var_t        pending_mask;
    25 #endif
    26 #endif
    27     unsigned long        threads_oneshot;
    28     atomic_t        threads_active;
    29     wait_queue_head_t       wait_for_threads;
    30 #ifdef CONFIG_PROC_FS
    31     struct proc_dir_entry    *dir;
    32 #endif
    33     int            parent_irq;
    34     struct module        *owner;
    35     const char        *name;
    36 } ____cacheline_internodealigned_in_smp;

    真正的中断处理程序并不直接放在struct irq_desc结构体中,而是存放在struct irq_desc结构体的action成员所指向的struct irqaction结构体中,第8行。下面看下struct irqaction结构体类型(include/linux/interrupt.h)。

     1 struct irqaction {
     2     irq_handler_t        handler;                    
     3     void            *dev_id;
     4     void __percpu        *percpu_dev_id;
     5     struct irqaction    *next;
     6     irq_handler_t        thread_fn;
     7     struct task_struct    *thread;
     8     unsigned int        irq;
     9     unsigned int        flags;
    10     unsigned long        thread_flags;
    11     unsigned long        thread_mask;
    12     const char        *name;
    13     struct proc_dir_entry    *dir;
    14 } ____cacheline_internodealigned_in_smp;

    第1行handler中存放着中断服务例程。第5行next中存放下一个该类型结构体指针。因为一个中断号可以对应多个中断服务例程(中断线共享),内核将中断号相同的多个中断服务例程组织成一个链表,挂到以irq号作为下标的irq_desc数组元素中。

    回到handle_irq函数中,第8行获取到irq号所对应的struct irq_desc结构体指针desc,接着第15行执行了desc->handle_irq函数,在该函数中执行irq号的所有中断服务例程。

    在这里,一定要区别清楚IDT表idt_table和irq_desc数组的区别,idt_table中存放的是中断处理程序,而且这些中断处理程序的开头部分代码都是相同的,都要跳到common_interrupt函数中,进而去寻找中断服务例程。而irq_desc数组中存放的是中断服务例程,中断处理程序最终要通过该数组找到对应的中断服务例程并执行它。我们在编写驱动程序时,很多时候需要编写设备的中断服务例程,我们将中断服务例程存放在申请的struct irqaction结构体当中,并将该结构体挂到irq_desc数组的对应链表中,当中断发生后,系统会自动通过IDT--->GDT--->中断处理程序--->common_interrupt(属于中断处理程序)--->do_IRQ--->handle_irq,然后将irq_desc[NR_IRQS]数组中irq号对应的所有中断服务例程全部执行一遍。

    当中断服务例程执行结束后,就会返回文章最开始的common_interrupt函数中,开始执行第9行,跳入到ret_from_intr函数中(arch/x86/kernel/entry_32.S)

     1     ALIGN
     2     RING0_PTREGS_FRAME
     3 ret_from_exception:
     4     preempt_stop(CLBR_ANY)
     5 ret_from_intr:
     6     GET_THREAD_INFO(%ebp)
     7 #ifdef CONFIG_VM86
     8     movl PT_EFLAGS(%esp), %eax    # mix EFLAGS and CS
     9     movb PT_CS(%esp), %al
    10     andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
    11 #else
    12     /*
    13      * We can be coming here from child spawned by kernel_thread().
    14      */
    15     movl PT_CS(%esp), %eax
    16     andl $SEGMENT_RPL_MASK, %eax
    17 #endif
    18     cmpl $USER_RPL, %eax
    19     jb resume_kernel        # not returning to v8086 or userspace
    20 
    21 ENTRY(resume_userspace)
    22     LOCKDEP_SYS_EXIT
    23      DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
    24                     # setting need_resched or sigpending
    25                     # between sampling and the iret
    26     TRACE_IRQS_OFF
    27     movl TI_flags(%ebp), %ecx
    28     andl $_TIF_WORK_MASK, %ecx    # is there any work to be done on
    29                     # int/exception return?
    30     jne work_pending
    31     jmp restore_all
    32 END(ret_from_exception)

    第15行从当前栈中将cs寄存器的值存入eax中,第16行通过掩码计算,将eax中的DPL字段提取出来再存入eax,第18行比较eax(DPL)和用户空间权限的大小,如果DPL权限大的话,执行19行,恢复到内核态中,否则恢复到用户态,21行。

  • 相关阅读:
    python中创建实例属性
    Python中if __name__ == "__main__": 的理解
    模块
    python函数式编程
    python-复杂生成式
    生成器的测试
    mysql乱码配置
    javascript
    Sql Server 2008 R2 下载地址
    Microsoft Data Access Components 2.8
  • 原文地址:https://www.cnblogs.com/liangning/p/3874236.html
Copyright © 2011-2022 走看看