ARM Linux中断源码分析(2)——中断处理流程.docx_第1页
ARM Linux中断源码分析(2)——中断处理流程.docx_第2页
ARM Linux中断源码分析(2)——中断处理流程.docx_第3页
ARM Linux中断源码分析(2)——中断处理流程.docx_第4页
ARM Linux中断源码分析(2)——中断处理流程.docx_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

ARM Linux中断源码分析(2)中断处理流程ARM支持7类异常中断,所以中断向量表设8个条目,每个条目4字节,共32字节。异常名称中断向量异常中断模式优先级复位0x0特权模式1未定义的指令0x4未定义指令中止模式6软件中断0x8特权模式6指令预取中止0x0c中止模式5数据访问中止0x10中止模式2保留0x14外部中断请求IRQ0x18IRQ模式4快速中断请求FIQ0x1cFIQ模式3回顾第一节所讲的内容,当一个异常或中断发生时,处理器会将PC设置为特定地址,从而跳转到已经初始化好的异常向量表。因此,要理清中断处理流程,先从异常向量表开始。对于ARM Linux而言,异常向量表和异常处理程序都存在arch/arm/kernel/entry_armv.S汇编文件中。vector异常向量表点击(此处)折叠或打开1. .globl_vectors_start2. _vectors_start:3. swiSYS_ERROR04. bvector_und+stubs_offset5. ldrpc,.LCvswi+stubs_offset6. bvector_pabt+stubs_offset7. bvector_dabt+stubs_offset8. bvector_addrexcptn+stubs_offset9. bvector_irq+stubs_offset 中断入口,vector_irq10. bvector_fiq+stubs_offset11.12. .globl_vectors_end13. _vectors_end:vector_irq+stubs_offset为中断的入口点,此处之所以要加上stubs_offset,是为了实现位置无关编程。首先分析一下stubs_offset(宏)是如何计算的:.equstubs_offset, _vectors_start + 0x200 - _stubs_start在第3节中已经提到,内核启动时会将异常向量表拷贝到 0xFFFF_0000,将异常向量处理程序的 stub 拷贝到 0xFFFF_0200。图5-1描述了异常向量表和异常处理程序搬移前后的内存布局。图5-1异常向量表和异常处理程序搬移前后对比当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(32M)写入指令码。由于内核启动时中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。设搬移后的偏移量为offset,如图5-1所示,offset = L1+L2= 0x200 - (irq_PC_X - _vectors_start_X) + (vector_irq_X - _stubs_start_X)= 0x200 - (irq_PC - _vectors_start) + (vector_irq - _stubs_start)= 0x200 - irq_PC + _vectors_start + vector_irq - _stubs_start= vector_irq + (_vectors_start + 0x200 - _stubs_start) - irq_PC令stubs_offset = _vectors_start + 0x200 - _stubs_start则offset = vector_irq + stubs_offset - irq_PC,所以中断入口点为“bvector_irq + stubs_offset”,其中减去irq_PC是由汇编器在编译时完成的。vector_irq处理函数在分析vector_irq处理函数之前,先了解一下当一个异常或中断导致处理器模式改变时,ARM处理器内核的处理流程如下图所示: 中断刚发生时,处理器处于irq模式。在_stubs_start和_stubs_end之间找到vector_irq处理函数的定义vector_stub irq, IRQ_MODE, 4,其中vector_stub是一个宏(在arch/arm/kernel/entry_armv.S中定义),为了分析更直观,我们将vector_stub宏展开如下:1. /*2. *Interrupt dispatcher3. */4. vector_irq:5. .if46. sublr,lr,#4在中断发生时,lr指向最后执行的指令地址加上8。只有在当前指令执行完毕后,才进入中断处理,所以返回地址应指向下一条指令,即(lr-4)处。7. .endif8.9. 10. Save r0,lr_(parent PC)andspsr_11. (parent CPSR)12. 13. stmiasp,r0,lr 保存r0,lr到irq模式下的栈中14. mrslr,spsr15. strlr,sp,#8保存spsr到irq模式下的栈中16.17. 18. PrepareforSVC32 mode.IRQs remain disabled.19. 20. mrsr0,cpsr21. eorr0,r0,#(IRQ_MODE SVC_MODE)设置成SVC模式,但未切换22. msrspsr_cxsf,r0 保存到spsr_irq中23.24. 25. the branch table must immediately followthiscode26. 27. andlr,lr,#0x0f lr存储着上一个处理器模式的cpsr值,lr=lr & 0x0f取出用于判断发生中断前是用户态还是核心态的信息,该值用于下面跳转表的索引。28. movr0,sp 将irq模式下的sp保存到r0,作为参数传递给即将调用的_irq_usr或_irq_svc29. ldrlr,pc,lr,lsl#2pc指向当前执行指令地址加8,即跳转表的基址。lr作为索引,由于是4字节对齐,所以lr=lr2.30. movspc,lr branch to handlerinSVC mode31. 当mov指令后加“s”且目标寄存器为pc时,当前模式下的spsr会被复制到cpsr,从而完成模式切换(从irq模式切换到svc模式)并且跳转到pc指向的指令继续执行32. ENDPROC(vector_irq)33.34. .long_irq_usr 0(USR_26 / USR_32)35. .long_irq_invalid 1(FIQ_26 / FIQ_32)36. .long_irq_invalid 2(IRQ_26 / IRQ_32)37. .long_irq_svc 3(SVC_26 / SVC_32)38. .long_irq_invalid 439. .long_irq_invalid 540. .long_irq_invalid 641. .long_irq_invalid 742. .long_irq_invalid 843. .long_irq_invalid 944. .long_irq_invalid a45. .long_irq_invalid b46. .long_irq_invalid c47. .long_irq_invalid d48. .long_irq_invalid e49. .long_irq_invalid f_irq_usr如果发生中断前处于用户态则进入_irq_usr,其定义如下(arch/arm/kernel/entry_armv.S):1. .align52. _irq_usr:3. usr_entry 保存中断上下文,稍后分析4. kuser_cmpxchg_check5. #ifdef CONFIG_TRACE_IRQFLAGS6. bltrace_hardirqs_off7. #endif8. get_thread_info tsk 获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk(r9)(在entry-header.S中定义)9. #ifdef CONFIG_PREEMPT 如果定义了抢占,增加抢占数值10. ldrr8,tsk,#TI_PREEMPT 获取preempt计数器值11. addr7,r8,#1 preempt加1,标识禁止抢占12. strr7,tsk,#TI_PREEMPT将加1后的结果写入进程内核栈的变量中13. #endif14. irq_handler 调用中断处理程序,稍后分析15. #ifdef CONFIG_PREEMPT16. ldrr0,tsk,#TI_PREEMPT获取preempt计数器值17. strr8,tsk,#TI_PREEMPT将preempt恢复到中断前的值18. teqr0,r7 比较中断前后preempt是否相等19. strner0,r0,-r0如果不等,则产生异常(向地址0写入数据)?20. #endif21. #ifdef CONFIG_TRACE_IRQFLAGS22. bltrace_hardirqs_on23. #endif24. movwhy,#0 r8=025. bret_to_user 中断处理完成,恢复中断上下文并返回中断产生的位置,稍后分析26. UNWIND(.fnend)27. ENDPROC(_irq_usr)宏定义usr_entry(保护上下文到栈)上面代码中的usr_entry是一个宏定义,主要用于保护上下文到栈中:1. .macrousr_entry2. UNWIND(.fnstart)3. UNWIND(.cantunwind) dont unwind the user space4. subsp,sp,#S_FRAME_SIZE ATPCS中,堆栈被定义为递减式满堆栈,所以首先让sp向下移动#S_FRAME_SIZE(pt_regs结构体size),准备向栈中存放数据。此处的sp是svc模式下的栈指针。5. stmibsp,r1-r126.7. ldmiar0,r1-r38. addr0,sp,#S_PC hereforinterlock avoidance9. movr4,#-110.11. strr1,sp save therealr0 copied12. from the exception stack13.14. 15. We are now ready to fillinthe remaining blanks on the stack:16. 17. r2-lr_,already fixed upforcorrect return/restart18. r3-spsr_19. r4-orig_r0(see pt_regs definitioninptrace.h)20. 21. Also,separately save sp_usrandlr_usr22. 23. stmiar0,r2-r424. stmdbr0,sp,lr 将user模式下的sp和lr保存到svc模式的栈中25.26. 27. Enable the alignment trapwhileinkernel mode28. 29. alignment_trap r030.31. 32. Clear FP to mark the first stack frame33. 34. zero_fp35. .endm上面的这段代码主要是在填充结构体pt_regs ,在include/asm/ptrace.h中定义:1. structpt_regs 2. long uregs18;3. ;4.5. #define ARM_cpsruregs166. #define ARM_pcuregs157. #define ARM_lruregs148. #define ARM_spuregs139. #define ARM_ipuregs1210. #define ARM_fpuregs1111. #define ARM_r10uregs1012. #define ARM_r9uregs913. #define ARM_r8uregs814. #define ARM_r7uregs715. #define ARM_r6uregs616. #define ARM_r5uregs517. #define ARM_r4uregs418. #define ARM_r3uregs319. #define ARM_r2uregs220. #define ARM_r1uregs121. #define ARM_r0uregs022. #define ARM_ORIG_r0uregs17usr_entry宏填充pt_regs结构体的过程如图5-2所示,先将r1r12保存到ARM_r1ARM_ip(绿色部分),然后将产生中断时的r0寄存器内容保存到ARM_r0(蓝色部分),接下来将产生中断时的下一条指令地址lr_irq、spsr_irq和r4保存到ARM_pc、ARM_cpsr和ARM_ORIG_r0(红色部分),最后将用户模式下的sp和lr保存到ARM_sp和ARM_lr中。图5-2 usr_entry宏填充pt_regs结构体_irq_svc如果发生中断前处于核心态则进入_irq_svc,其定义如下(arch/arm/kernel/entry_armv.S):1. .align52. _irq_svc:3. svc_entry 保存中断上下文4.5. #ifdef CONFIG_TRACE_IRQFLAGS6. bltrace_hardirqs_off7. #endif8. #ifdef CONFIG_PREEMPT9. get_thread_info tsk10. ldrr8,tsk,#TI_PREEMPT 获取preempt计数器值11. addr7,r8,#1 preempt加1,标识禁止抢占12. strr7,tsk,#TI_PREEMPT将加1后的结果写入进程内核栈的变量中13. #endif14.15. irq_handler 调用中断处理程序,稍后分析16. #ifdef CONFIG_PREEMPT17. strr8,tsk,#TI_PREEMPT 恢复中断前的preempt计数器18. ldrr0,tsk,#TI_FLAGS 获取flags19. teqr8,#0 判断preempt是否等于020. movner0,#0 如果preempt不等于0,r0=021. tstr0,#_TIF_NEED_RESCHED 将r0与#_TIF_NEED_RESCHED做“与操作”22. blnesvc_preempt 如果不等于0,说明发生内核抢占,需要重新调度。23. #endif24.25. ldrr0,sp,#S_PSR irqs are already disabled26. msrspsr_cxsf,r027. #ifdef CONFIG_TRACE_IRQFLAGS28. tstr0,#PSR_I_BIT29. bleqtrace_hardirqs_on30. #endif31. svc_exit r4 恢复中断上下文,稍后分析。32. UNWIND(.fnend)33. ENDPROC(_irq_svc)宏定义svc_entry(保护中断上下文到栈)其中svc_entry是一个宏定义,主要用于保护中断上下文到栈中。svc_entry主要是在当前堆栈上分配一个pt_regs结构,把r0-r15以及cpsr等保存到这个结构中,在进入irq_handler时,sp指向pt_regs底端:1. .macrosvc_entry,stack_hole=02. UNWIND(.fnstart)3. UNWIND(.save r0-pc)4. subsp,sp,#(S_FRAME_SIZE+stack_hole)5. SPFIX(tstsp,#4)6. SPFIX(bicnesp,sp,#4)7. stmibsp,r1-r128.9. ldmiar0,r1-r310. addr5,sp,#S_SP hereforinterlock avoidance11. movr4,#-112. addr0,sp,#(S_FRAME_SIZE+stack_hole)13. SPFIX(addner0,r0,#4)14. strr1,sp save therealr0 copied15. from the exception stack16.17. movr1,lr18.19. 20. We are now ready to fillinthe remaining blanks on the stack:21. 22. r0-sp_svc23. r1-lr_svc24. r2-lr_,already fixed upforcorrect return/restart25. r3-spsr_26. r4-orig_r0(see pt_regs definitioninptrace.h)27. 28. stmiar5,r0-r429. .endmsvc_entry宏填充pt_regs结构体的过程如图5-2所示,先将r1r12保存到ARM_r1ARM_ip(绿色部分),然后将产生中断时的r0寄存器内容保存到ARM_r0(蓝色部分),由于是在svc模式下产生的中断,所以最后将sp_svc、lr_svc、lr_irq、spsr_irq和r4保存到ARM_sp、ARM_lr、ARM_pc、ARM_cpsr和ARM_ORIG_r0(红色部分)。图5-3 svc_entry宏填充pt_regs结构体上述的中断上下文保存过程共涉及了3种栈指针,分别是:用户空间栈指针sp_usr,内核空间栈指针sp_svc和irq模式下的栈栈指针sp_irq。sp_usr指向在setup_arg_pages函数中创建的用户空间栈。sp_svc指向在alloc_thread_info函数中创建的内核空间栈。sp_irq在cpu_init函数中被赋值,指向全局变量stacks.irq0。附录1,arm体系下pt_regs结构structpt_regs long uregs18;uregs0 - uregs17分别对应,r0 - r15,cpsr,ORIG_r0附录1,irq中断时堆栈的变化-spsr-lr ,中断返回地址,修正后的-r0- -进入irq_svc之前,sp的值,也是r0的值pt_regs- flags6. tstr1,#_TIF_WORK_MASK 判断是否有待处理的work7. bnework_pending 如果有,则进入work_pending进一步处理,主要是完成用户进程抢占相关处理。8. no_work_pending:如果没有work待处理,则准备恢复中断现场,返回用户空间。9. /*perform architecture specific actions before user return*/10. arch_ret_to_user r1,lr 调用体系结构相关的代码11.12. restore_user_regs fast=0,offset=0 调用restore_user_regs13. ENDPROC(ret_to_user)14.15. 以下是恢复中断现场寄存器的宏,就是将发生中断时保存在内核空间堆栈上的寄存器还原,可以对照图5-2所示的内核空间堆栈保存的内容来理解下面代码:16. .macrorestore_user_regs,fast=0,offset=017. ldrr1,sp,#offset+S_PSR 从内核栈中获取发生中断时的cpsr值18. ldrlr,sp,#offset+S_PC! 从内核栈中获取发生中断时的下一条指令地址19. msrspsr_cxsf,r1 将r1保存到spsr_svc20. #ifdefined(CONFIG_CPU_32v6K)21. clrex clear the exclusive monitor22. #elif defined(CONFIG_CPU_V6)23. strexr1,r2,sp clear the exclusive monitor24. #endif25. .iffast26. ldmdbsp,r1-lr get calling r1-lr27. .else28. ldmdbsp,r0-lr 存在,所以将内核栈保存的内容恢复到用户空间的r0lr寄存器29. .endif30. addsp,sp,#S_FRAME_SIZE-S_PC31. movspc,lr将发生中断时的下一条指令地址存入pc,从而返回中断点继续执行,并且将发生中断时的cpsr内容恢复到cpsr寄存器中(开启中断)。32. .endmsvc_exit如果中断产生于内核空间,则调用svc_exit来恢复中断现场:1. arch/arm/kernel/ entry-header.S2. .macrosvc_exit,rpsr3. msrspsr_cxsf,rpsr4. #ifdefined(CONFIG_CPU_32v6K)5. clrex clear the exclusive monitor6. ldmiasp,r0-pc load r0-pc,cpsr7. #elif defined(CONFIG_CPU_V6)8. ldrr0,sp9. strexr1,r2,sp clear the exclusive monitor10. ldmibsp,r1-pc load r1-pc,cpsr11. #else12. ldmiasp,r0-pc 返回内核空间时,恢复中断现场比较简单,就是将r0-pc以及cpsr恢复即可,同时中断也被开启。13. #endif14. .endmasm_do_IRQ函数ok,分析完所有与中断相关的汇编语言代码后,下面开始分析C语言代码:在arch/arm/kernel/irq.c文件中找到asm_do_IRQ函数定义:1. asmlinkage void _exception asm_do_IRQ(unsignedintirq,structpt_regs*regs)2. 3. /*保存新的寄存器集合指针到全局cpu变量,方便后续处理程序访问寄存器集合。*/4. structpt_regs*old_regs=set_irq_regs(regs);5.6. irq_enter();7.8. /*9. *Some hardware gives randomly wrong interrupts.Rather10. *than crashing,do something sensible.11. */12. if(unlikely(irq=NR_IRQS) /判断中断号13. if(printk_ratelimit()14. printk(KERN_WARNINGBad IRQ%un,irq);15. ack_bad_irq(irq);16. else17. generic_handle_irq(irq);/调用中断处理函数18. 19.20. /*AT91 specific workaround*/21. irq_finish(irq);22.23. irq_exit();24. set_irq_regs(old_regs);25. asm_do_IRQ是中断处理的C入口函数,主要负责调用request_irq注册的中断处理函数,其流程如图5-4所示:图5-4 asm_do_IRQ流程1、old_regs=set_irq_regs(regs)其中,set_irq_regs将指向寄存器结构体的指针保存在一个全局的CPU变量中,后续的程序可以通过该变量访问寄存器结构体。所以在进入中断处理前,先将全局CPU变量中保存的旧指针保留下来,等到中断处理结束后再将其恢复。2、irq_enterirq_enter负责更新一些统计量:1. 2. void irq_enter(void)3. 4. intcpu=smp_processor_id();5.6. rcu_irq_enter();7. if(idle_cpu(cpu)&!in_interrupt()8. _irq_enter();9. tick_check_idle(cpu);10. else11. _irq_enter();12. 如果系统开启动态时钟特性且很长时间没有产生时钟中断,则调用tick_check_idle更新全局变量jiffies(关于动态时钟特性,在后续的总结中再进行分析)。宏_irq_enter()定义如下:1. #define _irq_enter()2. do3. account_system_vtime(current);4. add_preempt_count(HARDIRQ_OFFSET);5. trace_hardirq_enter();6. while(0)add_preempt_count(HARDIRQ_OFFSET)使表示中断处理程序嵌套层次的计数器加1。计数器保存在当前进程thread_info结构的preempt_count字段中:图5-5 preempt_count结构内核将preempt_count分成5部分:bit07与PREEMPT相关,bit815用作软中断计数器,bit1625用作硬中断计数器,bit26用作不可屏蔽中断计数器,bit28用作PREEMPT_ACTIVE标志。3、generic_handle_irqgeneric_handle_irq是体系结构无关函数,用来调用desc-handle_irq,该函数指针在中断初始化时指向了电流处理函数(handle_level_irq或handle_edge_irq),针对不同的中断触发类型(边沿触发或电平触发)做相应的处理。然后调用handle_IRQ_event遍历action链表从而调用该中断号对应的一个或多个中断处理程序action-handler,而action-handler就是通过request_irq初始化的。handle_level_irq首先分析一下handle_level_irq函数:1. 2. void handle_level_irq(unsignedintirq,struct irq_desc*desc)3. 4. struct irqaction*action;5. irqreturn_t action_ret;6.7. spin_lock(&desc-lock);/*访问desc内容之前先加自旋锁*/8. mask_ack_irq(desc,irq);/*屏蔽与irq号对应的中断线*/9.10. /*在多处理器系统上,为了避免多cpu同时处理同一中断。11. *当desc-status包含IRQ_INPROGRESS标志时,说明该中断12. *正在另一个cpu上处理,因此当前cpu可以直接放弃处理。13. */14. if(unlikely(desc-status&IRQ_INPROGRESS)15. goto out_unlock;16. desc-status&=(IRQ_REPLAY|IRQ_WAITING);17. kstat_incr_irqs_this_cpu(irq,desc);18.19. /*20. *如果没有对该中断注册处理程序,即desc-action为NULL。21. *或者desc-status设置为IRQ_DISABLED,表示该中断是被禁止的。22. *以上两种情况只要出现一种即可放弃处理。23. */24. action=desc-action;25. if(unlikely(!action|(desc-status&IRQ_DISABLED)26. goto out_unlock;27.28. desc-status|=IRQ_INPROGRESS;/*标识中断状态为正在处理*/29. spin_unlock(&desc-lock);/*释放自旋锁*/30.31. /*调用由request_irq注册的处理函数,稍后分析。*/32. action_ret=handle_IRQ_event(irq,action);33. if(!noirqdebug)34. note_interrupt(irq,desc,action_ret);35.36. spin_lock(&desc-lock);/*访问desc内容前加自旋锁*/37. desc-status&=IRQ_INPROGRESS;/*清除“正在处理”的标识*/38.39. /*如果desc-status 包含IRQ_ONESHOT,40. *则将desc-status设置为IRQ_MASKED,使该中断仍处于被屏蔽状态。*/41. if(unlikely(desc-status&IRQ_ONESHOT)42. desc-status|=IRQ_MASKED;43. /*如果中断处理函数中未对desc-status 设置为IRQ_ DISABLED,44. *且desc-chip-unmask不为空,则desc-chip-unmask所指向的芯片相关函数,45. *解除对该中断的屏蔽。46. */47. elseif(!(desc-status&IRQ_DISABLED)&desc-chip-unmask)48. desc-chip-unmask(irq);49. out_unlock:50. spin_unlock(&desc-lock);/*释放自旋锁*/51. handle_edge_irq再来介绍一下handle_edge_irq函数,相对于handle_level_irq要复杂一点:1. 2. void handle_edge_irq(unsignedintirq,struct irq_desc*desc)3. 4. spin_lock(&desc-lock);5. desc-status&=(IRQ_REPLAY|IRQ_WAITING);6. /*7. *如果该中断正在被其他cpu处理,或者是该中断已被禁止,8. *则不处理该中断,但

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论