超强的Linux中断分析Word格式.docx
《超强的Linux中断分析Word格式.docx》由会员分享,可在线阅读,更多相关《超强的Linux中断分析Word格式.docx(14页珍藏版)》请在冰豆网上搜索。
#endif
#ifdefined(CONFIG_GENERIC_PENDING_IRQ)||defined(CONFIG_IRQBALANCE)
cpumask_tpending_mask;
#ifdefCONFIG_PROC_FS
structproc_dir_entry*dir;
constchar*name;
}____cacheline_internodealigned_in_smp;
Linux有一个全局变量,包含了了所有的IRQ:
(kernel/irq/handle.c)
structirq_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,
.affinity=CPU_MASK_ALL
}
3)irq_chip(即在genericirq之前的hw_interrupt_type)
>
以下这段是genericirq之前的:
Linux支持N种可编程中断控制器PIC,所以有一个structhw_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的定义。
typedefstruct{
unsignedint__softirq_pending;
unsignedlongidle_timestamp;
unsignedint__nmi_count;
/*archdependent*/
unsignedintapic_timer_irqs;
}____cacheline_alignedirq_cpustat_t;
5).中断共享
我们知道,多个中断源可以共享一个irq线。
Linux的实现方式是,每个中断源都有自己的
一个structirqaction,
irqaction结构的定义:
structirqaction{
irq_handler_thandler;
unsignedlongflags;
cpumask_tmask;
void*dev_id;
structirqaction*next;
intirq;
};
同一个irq可能有多个irqaction,组成一个链表。
structirq_desc中有个域:
这个链表就包含了所有共享该irq号的中断源(及其对应的handler等信息)。
当devicedriver
进行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'
tshareinterruptsunlessbothagreetoandare
*thesametype(level,edge,polarity).Sobothflag
*fieldsmusthaveIRQF_SHAREDsetandthebitswhich
*setthetriggertypemustmatch.
*/
if(!
((old->
flags&
new->
flags)&
IRQF_SHARED)||
((old->
flags^new->
IRQF_TRIGGER_MASK)){
old_name=old->
name;
gotomismatch;
6).中断处理(do_IRQ,__do_IRQ,generic_handle_irq,etc)-PartI:
__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->
ack(irq);
*REPLAYiswhenLinuxresendsanIRQthatwasdroppedearlier
*WAITINGisusedbyprobetomarkirqsthatarebeingtested
/*清除IRQ_REPLAY和IRQ_WAITING标志*/
status=desc->
status&
~(IRQ_REPLAY|IRQ_WAITING);
/*设置IRQ_PENDING标志。
这个flag的意思是,已经ACK但尚未处理*/
status|=IRQ_PENDING;
/*we_want_tohandleit*/
*IftheIRQisdisabledforwhateverreason,wecannot
*usetheactionwehave.
/*如果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;
/*wecommittohandling,清除pending标志*/
status|=IRQ_INPROGRESS;
/*wearehandlingit,设置inprogress标志*/
status=status;
*IfthereisnoIRQhandleroritwasdisabled,exitearly.
*SincewesetPENDING,ifanotherprocessorishandling
*adifferentinstanceofthissameirq,theotherprocessor
*willtakecareofit.
if(unlikely(!
action))
gotoout;
*Edgetriggeredinterruptsneedtoremember
*pendingevents.
*Thisappliestoanyhwinterruptsthatallowasecond
*instanceofthesameirqtoarrivewhileweareindo_IRQ
*orinthehandler.Butthecodehere>
for(;
;
){
irqreturn_taction_ret;
//真正的IRQ处理是handle_IRQ_event,我们先unlock
spin_unlock(&
action_ret=handle_IRQ_event(irq,action);
//再lock,因为后面还要unlock
*在我们调用handle_IRQ_event时,如果同一个irq又在另一个CPU上
*来了一次,那个CPU会检测到IRQ_INPROGRESS标志,只设置了IRQ_PENDING
*标志便退出了。
这时我们就会检测到该标志,从而再处理第2次到来的irq
*注意!
IRQ_PENDING只是个逻辑标志,而不是一个counter!
所以,这种方式
*只能处理同一irq的两个实例!
如果发生了更多实例,第3个,第4个……就丢失了
//如果没有第2个需要处理,退出
(desc->
IRQ_PENDING)))
break;
//还有第2个需要处理,那么就清除IRQ_PENDING标志,表示我们已经答应要处理它了
/*}}}*/
7).中断处理(do_IRQ,__do_IRQ,generic_handle_irq,etc)-PartII:
handle_IRQ_event
handle_IRQ_event()依次调用irq_desc[irq]->
action链表上的每一个action。
它会先打开中断(如果request_irq时没有设置IRQF_DISABLED标志),然后一个个执行irqaction,
再禁用本地中断。
handle_IRQ_event:
irqreturn_thandle_IRQ_event(unsignedintirq,structirqaction*action)
{
irqreturn_tret,retval=IRQ_NONE;
unsignedintstatus=0;
handle_dynamic_tick(action);
//如果指定了IRQF_DISABLED,就在关中断的情形下执行ISR
//否则的话,在开中断的情形下执行ISR
(action->
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();
returnretval;
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)-PartIII:
GenericIRQ补丁
FIXME:
我记得genericirq补丁是ThomasGleixner和IngoMolnar在大约2.6.17时引入的,
当时支持i386、x86-64和arm三个体系结构。
genericirq层的引入,是为了剥离irqflow和irqchip过于紧密的耦合。
为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()=>
针对leveltype的irqhandler
handle_edge_irq()=>
针对edgetype的irqhandler
handle_simple_irq()=>
针对Simpleandsoftware-decodedIRQS
//FIXME:
我猜测percpuirq不是IPI,而是某种x86没有的东西
handle_percpu_irq()=>
针对per-cpulocalIRQs
handle_fasteoi_irq()=>
针对transparentcontrollers,目前IO-APIC主要用它和edge
什么叫透明的中断控制器?
老子咋看不懂涅?
Irq的flowtype,genericirq有以下数种:
#defineIRQ_TYPE_NONE0x00000000/*Default,unspecifiedtype*/
#defineIRQ_TYPE_EDGE_RISING0x00000001/*Edgerisingtype*/
#defineIRQ_TYPE_EDGE_FALLING0x00000002/*Edgefallingtype*/
#defineIRQ_TYPE_EDGE_BOTH(IRQ_TYPE_EDGE_FALLING|IRQ_TYPE_EDGE_RISING)
#defineIRQ_TYPE_LEVEL_HIGH0x00000004/*Levelhightype*/
#defineIRQ_TYPE_LEVEL_LOW0x00000008/*Levellowtype*/
#defineIRQ_TYPE_SENSE_MASK0x0000000f/*Maskoftheabove*/
#defineIRQ_TYPE_PROBE0x00000010/*Probinginprogress*/
--没有看到simple类型和per-cpu类型,我估计这2者都是其他architectures上的。
这里把EDGE触发的irq又分成了
上升沿、下降沿和both,level触发的又分成了低电平有效和highactive。
这5个函数取代了原来的__do_IRQ,由do_IRQ直接调用:
handle_irq(irq,desc);
而这个irq_desc[irq].handle_irq又是在哪里设置的呢?
不同的irqchip有不同的设置,现在让
我们看一下ioapic_chip上的irqs的设置:
staticvoidioapic_register_intr(intirq,unsignedlongtrigger)
/*如果不是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;
handle_edge_irq,"
edge"