1、Linux.中断处理.中断处理通过上一篇文章就可以看出 Linux是使用 struct irq_desc这个结构体的数组来管理对应的中断号上的中断处理事务的。在看 irq_desc这个结构体之前可以试着猜想它需要包含哪些要素: 中断号。不过在后面可以看到中断号其实就暗含中该中断描述符相对于数组起始地址的偏移之中了。 管理这个中断号的是什么样的中断控制器。是 8259A还是 IO-APIC等等。以及对相当控制器的操作如:启停控制器、开启或禁用相应的中断号等。 在这个中断号上的中断处理函数。这其中必需要考虑的问题有:由于中断号的数量是有限的,必定就会出现多个设备共享一个中断号的情况,这时中断处理函
2、数应该是什么样的。 以上几点是一个中断请求描述符必需要包含的,至于其它的可以看struct irq_desc定义部分(include/linux/irq.h):147148149150151152153154155156157158159160161162163164/* struct irq_desc - interrupt descriptor* irq: interrupt number for this descriptor* timer_rand_state: pointer to timer rand state struct* kstat_irqs: irq stats per
3、cpu* irq_2_iommu: iommu with this irq* handle_irq: highlevel irq-events handler if NULL, _do_IRQ()* chip: low level interrupt hardware access* msi_desc: MSI descriptor* handler_data: per-IRQ data for the irq_chip methods* chip_data: platform-specific per-chip private data for the chip* methods, to a
4、llow shared chip implementations* action: the irq action chain* status: status information* depth: disable-depth, for nested irq_disable() calls* wake_depth: enable depth, for multiple set_irq_wake() 1651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971
5、98199200201202203204205206207208callers* irq_count: stats field to detect stalled irqs* last_unhandled: aging timer for unhandled count* irqs_unhandled: stats field for spurious unhandled interrupts* lock: locking for SMP* affinity: IRQ affinity on SMP* node: node index useful for balancing* pending
6、_mask: pending rebalanced interrupts* threads_active: number of irqaction threads currently running* wait_for_threads: wait queue for sync_irq to wait for threaded handlers* dir: /proc/irq/ procfs entry* name: flow handler name for /proc/interrupts output*/struct irq_desc unsigned int irq;struct tim
7、er_rand_state *timer_rand_state;unsigned int *kstat_irqs;#ifdef CONFIG_INTR_REMAPstruct irq_2_iommu *irq_2_iommu;#endifirq_flow_handler_t handle_irq;struct irq_chip *chip;struct msi_desc *msi_desc;void *handler_data;void *chip_data;struct irqaction *action; /* IRQ action list */unsigned int status;
8、/* IRQ status */unsigned int depth; /* nested irq disables */unsigned int wake_depth; /* nested wake enables */unsigned int irq_count; /* For detecting broken IRQs */unsigned long last_unhandled; /* Aging timer for unhandled count */unsigned int irqs_unhandled;spinlock_t lock;#ifdef CONFIG_SMPcpumas
9、k_var_t affinity;unsigned int node;#ifdef CONFIG_GENERIC_PENDING_IRQcpumask_var_t pending_mask;209 #endif#endifatomic_t threads_active;wait_queue_head_t wait_for_threads;#ifdef CONFIG_PROC_FSstruct proc_dir_entry *dir;#endifconst char *name; _cacheline_internodealigned_in_smp;在上面的结构体中 chip便是对应的中断控制器
10、, action 便是对应的中断处理函数,Linux 是将共享中断号的处理函数连成一个链表,依次调用的。下面先看 struct irq_chip(include/linux/irq.h)。该结构体所体现的都是对相应硬件操作的函数,可以不用全部定义,其中 typename是为了与以前代码兼容,现已经废弃。C struct irq_chip const char *name;unsigned int (*startup)(unsigned int irq);void (*shutdown)(unsigned int irq);void (*enable)(unsigned int irq);voi
11、d (*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);int (*set_affinity)(unsigned int irq,const struct cpumask *dest);int (*retrigger
12、)(unsigned int irq);int (*set_type)(unsigned int irq, unsigned int flow_type);int (*set_wake)(unsigned int irq, unsigned int on);void (*bus_lock)(unsigned int irq);void (*bus_sync_unlock)(unsigned int irq);/* Currently used only by UML, might disappear one day.*/#ifdef CONFIG_IRQ_RELEASE_METHODvoid
13、(*release)(unsigned int irq, void *dev_id);#endif/* For compatibility, -typename is copied into -name.* Will disappear.*/const char *typename;如用上述结构对 8259A进行抽象的代码如下(arch/x86/kernel/i8259.c)。假设我们的第 0号中断是时钟中断是由 8259A来管理的,我们只需要将irq_desc数组的第 0个元素的 chip指向 i8259A_chip,以后内核就可以通过这里注册的函数操作 8259A。C 3031323334
14、3536struct irq_chip i8259A_chip = .name = “XT-PIC“,.mask = disable_8259A_irq,.disable = disable_8259A_irq,.unmask = enable_8259A_irq,.mask_ack = mask_and_ack_8259A,;struct irqaction最主要的就是描述一个中断处理函数(include/linux/interrupt.h)。注意到 irq_handler_t的类型已经较以前(2.6.19)的处理函数的形式发生了变化,以前的形式是 irqreturn_t (*handler
15、)(int irq, void *dev_id, struct pt_regs *regs)。可以看到实际上是去掉了 regs这个参数。这是因为中断处理这里的代码会被频繁调用而绝大多数中断片处理程序都用不到 regs这个参数,所以就省掉了。C 8081828384858687typedef irqreturn_t (*irq_handler_t)(int, void *);/* struct irqaction - per interrupt action descriptor* handler: interrupt handler function* flags: flags (see IR
16、QF_* above)* name: name of the device* dev_id: cookie to identify the device888990919293949596979899100101102103104105106* next: pointer to the next irqaction for shared interrupts* irq: interrupt number* dir: pointer to the proc/irq/NN/name entry* thread_fn: interupt handler function for threaded i
17、nterrupts* thread: thread pointer for threaded interrupts* thread_flags: flags related to thread*/struct irqaction irq_handler_t handler;unsigned long flags;const char *name;void *dev_id;struct irqaction *next;int irq;struct proc_dir_entry *dir;irq_handler_t thread_fn;struct task_struct *thread;unsi
18、gned long thread_flags;当然光提供一个 irqaction描述一个处理程序是不够的,关键是怎么让驱动程序把中断处理程序挂到相当中断请求描述符的处理函数队列(action)上。内核为此提供了一个函数(include/linux/interrupt.h)C 116117118119120121static inline int _must_checkrequest_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)return request
19、_threaded_irq(irq, handler, NULL, flags, name, dev);接下来就可以描述中断的整体处理过程了,接上一篇文章,当程序执行到common_interrupt(arch/x86/kernel/entry_32.S)。程序首先保存所有寄存器,然后将 esp的值通过 eax作为参数传递给 do_IRQ(arch/x86/kernel/irq.c)然后通过 ret_from_intr返回。ASM 802803804805806807808809810811812813814815/* the CPU automatically disables interr
20、upts when executing an IRQ vector,* so IRQ-flags tracing has to follow that:*/.p2align CONFIG_X86_L1_CACHE_SHIFTcommon_interrupt:addl $-0x80,(%esp) /* Adjust vector into the -256,-1 range */SAVE_ALLTRACE_IRQS_OFFmovl %esp,%eaxcall do_IRQjmp ret_from_intrENDPROC(common_interrupt)CFI_ENDPROC接下来看 do_IR
21、Q。它最重要的就是调用了 handle_irq函数。C 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 */unsigned vector = regs-orig_ax;unsigned irq;exit_idle();irq_enter();irq = _get_cpu_var(vector_irq)vector;if (!handle_irq(irq, regs) ack_A
22、PIC_irq();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;handle_irq(arch/x86/kernel/irq_32.c)C 195196197198199200201202203204205206207208209210211212213bool handle_irq(unsigned irq, st
23、ruct pt_regs *regs)struct irq_desc *desc;int overflow;overflow = check_stack_overflow();desc = irq_to_desc(irq);if (unlikely(!desc)return false;if (!execute_on_irq_stack(overflow, desc, irq) if (unlikely(overflow)print_stack_overflow();desc-handle_irq(irq, desc);return true;第 206行根据名字就可以看出是将中断程序放到中断
24、栈中执行其函数的具体定义如下C 787980818283848586878889static inline intexecute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)union irq_ctx *curctx, *irqctx;u32 *isp, arg1, arg2;curctx = (union irq_ctx *) current_thread_info();irqctx = _get_cpu_var(hardirq_ctx);/* this is where we switch to the IRQ sta
25、ck. However, if 90919293949596979899100101102103104105106107108109110111112113114115116117118119120we are* already using the IRQ stack (because we interrupted a hardirq* handler) we cant do that and just have to keep using the* current stack (which is the irq stack already after all)*/if (unlikely(c
26、urctx = irqctx)return 0;/* build the stack frame on the IRQ stack */isp = (u32 *) (char *)irqctx + sizeof(*irqctx);irqctx-tinfo.task = curctx-tinfo.task;irqctx-tinfo.previous_esp = current_stack_pointer;/* Copy the softirq bits in preempt_count so that the* softirq checks work in the hardirq context
27、.*/irqctx-tinfo.preempt_count =(irqctx-tinfo.preempt_count if (unlikely(overflow)call_on_stack(print_stack_overflow, isp);asm volatile(“xchgl %ebx,%esp n“call *%edi n“movl %ebx,%esp n“: “=a“ (arg1), “=d“ (arg2), “=b“ (isp): “0“ (irq), “1“ (desc), “2“ (isp),“D“ (desc-handle_irq): “memory“, “cc“, “ecx
28、“);return 1;可以看到程序经过栈切换最终会调用 desc-handle_irq,而如果这个函数本身已经是在中断栈中执行则会转到 arch/x86/kernel/irq_32.c的 209行即handle_irq函数中的 desc-handle_irq(irq, desc);这一句,可见最终的调用就是 desc-handle_irq,而这个函数究竟是什么呢?这个成员其实是在上一篇文章就已经讲到的 init_ISA_irqs中初始化的。C 140141set_irq_chip_and_handler_name(i, 可见其实最终调用的就是 handle_level_irq(kernel/
29、irq/chip.c)。该函数最终会调用 handle_IRQ_event(kernel/irq/handle.c)。C 421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455voidhandle_level_irq(unsigned int irq, struct irq_desc *desc)struct irqaction *action;irqreturn_t action_ret;spin_lock(mask_ack_irq(de
30、sc, irq);if (unlikely(desc-status desc-status kstat_incr_irqs_this_cpu(irq, desc);/* If its disabled or no action available* keep it masked and get out of here*/action = desc-action;if (unlikely(!action | (desc-status desc-status |= IRQ_INPROGRESS;spin_unlock(action_ret = handle_IRQ_event(irq, actio
31、n);if (!noirqdebug)note_interrupt(irq, desc, action_ret);spin_lock(desc-status if (unlikely(desc-status else if (!(desc-status out_unlock:spin_unlock(EXPORT_SYMBOL_GPL(handle_level_irq);C 370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406i
32、rqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)irqreturn_t ret, retval = IRQ_NONE;unsigned int status = 0;if (!(action-flags do 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_TH
33、READ:/* 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 thr
34、ead crashed and was* killed we just pretend that we handled the* interrupt. The hardirq handler above has* disabled the device interrupt, so no irq407408409410411412413414415416417418419420421422423424425426427428429430431432* storm is lurking.*/if (likely(!test_bit(IRQTF_DIED,wake_up_process(action
35、-thread);/* Fall through to add to randomness */case IRQ_HANDLED:status |= action-flags;break;default:break;retval |= ret;action = action-next; while (action);if (status local_irq_disable();return retval;可以看到 380行就是调用注册在 irq_desc的中断函数处理链表上的函数。然后中断处理程序原路返回最后到达ret_from_intr(arch/x86/kernel/entry_32.S)
36、处,最后中断处理退出。ASM 343344345346347348349350351352# userspace resumption stub bypassing syscall exit tracingALIGNRING0_PTREGS_FRAMEret_from_exception:preempt_stop(CLBR_ANY)ret_from_intr:GET_THREAD_INFO(%ebp)check_userspace:movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CSmovb PT_CS(%esp), %al353354355356357
37、358359360361362363364365366367368369370371372373374375376377378379380381382383384385andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eaxcmpl $USER_RPL, %eaxjb resume_kernel # not returning to v8086 or userspaceENTRY(resume_userspace)LOCKDEP_SYS_EXITDISABLE_INTERRUPTS(CLBR_ANY) # make sure we dont miss an
38、interrupt# setting need_resched or sigpending# between sampling and the iretTRACE_IRQS_OFFmovl TI_flags(%ebp), %ecxandl $_TIF_WORK_MASK, %ecx # is there any work to be done on# int/exception return?jne work_pendingjmp restore_allEND(ret_from_exception)#ifdef CONFIG_PREEMPTENTRY(resume_kernel)DISABLE
39、_INTERRUPTS(CLBR_ANY)cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?jnz restore_allneed_resched:movl TI_flags(%ebp), %ecx # need_resched set ?testb $_TIF_NEED_RESCHED, %cljz restore_alltestl $X86_EFLAGS_IF,PT_EFLAGS(%esp) # interrupts off (exception path) ?jz restore_allcall preempt_schedule_irqjmp need_reschedEND(resume_kernel)#endifCFI_ENDPROC