中断——中断处理程序的进入与退出(三) (基于3.16-rc4) – Mr.Sting

上一篇博文我们分析了中断描述符表的中断门初始化过程,并且在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)n,
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/entrt_32.S)

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

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

 

本文链接:中断——中断处理程序的进入与退出(三) (基于3.16-rc4),转载请注明。



You must enable javascript to see captcha here!

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress

无觅相关文章插件,快速提升流量