超强的Linux中断分析[日期:2008-09-13] 来源: 作者:
1) IPI中断的初始化。
intr_init_hook调用apic_intr_init(),后者再调用──如果CONFIG_SMP──smp_intr_init(),
这个函数设置IPI中断的处理,然后, apic_intr_init()为另外两个IPI:SPURIOUS_APIC_VECTOR和
ERROR_APIC_VECTOR设置ISR。
2) irq_desc[NR_IRQS]
structirq_desc,亦即irq_desc_t,描述了一个irq的属性,如irqaction、depth、
pending_mask等。
include/linux/irq.h:
struct irq_desc {
irq_flow_handler_thandle_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 int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#ifdefined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
}____cacheline_internodealigned_in_smp;
Linux有一个全局变量,包含了了所有的IRQ:(kernel/irq/handle.c)
struct irq_descirq_desc[NR_IRQS] __cacheline_aligned = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.chip = &no_irq_chip,
.handle_irq =handle_bad_irq,
.depth = 1,
.lock = SPIN_LOCK_UNLOCKED,
#ifdef CONFIG_SMP
.affinity = CPU_MASK_ALL
#endif
}
}
3)irq_chip(即在genericirq之前的hw_interrupt_type)
>以下这段是genericirq之前的:
>Linux支持N种可编程中断控制器PIC,所以有一个struct hw_interrupt_type,对于i8259A
>来说,这个结构是i8259A_irq_type,对于IOAPIC来说,根据设置为电平触发或边沿触发的方式,
>分别有ioapic_level_type和ioapic_edge_type两个不同的结构。
在引入genericirq补丁之后,定义了几个irq_chip结构:
针对i386和x86-64各有一个定义的:
ioapic_chip,
i8259A_chip,
lapic_chip,
msi_chip,
ht_irq_chip,
vmi_chip,
针对visw体系的:
cobalt_irq_chip,
piix4_master_irq_type,
piix4_virtual_irq_type
针对voyager体系的:
vic_chip
其它目的的:
no_irq_chip,
dummy_irq_chip
4) irq_stat[NR_CPUS]
Linux定义了一个全局的数组,用来描述每个CPU上的irq处理状态:(arch/i386/kernel/irq.c)
DEFINE_PER_CPU(irq_cpustat_t,irq_stat) __cacheline_internodealigned_in_smp;
EXPORT_PER_CPU_SYMBOL(irq_stat);
irq_stat_t的定义。
typedef struct {
unsigned int__softirq_pending;
unsigned longidle_timestamp;
unsigned int __nmi_count;/* arch dependent */
unsigned intapic_timer_irqs; /* arch dependent */
} ____cacheline_alignedirq_cpustat_t;
5). 中断共享
我们知道,多个中断源可以共享一个irq线。Linux的实现方式是,每个中断源都有自己的
一个structirqaction,
irqaction结构的定义:
struct irqaction {
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等信息)。当device driver
进行request_irq()时,会为它生成一个irqaction,设置相应的值,然后挂载
irq_desc[<irq>].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 :告诉内核,本中断源可以用作随机数生成器的熵池
只有满足以下条件,irq才可以在多个中断源之间共享:
a). 每个中断源都愿意共享irq:request_irq时指定了IRQF_SHARED
b).试图共享一个irq的中断源,具有相同的触发机制(都是leveltrigger,或者都是edge
trigger),并且具有相同的polarity(都是低电平有效,或者都是高电平有效)
下面是set_irq()函数中判断old和new两个中断源是否可以share同一个irq号的代码:
/*
* Can't share interrupts unlessboth agree to and are
* the same type (level, edge,polarity). So both flag
* fields must have IRQF_SHAREDset and the bits which
* set the trigger type mustmatch.
*/
if (!((old->flags &new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags)& IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch;
}
6). 中断处理(do_IRQ,__do_IRQ, generic_handle_irq, etc) - Part I: __do_IRQ
__do_IRQ()是genericirq引入之前的通用中断处理函数(除了IPI中断,其它所有中断/异常
都经过它),它由do_IRQ调用,并调用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_desc[irq].lock加锁,以免别的CPU访问该desc结构
spin_lock(&desc->lock);
//发送ACK给中断控制器
if (desc->chip->ack)
desc->chip->ack(irq);
/*
* REPLAY is when Linux resends anIRQ that was dropped earlier
* WAITING is used by probe tomark irqs that are being tested
*/
/*清除IRQ_REPLAY和IRQ_WAITING标志*/
status = desc->status &~(IRQ_REPLAY | IRQ_WAITING);
/*设置IRQ_PENDING标志。这个flag的意思是,已经ACK但尚未处理*/
status |= IRQ_PENDING; /* we_want_ to handle it */
/*
* If the IRQ is disabled forwhatever reason, we cannot
* use the action we have.
*/
/*如果IRQ被disable了,但是我们收到了中断,说明这是个spuriousinterrupt,
* 有些有BUG的主板等硬件会干这种事
*/
action = NULL;
/*只要IRQ_DISABLED或者IRQ_INPROGRESS被设置,我们就不handle该irq。
*
*对于IRQ_INPROGRESS被设置的情况,说明此irq号的另一个实例正运行在
* 另一个CPU上,我们就不处理了,而是让_那个_ CPU在运行完它的ISR时再检查
*一下IRQ_PENDING标志,那时候它会再去处理我们这里逃避的事情的
*/
if (likely(!(status &(IRQ_DISABLED | IRQ_INPROGRESS)))) { /*正常情况下2这都不被设置,
*那我们就设置desc->status
*/
action = desc->action;
status &= ~IRQ_PENDING; /*we commit to handling,清除pending标志*/
status |= IRQ_INPROGRESS; /*we are handling it ,设置inprogress标志*/
}
desc->status = status;
/*
* If there is no IRQ handler orit was disabled, exit early.
* Since we set PENDING, ifanother processor is handling
* a different instance of thissame irq, the other processor
* will take care of it.
*/
if (unlikely(!action))
goto out;
/*
* Edge triggered interrupts needto remember
* pending events.
* This applies to any hwinterrupts that allow a second
* instance of the same irq toarrive while we are in do_IRQ
* or in the handler. But the codehere>for (;;) {
irqreturn_t action_ret;
//真正的IRQ处理是handle_IRQ_event,我们先unlock
spin_unlock(&desc->lock);
action_ret =handle_IRQ_event(irq, action);
spin_lock(&desc->lock);
//再lock,因为后面还要unlock
/*
*在我们调用handle_IRQ_event时,如果同一个irq又在另一个CPU上
*来了一次,那个CPU会检测到IRQ_INPROGRESS标志,只设置了IRQ_PENDING
* 标志便退出了。这时我们就会检测到该标志,从而再处理第2次到来的irq
*
* 注意!IRQ_PENDING只是个逻辑标志,而不是一个counter!所以,这种方式
*只能处理同一irq的两个实例!如果发生了更多实例,第3个,第4个……就丢失了
*/
//如果没有第2个需要处理,退出
if (likely(!(desc->status &IRQ_PENDING)))
break;
//还有第2个需要处理,那么就清除IRQ_PENDING标志,表示我们已经答应要处理它了
desc->status &=~IRQ_PENDING;
}
/*}}}*/
7). 中断处理(do_IRQ,__do_IRQ, generic_handle_irq, etc) - Part II: handle_IRQ_event
handle_IRQ_event()依次调用irq_desc[irq]->action链表上的每一个action。
它会先打开中断(如果request_irq时没有设置IRQF_DISABLED标志),然后一个个执行irqaction,
再禁用本地中断。
handle_IRQ_event:
irqreturn_thandle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval =IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
//如果指定了IRQF_DISABLED,就在关中断的情形下执行ISR
//否则的话,在开中断的情形下执行ISR
if (!(action->flags &IRQF_DISABLED))
local_irq_enable_in_hardirq();
//该循环遍历irq_desc[irq]->action链表,一个个调用其handler域
do {
ret = action->handler(irq,action->dev_id);
if (ret == IRQ_HANDLED)
status |=action->flags;
retval |= ret;
action = action->next;
} while (action);
if (status &IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
Linux有两种情况可能导致丢中断,都是在SMP下才会发生的:
a). CPU1在处理irqN,结果又来了一个irqN在CPU2上执行,这时候该CPU2只设置
irq_desc[irq].status的IRQ_PENDING标志,以便CPU1去检查从而再执行一遍。
当如果CPU3又收到一次,也设置IRQ_PENDING标志,这时CPU2设置的信息会丢失。
补救办法:无
b).CPU1在处理器某IRQ之前,先发送ACK给PIC,结果这时候CPU2通过PIC禁用了该irq,
从而导致irq_desc[irq].status的IRQ_DISABLED标志被设置。然后CPU1在正要处理
irq时发现对应的IRQ_DISABLED标志置位,于是退出。这样就丢了一次中断。
补救办法:在下一次enable_irq()被调用时,检查是否存在的这样的丢失。若然,
调用check_irq_resend()重新generate一次中断。
注意,在__do_IRQ()的一开始会清楚irq_desc[irq].status的IRQ_REPLAY
标志,这时为了防止对一次irq丢失「补救」多次。
8). 中断处理(do_IRQ,__do_IRQ, generic_handle_irq, etc) - Part III: Generic IRQ补丁
FIXME:我记得genericirq补丁是Thomas Gleixner和IngoMolnar在大约2.6.17时引入的,
当时支持i386、x86-64和arm三个体系结构。
generic irq层的引入,是为了剥离irqflow和irq chip过于紧密的耦合。为driver程序员提供
通用的API来request/enable/disable/free中断,这样程序员不用知道任何底层的中断控制器细节。
8.1) 它为driver程序员提供的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) 它为irqflow提供了一组预定义了的方法:
handle_level_irq() =>针对level type的irqhandler
handle_edge_irq() =>针对edge type的irqhandler
handle_simple_irq() =>针对Simple and software-decodedIRQS
//FIXME: 我猜测percpuirq不是IPI,而是某种x86没有的东西
handle_percpu_irq() =>针对per-cpu local IRQs
handle_fasteoi_irq() =>针对transparent controllers,目前IO-APIC主要用它和edge
//FIXME:什么叫透明的中断控制器?老子咋看不懂涅?
Irq的flowtype, generic irq有以下数种:
#define 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 /* 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触发的又分成了低电平有效和highactive。
这5个函数取代了原来的__do_IRQ,由do_IRQ直接调用:
desc->handle_irq(irq, desc);
而这个irq_desc[irq].handle_irq又是在哪里设置的呢?不同的irq chip有不同的设置,现在让
我们看一下ioapic_chip上的irqs的设置:
static voidioapic_register_intr(int irq, unsigned long trigger)
{
/*如果不是edge触发的,就设置为handle_fasteoi_irq*//
if (trigger) {
irq_desc[irq].status |=IRQ_LEVEL;
set_irq_chip_and_handler_name(irq, &ioapic_chip,
handle_fasteoi_irq, "fasteoi");
} else {
/*如果是edge触发的,就设置为handle_edge_irq*/
irq_desc[irq].status &=~IRQ_LEVEL;
set_irq_chip_and_handler_name(irq, &ioapic_chip,
handle_edge_irq, "edge");
}
}
原来MSI中断也是用handle_edge_irq处理的,见代码:
pci_enable_msi() >msi_capability_init() \
=>arch_setup_msi_irqs() > arch_setup_msi_irq():
pci_enable_msim() >msim_capability_init() /
set_irq_msi(irq, desc);
write_msi_msg(irq, &msg);
set_irq_chip_and_handler_name(irq, &msi_chip, handle_edge_irq,"edge");
8.4) genericirq提供的一些publicfunctions
synchronize_irq : wait forpending IRQ handlers (on other CPUs)
disable_irq_nosync : disablean irq without waiting
disable_irq : disable anirq and wait for completion
enable_irq : enablehandling of an irq
set_irq_wake : control irqpower management wakeup
free_irq : free aninterrupt
request_irq : allocate aninterrupt line
set_irq_chip : set the irqchip for an irq
set_irq_type : set the irqtype for an irq
set_irq_data : set irqtype data for an irq
set_irq_chip_data : set irqchip data for an irq
8.5) geneirc irq提供的一些internalfunctions
handle_bad_irq : handlespurious and unhandled irqs
handle_IRQ_event : irqaction chain handler
__do_IRQ : original allin>: initialize a dynamically allocated irq
dynamic_irq_cleanup :cleanup a dynamically allocated irq
set_irq_msi : set irq typedata for an irq
handle_simple_irq : Simpleand software-decoded IRQs.
handle_level_irq : Leveltype irq handler
handle_fasteoi_irq : irqhandler for transparent controllers
handle_edge_irq : edge typeIRQ handler
handle_percpu_irq : Per CPUlocal irq handler
8.6) irq_chip结构的方法
startup : start up theinterrupt (defaults to ->enable if NULL)
enable中断,使PIC可以handle它
shutdown : shut down theinterrupt (defaults to ->disable if NULL)
enable : enable theinterrupt (defaults to chip->unmask if NULL)
disable : disable theinterrupt (defaults to chip->mask if NULL)
ack : start of a newinterrupt
通知PIC:CPU开始处理这个irq了
mask : mask an interruptsource
mask_ack : ack and mask aninterrupt source
mask和ack方法的结合,这样在某些平台上可以得到优化
unmask : unmask aninterrupt source
eoi : end of interrupt -chip level
end : end of interrupt -flow level
通知PIC:中断处理完毕
set_affinity : set the CPUaffinity>: resend an IRQ to the CPU
重新创建和递送一个irq
set_type : set the flow type(IRQ_TYPE_LEVEL/etc.) of an IRQ
设置irq的flowtype: level, edge, simple, per-cpu
set_wake : enable/disablepower-management wake-on of an IRQ
是否支持由该irq来wake睡眠中的系统
release : releasefunction solely used by UML
仅由ULM使用
typename : obsoleted by name,kept as migration helper
已废弃
我自己还没弄懂,只是零星的记录了一些看到的东西。 (因此别问我,否则失望)
看高手的文档,可以从无到有的学习,真的佩服这种写文档的能力。我就只会写只有自己能看懂的文档。