arm linux内核中ARM中断实现详解.docx
Iinux-2.6.26内核中ARM中断实现详解(1)作者:刘洪涛,华清远见嵌入式学院金牌讲师,ARMATC授权培训讲师。看了一些网络上关于IinUX中断实现的文章,感觉有一些写的非常好,在这里首先感谢他们的无私付出,然后也想再补充自己对些问题的理解,先从函数注册引出问感吧.一、中断注册方法在IinUX内核中用于申请中断的函数是requesiirq<),函数原型在KerneIirqmanage.c中定义:intrequest/rq(unsignedintirq,irq_hand1.er_thand1.er,unsigned1.ongirqf1.ags,constchardevname,void*dev_id)irq是要申请的硬件中断号。hand1.er是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,deid参数将被传递给它。irqf1.ags是中断处理的属性,若设JS了IRQFD1.SAB1.ED(老板本中的SAINTERRUPT,本版ZhOn已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被谢用时屏锻所有中断,慢速处理程序不屏蔽:若设置了IRQF.SHARED(老版本中的SAsHIRQ),则表示多个设备共享中断.若设置了IRQFSAMP1.E_RAMX)M(老版本中的SA-SAMP1.ERANDOM),表示对系统端有奉献,对系统获取随机数有好处,(这几个f1.ag是可以通过或的方式同时使用的)devid在中断共享时会用到,一般设巴为这个设备的设备结构体或者NU1.Jdevname设置中断名称,在Catprocinterrupts中可以看到此名称。requcSJirqO返回0表示成功,返回-INVA1.表示中断号无效或处理函数指针为MJ1.1.,返回-EBUSY表示中断已经被占用且不能共享。关于中断注册的例子,大家可在内核中搜索下request.irq.在编写驶动的过程中,比较容易产生疑惑的地方是:1、中断向星表在什么位置?是如何建立的?2、从中断开始,系统是怎样执行到我自己注册的函数的?3、中I析号是如何确定的?对于硬件上有子中断的中断号如何确定?4、中断共享是怎么回事,deid的作用是?本文以2.6.26内核和S3C2410处理器为例,为大家讲解这几个问题,二、异常向量表的建立在ARMV4及Y4T以后的大局部处理器中,中断向盘表的位置可以有两个位置:一个是0,另一个是OXffffoO00。可以通过CP15协处理器C1.存放器中V位(bi1.13)控制。V和中断向量表的对应关系如F:OXOOOoooOO'0x00000O1.COxffffOOOO'OxffffOO1.Carch/ammproc-ar11920.S,.section”.1.ex1.ini1.”,Hoc,Uexecinstr_arn920_setup:orrr,r,.11.140x21001./bit13=1中断向量表基址为OxFFFFOOOOeRO的值将被付给CP15的C1.在IinUX中.向量:表建立的函数为:init/main.c->starJkerne1.()->trapinit()voidinittrapiniI(void)(unsigned1.ongvectors=CONFIG_VECTORS_BASE:nemcpy(void*)vectors,vectorsstart,_vectors_end-_vectors_start):memcpy(void*)vectors+0x200,_stubs_start,_stubs_end-_stubs_start);)在2.6.26内核中CONF1.GVECTORSBASE最初是在各个平台的配置文件中设定的,如:arch/arm/configss3c2410defconfi中CONFIGVECTORSBASE=OxffffOOOOvectorsend至_vectors_start之间为异常向量表。位于arch/arm/kerne1/entry-armv.S.g1.ob1._VeetOrS_starC_vectors_start:swiSYS_ERRORO:/未定义指令异常:软件中断异常:数据异常:保存:普通中断异常:快速中断异常:bvectorUnd+stubsoffset/复位异常:Idrpc,.1.Cvswi+stubs_offsetbvcctor_pabt+StUbS_OffSetbVeCtOJdabt+StUbS_OffSetbvectorHddrexcp1.n+stubsoffsetbVeCtoJirq+StUbS.offsetbvector_fiq+StUbS.offset.g1.ob1.vectorsend:_vectors_end:stubsend至SIUbSSIar1.之间是异常处理的位置。也位于文件archarmkerne1.entry-a11nv.S,t,vectorUnd、VeCtOJPabt、vectorirq.VeCtor.fiq都在它们中间。stubsoffset值如下:.equStUbS.offset,_vectors_start+0x200-_StUbS_StartStUbS.offset是如何獭定的呢?(引用网络上的一段比较详细的解糅)当汇编器看到B指令后会把要跳转的标卷转化为相对于当前PC的偏移量(+32M)写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成bvectorirq,那么实际执行的时候就无法跳转到搬移后的VeCtorirq处,因为指令码里写的是原来的儡移垃,所以需要把指令码中的偏移量写成搬移后的.我们把搬移前的中断向量表中的irq入口地址记irqPC,它在中断向量表的偏移域就是irqPCrec1.orsstart,vectorirq在StUbS中的偏移量是VeCtorJrq-StUbS_start,这两个偏移量在搬移前后是不变的。搬移后YCCtOrS_Start在OXffffOOOO处,而StUbSStart在OXffff在00处,所以搬移后的VeetoJirq相对于中断向量中的中断入口地址的偏移坑就是,200+v。Ctorirq在StUbS中的偏移量再减去中断入口在向量表中的偏移量,即200+vectorirqstubss1.ar1.-irqPCvectorsstart=(vector_irq-irq_PC)+VeCtorS_start+200-StUbS_Start,对于括号内的值实际上就是中断向量表中写的VeCtorirq,她去irq_PC是由汇编器完成的,而后面的VeCtOrS_Start+200-StUbSstart就应该是stubsoffset,实际上在entry-arnv.S中也是这样定义的.三、中断处理过程这一节将以S3C2410为例,描述IinUX-2.6.26内核中,从中断开始,中断是如何步步执行到我们注册函数的。3. 1中断向量衰archarmkerne1.entry-armv.S_VeCtorS_start:swiSYS_ERRORObVeCtoJUnd+stubs_offsot1.<1.rpc,.1.Cvswi+stubsoffsetbvector_pabt+StUbS.offsetbvector_dabt+StUbS.offsetbVeCtOJaddreXCPtn+stubs_offsetbVeCtOjirq+stubs_offsetbvector_fiq+StUbS.offset.g1.ob1._vectors-endvectorsend:中断发生后,跳转到bvector_irq+st泌&oet的位置执行。注意现在的向:止表的初始位置是OXfffTOO00。3.2 中断跳转的入I位置archarmkerne1.entry-armv.S.g1.ob1._StUbS_startstubsstart:*Interruptdispatcher*/.vector.stubirq,IRQ_MODE,4'IRQ-MODEinc1.udenptrace.h中定义t0x12.1.ong_irq_usr0(USR_26/USR.32)1.ongirqiriva1.id91(FIQ26/FIQ32)1.ongirqiriva1.id92(IRQ26/IRQ32)1.ong_irq_svc3(SVC_267SVC_32).1.ong_irq_inva1.id4.1.ong_irq_inva1.id51.ong_irq_inva1.id61.ong_irq_inva1.id97-1.ong_irq_inva1.id8.1.ong_irq_inva1.id09.1.ongirqinva1.id®a1.ong_irq_inva1.ideb.1.ong.irq_inva1.idc.1.ong一irq_inva1.idd.1.ongirqinva1.id®e1.ong.irq_inva1.idf上面代码中vectorstub宏的定义为:.macrovectorstub,mime,mode,Correction=O.a1.ign5vector-name:.ifcorrectionsubIr,Ir,#correction.endifSaver,1.r_<exception>(parentPC)andspsr_<excoption>蚀(parentCPS1.Ostmiasp.(r,Ir)saver.Irmrs1r,spsrsirIr,sp,即savespsrPrepareforSVC32mode.IRQsremaindisab1.ed.mrsr,cpsreorr,r,#(mode*SVjMODE)msrSPSJCXSf,r为后面进入SVC模式做准备®thebranchtab1.emustimmediate1.yfo1.1.owthiscodeandIr,Ir,的XOf进入中断前的mod。的后4位defineVSRMODE0x00000010鲫defineFIe1.MoDE0x00000011鲍defineIRQMODE0x00000012触defin。SYJM(2E0x00000013例defineABTMODE0x00000017购defineUM1.MODE0x000000Ib鲫defineSYSTEMMODE0x0000001fmovr,sp1.<rIr,pc,Ir,Is1.#2。如果进入中断前是usr,则取出PC+4*0的内容,即irq-usr©如果进入中断前是SVc,则取出PC+4*3的内容,即_irq_svcmovspc,Ir当指令的目标存放器是PC,I1.M令以S结束,则它会把&SPSr的值恢的给CPSrbranchtohand1.erinSVCmode.endm.g1.ob1._stubs_start_StUbS_start:*Interruptdispatcher*/vector_stubirq,IRQMODE,4.1.ong_irq_usr0(USR_26/USf1.32).1.ongirqinva1.id1(FIQ26/I'1Q32).1.ong_irq_inva1.id®2(IRQ_26/1RQ_32).1.ong_irq_svc3(SVC_267SVC_32)用“irq,IR(1.MODE,4"代替宏Veetor_stub中的“name,node,correction".找到了我们中断处理的入口位置为VeCtor.irq(宏里面的VeCtOJnaae)。从上面代码中的注择可以看出,根据进入中断前的工作模式不同,程序下一步将跳转到irqusr、或_irq_SVC等位置。我们先选择_irq_uSr作为下一步跟踪的目标。3.3 _irq_usr的实现archar三kerne1.entry-a11nv.S_irq_usr:usr_entry后面有解释.kuser_cmpxchg_CheCkIfifdefCONFIG_TRACE_IRQFI.AGS.b1.trace_hardirqs_off#endif.get_thread_infotsk©获取当前进程的进程描述符中的成员变量threadinfo的地址,并将该地址保存到存放井tsk等于r9(在entry-header.S中定义)#ifdefCONFIG-PREEMPt如果定义了抢占,增加抢占数值Idr18,1.sk,#TIPREEMPTgetpreemptcountaddr71r8,#10incrementitsirr7,1.sk,#T1.PREEMPTUendifirjhand1.er中新处理,我们最关心的地方,3.4节有实现过程.#ifdefCONF1.G.PREEMPTIdrr,tsk,nT1.PREEMPTstrr8,tsk,#TI_PREEMPTIeqrO,r7strner,r.-rJtendif#ifdefCONEIGTRACEIRQ1.GSb1.tracehardirqson#endifmovwhy,#0bret_to_user6中断处理完成,返回中断产生的位置,3.7节有实现过程上面代码中的U8jentry是一个宏,主要实现了将USr模式下的存放器、中断返回地址保存到堆栈中。.macrour-entrysubsp,sp,#S_FRAME_SIZE«S_FRAME_SIZE的值在archarmkcrne1asm-offsets,c.中定义DEFINE(S_FRAME_SIZE,sizeof(structpt_regs>);实际上等于72stmibsp,(r1.-r1.21.<1.miar,(r1.-r3addr,sp,#5PCehereforinter1.ockavoidance1.xa3."R"A,”“.”movr4,5-10strr1.,sp®savethe"rea1."rcopiedfromtheexceptionstack©Wearenow-eadytofi1.1.intheremainingb1.anksonthestack:®r2_Ir<exception>,a1.readyfixedupforcorrectreturnrestartr3-spsr-<exception>ar4-orig_rO(seept_regsdefinitioninptrace,h)A1.so,separate1.ysavesp_usrand1r_usrStmiar,(r2-r4)s1.mdbr,(sp,Ir),®Enab1.ethea1.ignmenttrapwhi1.einkerne1.modea1.ignmenttraprC1.earFPtomarkthefirststackframeZerO_fp.endmJt面的这段代码主要在填充结构体pt_regs,这里提到的StrUCtptregs,在inc1.ude/asm/ptrace.h中定义.此时sp指向structPtJegSostructpt_i'egs1.onguregs18;;HdefineARM/psrUrCgSU6PdefineAMPCuregs15KdofineARMIruregs14HdefineARMspUregS(HdefineARMipuregs12IfdefincARMfpuregs11HdefineARMr1.uregs10HdefineARMr9UregSHdefineARMr8regs8PdefineARMr7UregS7tidefineAR!ruregs6defineARMr5UregSdefineARMHuregs4PdefineARA1.r3uregs3tidefineARMr2uregs2tidefineARMr1.Uregs1.HdefineARi1.roUregS0tidefineARMORIG_iOuregs173.4 irqjand1.er的实现过程,archarBkrn1.entry-ar三v.S.macroirq-hand1.erget_irqnr_preamb1er5,Ir&在inc1udeasmarch-s3c2410/entry-nacro.s中定义/宏getirqnrPreambIe为空操作,什么都不做1:get_irqnr_and_baser,r6,r5,Ir判断中断号,通过RO返回,3.5节有实现过程movner1.,SP9routineca1.1.edwithr=irqnumber,r1.=structptregs*adrneIr,Ibbneasm_do_IRQjtt入中新处理.er1.113.5get_irqnJandj)ase中断号判断过程,inc1udeasmarc-s3c2410en1.ry-macro.s.macrogetirqnr_and_base,irqnr,irqstat,base,impmovbase,#S3C24XXVIRQtrytheinterruptoffsetregister,sinceitisthereIdrirqstat,base,WNTPNDteqirqstat,#0beq1002fIdrirqnr,base,用NTOFFSETj蛆过判断INTOFFSET存放器得到中断位置movtnp,#11stirqsta1.,1.mp,Is1.irqnrbne1001.fthenumberspecifiedisnotava1.idirq,sotryandworkitoutforourse1.vesmovirqnr,#0颔starthereworkoutwhichirq(ifany)wegotmovstmp,irqstat11.s1.fi1.6addeqirqnr,irqnr,#1611oveqirqs1.at,irqsta1.,1.sr#16tstirqstat,SOxffaddeqirqnr,irqnr,48InOVeqirqstat,irqstat,1.sr#81stirqstat,#0Xfaddeqirqnr,irqnr,#4moveqirqstat,irqstat,IsrMtstirqstat,#0x3addeqirqnr,irqnr,42moveqirqstat,irqstat,1.sr#2tstirqstat,#0x1addeqirqnr,irqnr,#19"WehaveIheva1.ue1001:addsirqnr,irqnr,#1R(1.EINTO”加上中新号的基准数值,得到最终的中断号,注意:此时没有考虑子中断的具体情况,(子中断的问题后面会有讲解).1鼠1.EINrO在inc1.udeas三arch-s3c2410irqs.h中定义.从这里可以看出,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起存放器中的中断号是不等的.1002:的exithere,7.f1.agunsetifIRQ.er1.m3.6asm_do_IRQ实现过程,arch/arm/kerne1./irq.casm!inkagevoid_exceptionas-doIRQ(UnSignedintirq,structPtJegS*regs)("structpt_regs*o1.d_regs=set_irq_regs(regs);structirq_desc1Mesc=irq_desc+irq;根据中断号找到对应的irq_desc*Somehardwaregivesrandom1.ywronginterrupts.Rather*thancrashing,dosomethingsensib1.e.»/if(irq>=NRIRQS)desc=&bad_irq_desc;irq_enter();没做什么特别的工作,可以跳过不看desc_hand1.e_ixrq(irq,desc);/根据中断号和desc进入中断处理*AT91specificworkaround/irq_finish(irq);irq_exit();setirqregs(o1.dregs);staticin1.inevoiddesc_hand1.e_irq(unsignedintirq,structirqdesc*desc)(desc->hand1.e_irq(irq,desc);中断处理)上述asm1.inkag。void.exceptionasm_doIRQ(unsignedintirq,structptregs*regs)f用了HSmIinkage标识。那么这个标识的含义如何理解呢?该符号定义在kerne1.inc1.udeIinUX/1.inkage.h中,如F所示:inc1.ude<asm1.inkage.h>各个具体处理器在此文件中定义asm1.inkageJtifdef_cp1.uspIusdefineCP1.1.ASM1.INKAGEextern"C#e1.seWefineCPP_ASH!1.INKAGE-endifJtifndefasm1.inkage/如果以前没有定义asm1.inkageSdcfineasm1.inkagcCPP_ASM1.INKAGESendif对于ARM处理器的<asmIinkageh,没有定义asm1.inkage,所以没有意义(不要以为参数是从堆栈传递的,对于平台来说还是符合ATPCS过程调用标准,通过存放器传递的)。但对于X86处理器的asmIinkagah中是这样定义的:Jtdefineasm1.inkageCPPSM1.INKAGEattribute(regparm(O)表示函数的参数传递是通过堆栈完成的。3.7描述3.3节中的ret_to_user中断返IS1.过程,archarmkerne1./entry-common.SENTRY(ret_to_user)rot_s1.ow_sysca1.1:disab1.eirq®disab1.einterruptsIdrr1.,tsk,#TI_F1.GStstr1.,电TI叱WORKJiASKbneWOrk.pendingnoworkpending:*performarchitecturespecificactionsbeforeuserreturn/arch_ret_to_userr1.,Ir&s1.ow_restore_user_regsIdrr1.,sp,eS_PSRgetca1.1ingcpsrIdrIr,sp,#S_PC!Ogetpcmsrspsrcxsf,r1.saveinSPSrSVCIdmdbsp,r-1.r'getca1.1.ingr-Irmovr.raddsp,sp,#S_FRAME_SIZE-S_PCmovspc,Irreturn&movespsr-svcintocpsr第三章主要跟踪了从中断发生到调用到对应中断号的desc->hand1.ejrq(irq,desc)中断函数的过程。后面的章节还会继续讲解后面的内容。四、中断处理模型要想弄清楚desc->hand1.eirq(irq,desc)和我们注册的中断有什么关联,就要了解中断处理模型了。4.1 中断处理模型结构中断处理模型如下列图所示,NRIRQS-ImII1MQKtion其中NRIRQS表示酸大的中断号,在inc1.ude/asm/arch/irq.h中定义。irq_desc是一个指向irq_deSJt结构的数组,irq_desc_t结构是各个设备中断效劳例程的描述符。Irqdescj结构体中的成员action指向该中断号对应的irqacIion结构体链表。IrqaCtiOn结构体定义在inc1.ude/IinuxZinterrupt,h中,如下:Iruc1.irqacIion(irq_hand1.er_thand1.er:中断处理函数,注册时提供unsigned1.ongf1.ags;/中断标志,注册时提供cpumasktmask;/中断拖码constchar*name;中断名称void*devjd;设备id.本文后面局部介绍中断共享时会详细说明这个参数的作用structirqaction+next;如果有中断共享,则继续执行,intirq;中断号,注册时提供structproc_dir_entry*dir;/指向IRQn相关的procirqn目录的描述符):在注册中断号为irq的中断效劳程序时,系统会根据注册参数封装相应的irqaction结构体。并把断号为irq的irqaction结构体写入irq-descirq->action.这样就把设备的中断请求号与该设备的中断效劳例程ir<iucii<,n联系在一就了。样当CPU接收到中断请求后,就可以根据中断号通过irqesc口找到该设备的中断效劳程序。4.2 中断共享的处理模型共享中断的不同设符的iqraction结构体都会添加进该中断号对应的irq_desc结构体的action成员所指向的irquc1.ion链表内。当内核发生中断时,它会依次调用该链表内所有的hand1.er函数。因此,若第动程序需要使用共享中断机制,其中断处理函数必须有能力识别是否是自己的硬件产生了中断,通常是通过读取该硬件设备提供的中断f1.ag标志位进行判断。也就是说不是任何设备都可以做为中断共享源的,它必须能够通过的它的中断f1.ag判断出是否发生J'中断。中断共享的注册方法是:intrequestirq(unsigne<1.intirq,ir<hand1.erthand1.er,IRQF_SHARED.constchar*devname,void*devid)很多权威资料中都提到,中断共享注册时的注册函数中的devid参数是必不可少的,并且devid的值必须唯那么这里提供唯的dev_id值的究竟是做什么用的?根据我们前面中断模量的知识,可以看出发牛.中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所仃该中断线上注册的中断处理函数(即irqaction->hand1.er函数).因此irqaction->hand1.er函数有责任识别出是否是自己的俊件设备产生了中断,然后再执行该中断处理函数,通常是通过读取该硬件设备提供的中断f1.ag标志位进行判断。那既然kerne1.循环执行该中断线上注册的所有irqaction->hand1.er函数,把识别冗竟是哪个硬件设备产生了中断这件事交给中断处理函数本身去做,那rcqucst_irq的devid参数究竟是做什么用的?很多资料中都建议将设备结构指针作为devid参数。在中断到来时,迅速地根据硬件存放落中的信息比照传入的devid参数判断是否是本设备的中断,若不是,应迅速返回。这样的说法没有问题,也是我们编程时都遵循的方法。但事实上并不能够说明为什么中断共享必须要设置dvid.下面解择一下dev_id参数为什么必须的,而且是必须唯一的.当调用freeirq注销中断处理函数时(通常卸载驱动时其中断处理函数也会被注销掉),因为dev_id是唯一的,所以可以通过它来判断从共享中断线上的多个中断处理程序中出除指定的一个。如果没有这个参数,那么kerne1.不可能知道给定的中断线上到底要删除哪一个处理程序。注销函数定义在Kerne1.irqmanage.c中定义:voidfreeirq(unsignedintirq,void*devid)五、S3C2410子中断的注册的实现5.1S3C2410子中断注册问题的提出参看3.5节中判断中断号的方法,可以看到只是通过S3C2410中断控制法中的INToFFSET存放罂来判断的。时下INTPND中的EINT47,EINT823、INTJJART0、IVrAi)C等带有子中断的向是,INToFFSET无法判断出具体的中断号。平4留给我们的注册方法如下:在inc1.udeasmarchirqs.h中有类似如下定义:*interruptsgeneratedfromtheexterna1.interruptssources*/defineIRQE1NT4S3C241OIRQ(32)*48*/XdefineIRC1.E1.NT5S3C2410RQ(33)SdefinoIR(1.E1.NT6S3C2410_IRQ(34)SdefineIR(1.E1.NT7S3C2410IRQ(35)UdefineIRQEINT8S3C2410IRQ(36)WefineIRC1.EINT9S3C2410IRQ(37)MefineIRQ,EINT10S3C2410JRQ(38)SdefineIR(1.EINTI1.S3C2410IRQ(39)#defineIRQEINT12S3C2410IRQ(40)-defineIR(1.E1.NT13S3C24IOJRQ(41)UdefineIRQEINT14S3C2410IRQ(42)WefineIRe1.EINT15S3C2410JRQ(43)SdofincIRQE1T16S3C2410.IRQ(44)SdefineIR(1.EINT17S3C2410.IRQ(45)WefineIRQEINTI8S3C2410IRQ(46)HdefineIR(1.E1.NT19S3C2410IRQ(47)SdcfincIR(1.EINT20S3C2410.IRQ(48)/644defineIRQ_EINT21S3C2410IRQ(49)UdefineIRQE1NT22S3C2410IRQ(SO)WefineIR(1.EINT23S3C2410.IRQ(51)可以看到平台为每种子中断都定义了中断号,如果你想实现E1.M1.o的中断注册,直接按照IRQ.EINT10这个中断号注册都可以了。那么平台代码是如何实现这局部中断注册的呢?5.2S3C2410子中断注册问题的解决*archa11np1.at-s3c24xxirq.c*/voidinits3c24xx_init_irq(void)SCIjrq_Chained.hand1.eM1.RQ_E1.NT4t7,s3c_irq_demuxextint4t7):setirqchainedhandIer(IRQEINT8t23,s3cirqdemuxextint8);set_irq_chained_handIer(IRQJART0,s3c_irq_demux_uartO);sct_irq_chained_hand1er(1RQIART1,s3c.irq_demux_uart1);SeJirq_Chained_hand1.er(IRQjART2,s3c_irq_dcmux_uart2);setirqchainedhandIer(IRQADCPARENT,s3cirqdemuxtide);)平台在初始化时会调用到s3c24xxiniIirq,在此函数中实现了对E1.NT47、EINT8.23JNT,UART0、INT_ADC等中断的注册。卜.面看看这些带有子中断的中断号对应的处理函数的内容。以IRQEINT4t7为例,其它情况类似。*archarmp1.at-s3c24xxirq.c*/s3c_irq_demux_extint4t7(unsignedintirq,structirq_desc*desc)(unsigned1.ongeintpnd=_rawread1.(S3C24XX_EINTPEND);unsigned1.ongeintmsk=rawread1.(S3C24XX-EINTMASK);eintpnd&='eintmsk;eintpnd&=Oxff;*on1.y1.owerirqs/"ein1.pnd中可以有多个位同时置1,这一点和in1.pnd的只能有1个位置1是不一样的*/whi1.e(eintpnd)(/循环执行所有置位的子中断irq=_ffs(eintpnd);/7算出第个不为0的位,类似armv5后的CIZ前导。的作用eintpnd&='(KCrq);/去除相应的位irq+=(IRQEINT4-4);算出对应的中断号desc_hand1e_irq(irq,irq_desc+irq);执行对应子中断的注册函数)从上面的函数可以看出子中断是如何注册及被调用到的.有人可能会问为何不在inc1udeasmarchs3c2410/enIry-nacro.s文件中getirqr11andbase函数判断中断号时,直接算出对应的子中断号,就可以直接找到了中断处理了呢?原因是:get/rqnjandbase是平台给系统提供的函数,对于多个子中断同时置位的情况无法通过个值返回(因为中断中,如eintpnd是可以多个位同时置位的)。而intpnd则没有这个问题。