1、超强的 Linux 中断分析日期:2008-09-13 来源: 作者:1) IPI 中断的初始化。intr_init_hook 调用 apic_intr_init(), 后者再调用 如果 CONFIG_SMPsmp_intr_init(), 这个函数设置 IPI 中断的处理, 然后, apic_intr_init()为另外两个IPI:SPURIOUS_APIC_VECTOR 和ERROR_APIC_VECTOR 设置 ISR。2) irq_descNR_IRQSstruct irq_desc,亦即 irq_desc_t,描述了一个 irq 的属性, 如 irqaction、depth、pend
2、ing_mask 等。 include/linux/irq.h:struct irq_desc irq_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; /* IRQ status */unsigned int depth; /* nested irq disables */unsigned i
3、nt wake_depth; /* nested wake enables */unsigned int irq_count; /* For detecting broken IRQs */unsigned int irqs_unhandled;spinlock_t lock;#ifdef CONFIG_SMPcpumask_t affinity;unsigned int cpu;#endif#if defined(CONFIG_GENERIC_PENDING_IRQ) | defined(CONFIG_IRQBALANCE)cpumask_t pending_mask;#endif#ifde
4、f CONFIG_PROC_FSstruct proc_dir_entry *dir;#endifconst char *name; _cacheline_internodealigned_in_smp;Linux 有一个全局变量, 包含了了所有的 IRQ:(kernel/irq/handle.c)struct irq_desc irq_descNR_IRQS _cacheline_aligned = 0 . NR_IRQS-1 = .status = IRQ_DISABLED,.chip = EXPORT_PER_CPU_SYMBOL(irq_stat);irq_stat_t 的定义。typ
5、edef struct unsigned int _softirq_pending;unsigned long idle_timestamp;unsigned int _nmi_count; /* arch dependent */unsigned int apic_timer_irqs; /* arch dependent */ _cacheline_aligned irq_cpustat_t;5). 中断共享我们知道,多个中断源可以共享一个 irq 线。 Linux 的实现方式是,每个中断源都有自己的一个 struct irqaction,irqaction 结构的定义:struct ir
6、qaction irq_handler_t handler;unsigned long flags;cpumask_t mask;const char *name;void *dev_id;struct irqaction *next;int irq;struct proc_dir_entry *dir;同一个 irq 可能有多个 irqaction,组成一个链表。 struct irq_desc 中有个域:struct irqaction *action; /* IRQ action list */这个链表就包含了所有共享该 irq 号的中断源(及其对应的 handler 等信息)。 当de
7、vice driver进行 request_irq()时,会为它生成一个 irqaction,设置相应的值,然后挂载irq_desc.action 队列中( 是添加在链表的最后面 )。request_irq(irq, handler, irqflags, devname, dev_id) setup_irq(irq, irqaction)flags 有 3 个:IRQF_SHARED : 共享中断号IRQF_DISABLED : 就是旧时代的 SA_INTERRUPT,设置了该标志,则执行 ISR 时关本地中断IRQF_SAMPLE_RANDOM : 告诉内核,本中断源可以用作随机数生成器的熵
8、池只有满足以下条件,irq 才可以在多个中断源之间共享:a). 每个中断源都愿意共享 irq: request_irq 时指定了 IRQF_SHAREDb). 试图共享一个 irq 的中断源,具有相同的触发机制(都是 level trigger,或者都是edgetrigger),并且具有相同的 polarity(都是低电平有效,或者都是高电平有效)下面是 set_irq()函数中判断 old 和 new 两个中断源是否可以 share 同一个 irq 号的代码:/* Cant share interrupts unless both agree to and are* the same typ
9、e (level, edge, polarity). So both flag* fields must have IRQF_SHARED set and the bits which* set the trigger type must match.*/if (!(old-flags goto mismatch;6). 中断处理(do_IRQ, _do_IRQ, generic_handle_irq, etc) - Part I: _do_IRQ_do_IRQ()是 genericirq 引入之前的通用中断处理函数( 除了 IPI 中断,其它所有中断/异常都经过它),它由 do_IRQ 调用
10、,并调用 handle_IRQ_event(而 handle_IRQ_event 会调用各个driver 的 ISR)。 在引入 genericirq 之后, _do_IRQ()函数已基本不用了。 64 位的 X86 系统上还可能使用它(通过 do_IRQ generic_handle_irq),32 位的 x86 已经完全不用它了。然而我们还是看一下_do_IRQ 函数,因为道理是一样的:_do_IRQ():/*/首先给 irq_descirq.lock 加锁,以免别的 CPU 访问该 desc 结构spin_lock(/发送 ACK 给中断控制器if (desc-chip-ack)desc
11、-chip-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_REPLAY 和 IRQ_WAITING 标志*/status = desc-status /*设置 IRQ_PENDING 标志。 这个 flag 的意思是,已经 ACK 但尚未处理*/status |= IRQ_PENDING; /* we _want_ to handle it */* If t
12、he IRQ is disabled for whatever reason, we cannot* use the action we have.*/*如果 IRQ 被 disable 了,但是我们收到了中断,说明这是个 spurious interrupt,* 有些有 BUG 的主板等硬件会干这种事*/action = NULL;/* 只要 IRQ_DISABLED 或者 IRQ_INPROGRESS 被设置,我们就不 handle 该irq。* 对于 IRQ_INPROGRESS 被设置的情况,说明此 irq 号的另一个实例正运行在* 另一个 CPU 上,我们就不处理了,而是让 _那个_
13、 CPU 在运行完它的 ISR 时再检查* 一下 IRQ_PENDING 标志,那时候它会再去处理我们这里逃避的事情的*/if (likely(!(status status /* we commit to handling,清除 pending 标志 */ status |= IRQ_INPROGRESS; /* we are handling it ,设置 inprogress 标志*/desc-status = status;/* If there is no IRQ handler or it was disabled, exit early.* Since we set PENDIN
14、G, 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 arr
15、ive while we are in do_IRQ* or in the handler. But the code herefor (;) irqreturn_t action_ret;/真正的 IRQ 处理是 handle_IRQ_event,我们先 unlockspin_unlock(action_ret = handle_IRQ_event(irq, action);spin_lock(/再 lock,因为后面还要 unlock/* 在我们调用 handle_IRQ_event 时,如果同一个 irq 又在另一个 CPU 上* 来了一次,那个 CPU 会检测到 IRQ_INPROGR
16、ESS 标志,只设置了IRQ_PENDING* 标志便退出了。 这时我们就会检测到该标志,从而再处理第 2 次到来的 irq * 注意! IRQ_PENDING 只是个逻辑标志,而不是一个 counter!所以,这种方式* 只能处理同一 irq 的两个实例!如果发生了更多实例,第 3 个,第 4 个就丢失了*/如果没有第 2 个需要处理,退出if (likely(!(desc-status /还有第 2 个需要处理,那么就清除 IRQ_PENDING 标志,表示我们已经答应要处理它了desc-status /*/7). 中断处理(do_IRQ, _do_IRQ, generic_handle_
17、irq, etc) - Part II: handle_IRQ_eventhandle_IRQ_event()依次调用 irq_descirq-action 链表上的每一个 action。它会先打开中断(如果 request_irq 时没有设置 IRQF_DISABLED 标志) ,然后一个个执行 irqaction,再禁用本地中断。handle_IRQ_event:irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)irqreturn_t ret, retval = IRQ_NONE;unsigned
18、 int status = 0;handle_dynamic_tick(action);/如果指定了 IRQF_DISABLED,就在关中断的情形下执行 ISR/否则的话,在开中断的情形下执行 ISRif (!(action-flags /该循环遍历 irq_descirq-action 链表,一个个调用其 handler 域do ret = action-handler(irq, action-dev_id);if (ret = IRQ_HANDLED)status |= action-flags;retval |= ret;action = action-next; while (acti
19、on);if (status local_irq_disable();return retval;Linux 有两种情况可能导致丢中断,都是在 SMP 下才会发生的:a). CPU1 在处理 irq N,结果又来了一个 irq N 在 CPU2 上执行,这时候该 CPU2 只设置irq_descirq.status 的 IRQ_PENDING 标志,以便 CPU1 去检查从而再执行一遍。当如果 CPU3 又收到一次,也设置 IRQ_PENDING 标志,这时 CPU2 设置的信息会丢失。补救办法:无b). CPU1 在处理器某 IRQ 之前,先发送 ACK 给 PIC,结果这时候 CPU2 通
20、过 PIC禁用了该 irq,从而导致 irq_descirq.status 的 IRQ_DISABLED 标志被设置。 然后 CPU1 在正要处理irq 时发现对应的 IRQ_DISABLED 标志置位,于是退出。 这样就丢了一次中断。补救办法: 在下一次 enable_irq()被调用时,检查是否存在的这样的丢失。若然,调用 check_irq_resend()重新 generate 一次中断。注意,在_do_IRQ()的一开始会清楚 irq_descirq.status 的 IRQ_REPLAY标志,这时为了防止对一次 irq 丢失补救多次。8). 中断处理(do_IRQ, _do_IRQ
21、, generic_handle_irq, etc) - Part III: Generic IRQ 补丁FIXME:我记得 generic irq 补丁是 Thomas Gleixner 和 Ingo Molnar 在大约 2.6.17 时引入的,当时支持 i386、x86-64 和 arm 三个体系结构。generic irq 层的引入,是为了剥离 irq flow 和 irq chip 过于紧密的耦合。 为 driver程序员提供通用的 API 来 request/enable/disable/free 中断,这样程序员不用知道任何底层的中断控制器细节。8.1) 它为 driver 程序
22、员提供的 highlevel 的 API:request_irq()free_irq()disable_irq()enable_irq()disable_irq_nosync() (SMP)synchronize_irq() (SMP)set_irq_type()set_irq_wake()set_irq_data()set_irq_chip()set_irq_chip_data()8.2) 它为 irq flow 提供了一组预定义了的方法:handle_level_irq() = 针对 level type 的 irq handlerhandle_edge_irq() = 针对 edge t
23、ype 的 irq handlerhandle_simple_irq() = 针对 Simple and software-decoded IRQS/FIXME: 我猜测 percpu irq 不是 IPI,而是某种 x86 没有的东西handle_percpu_irq() = 针对 per-cpu local IRQshandle_fasteoi_irq() = 针对 transparent controllers, 目前 IO-APIC 主要用它和 edge/FIXME: 什么叫透明的中断控制器?老子咋看不懂涅 ?Irq 的 flow type, generic irq 有以下数种:#de
24、fine IRQ_TYPE_NONE 0x00000000 /* Default, unspecified type */#define IRQ_TYPE_EDGE_RISING 0x00000001 /* Edge rising type */#define IRQ_TYPE_EDGE_FALLING 0x00000002 /* Edge falling type */#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)#define IRQ_TYPE_LEVEL_HIGH 0x00000004 /
25、* Level high type */#define IRQ_TYPE_LEVEL_LOW 0x00000008 /* Level low type */#define IRQ_TYPE_SENSE_MASK 0x0000000f /* Mask of the above */#define IRQ_TYPE_PROBE 0x00000010 /* Probing in progress */-没有看到 simple 类型和 per-cpu 类型,我估计这 2 者都是其他 architectures 上的。 这里把EDGE 触发的 irq 又分成了上升沿、下降沿和 both, level 触
26、发的又分成了低电平有效和 high active。这 5 个函数取代了原来的_do_IRQ ,由 do_IRQ 直接调用 :desc-handle_irq(irq, desc);而这个 irq_descirq.handle_irq 又是在哪里设置的呢? 不同的 irq chip 有不同的设置,现在让我们看一下 ioapic_chip 上的 irqs 的设置:static void ioapic_register_intr(int irq, unsigned long trigger)/* 如果不是 edge 触发的,就设置为 handle_fasteoi_irq */if (trigger)
27、irq_descirq.status |= IRQ_LEVEL;set_irq_chip_and_handler_name(irq, else /* 如果是 edge 触发的,就设置为 handle_edge_irq */irq_descirq.status set_irq_chip_and_handler_name(irq, 原来 MSI 中断也是用 handle_edge_irq 处理的,见代码:pci_enable_msi() msi_capability_init() = arch_setup_msi_irqs() arch_setup_msi_irq():pci_enable_msi
28、m() msim_capability_init() /set_irq_msi(irq, desc);write_msi_msg(irq, set_irq_chip_and_handler_name(irq, 8.4) genericirq 提供的一些 public functionssynchronize_irq : wait for pending IRQ handlers (on other CPUs) disable_irq_nosync : disable an irq without waiting disable_irq : disable an irq and wait for
29、 completion enable_irq : enable handling of an irq set_irq_wake : control irq power management wakeup free_irq : free an interrupt request_irq : allocate an interrupt line set_irq_chip : set the irq chip for an irq set_irq_type : set the irq type for an irq set_irq_data : set irq type data for an ir
30、q set_irq_chip_data : set irq chip data for an irq8.5) geneirc irq 提供的一些 internal functionshandle_bad_irq : handle spurious and unhandled irqs handle_IRQ_event : irq action chain handler _do_IRQ : original all in: initialize a dynamically allocated irq dynamic_irq_cleanup : cleanup a dynamically all
31、ocated irq set_irq_msi : set irq type data for an irq handle_simple_irq : Simple and software-decoded IRQs. handle_level_irq : Level type irq handler handle_fasteoi_irq : irq handler for transparent controllers handle_edge_irq : edge type IRQ handler handle_percpu_irq : Per CPU local irq handler8.6)
32、 irq_chip 结构的方法startup : start up the interrupt (defaults to -enable if NULL)enable 中断,使 PIC 可以 handle 它shutdown : shut down the interrupt (defaults to -disable if NULL)enable : enable the interrupt (defaults to chip-unmask if NULL)disable : disable the interrupt (defaults to chip-mask if NULL)ack :
33、 start of a new interrupt通知 PIC:CPU 开始处理这个 irq 了mask : mask an interrupt sourcemask_ack : ack and mask an interrupt sourcemask 和 ack 方法的结合,这样在某些平台上可以得到优化unmask : unmask an interrupt sourceeoi : end of interrupt - chip levelend : end of interrupt - flow level通知 PIC:中断处理完毕set_affinity : set the CPU af
34、finity: resend an IRQ to the CPU重新创建和递送一个 irqset_type : set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ设置 irq 的 flow type: level, edge, simple, per-cpuset_wake : enable/disable power-management wake-on of an IRQ是否支持由该 irq 来 wake 睡眠中的系统release : release function solely used by UML仅由 ULM 使用typename : obsoleted by name, kept as migration helper已废弃我自己还没弄懂, 只是零星的记录了一些看到的东西。 (因此别问我,否则失望)看高手的文档,可以从无到有的学习,真的佩服这种写文档的能力。 我就只会写只有自己能看懂的文档。