ARM Linux中断机制分析
C_MODE,其它位保持不变。
@ the branch table must immediately follow this code and lr, lr, #0x0f//提取发生异常前的处理器模式,这里也就是usr模式 mov r0, sp ldr lr, [pc, lr, lsl #2] movs pc, lr |
sp是SVC32模式下的堆栈指针,这里将它移到r0中,就可以作为C函数的第一个参数,即C函数中的pt_regs参数。
pc是当前地址+8,也就是本段代码后面紧跟的跳转表的基地址,lr用于在跳转表中索引,lr左移两位等同于*4,因为每个条目是4字节。从usr模式进入irq模式,则lr=pc+4*0,若从svc模式进入irq,则lr=pc+4*3,即__irq_svc的地址,其他地址进入__irq_invalid出错处理,因为不能从其他模式进入irq异常。
假设这里是从usr进入irq,则执行跳转表中的第一条指令。跳转的基准地址为当前pc,因为ARMv4是三级流水线结构的,它总是指向当前指令的下两条指令的地址,尽管以后版本的指令流水线扩展为5级和8级,但是这一特性一直被兼容处理,也即pc(excute)=pc(fetch) + 8,其中:pc(fetch)是当前正在执行的指令,就是之前取该指令时候的PC的值;pc(execute):当前指令执行的,计算中如果用到pc,是指此时pc的值。
当mov指令的目标寄存器是PC,且指令以S结束,则它会把spsr的值恢复给cpsr,上面说到当前的spsr中保存的是r0的值,即svc模式。所以本条指令是跳转到__irq_usr的同时将处理器模式转为svc模式。异常处理一定要进入svc模式的原因是:异常处理一定要进入PL1特权级;另一个原因是使能嵌套中断。具体原因在问题4中解释。关于__irq_svc和__irq_invalid暂时不讨论。
/* * Interrupt dispatcher以下跳转表必须紧跟在vector_irq之后 */ vector_stub irq, IRQ_MODE, 4 //生成vector_irq /*从用户态进入中断的处理函数*/ .long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) /*从SVC进入中断的处理函数*/ .long __irq_svc @ 3 (SVC_26 / SVC_32) .long __irq_invalid @ 4 .long __irq_invalid @ 5 .long __irq_invalid @ 6 |
图2IRQ中断处理跳转示意图
注意,以下操作都是在svc模式中,因为要借用SVC模式进行ISP处理,所以需要保存所有SVC模式下的寄存器到SVC堆栈中,最后才去调用中断服务例程(ISP)irq_handler。
2.2.1 __irq_usr
.align 5 __irq_usr: usr_entry //用于用户模式下发生中断时初始化中断处理堆栈,同时保存所有SVC态寄存器到堆栈。 kuser_cmpxchg_check //对低版本的ARM核来说,用户态无法实现原子比较交换。如果用户态在处理原 //子比较交换的过程中发生中断,需要特殊处理,略过 get_thread_info tsk //根据当前sp指针,将该指针最右边13位清0,获得当前任务的thread_info #ifdef CONFIG_PREEMPT//如果可以抢占,递增任务的抢占计数 ldr r8, [tsk, #TI_PREEMPT]//T被定义为offsetof(struct thread_info, preempt_count),显然通过tsk就 可以很容易得到进程preempt_count成员的地址了 add r7, r8, #1 @ increment it str r7, [tsk, #TI_PREEMPT] #endif irq_handler //中断服务例程,后面分析 #ifdef CONFIG_PREEMPT ldr r0, [tsk, #TI_PREEMPT]//获得当前的抢占计数 str r8, [tsk, #TI_PREEMPT]//并将r8中的值保存回去。相当于将前一步递增的抢占计数减回去了 teq r0, r7//r0,r7是调用irq_handler前后的抢占计数,这里进行比较,是防止驱动的ISR //程序没有配对操作抢占计数导致系统错误。 ARM( strne r0, [r0, -r0] )//如果抢占计数被破坏,则强制写入0. THUMB( movne r0, #0 ) THUMB( strne r0, [r0] ) #endif mov why, #0 b ret_to_user //返回到用户态 UNWIND(.fnend ) ENDPROC(__irq_usr) |
接下来分别看各个函数的功能
arch/arm/include/asm/ptrace.h struct pt_regs { unsigned long uregs[18]; }; #endif /* __KERNEL__ */ #define ARM_cpsr uregs[16] #define ARM_pc uregs[15] #define ARM_lr uregs[14] #define ARM_sp uregs[13] #define ARM_ip uregs[12] #define ARM_fp uregs[11] #define ARM_r10 uregs[10] #define ARM_r9 uregs[9] …… #define ARM_ORIG_r0 uregs[17] |
pt_regs结构体定义,后面要用到。
.macrousr_entry //usr_entry宏定义 UNWIND(.fnstart ) UNWIND(.cantunwind ) @ dont unwind the user space sub sp, sp, #S_FRAME_SIZE @ S_FRAME_SIZE定义在trimslice-kernel\arch\arm\kernel\arm-offsets.c中S_FRAME_SIZE被定义为sizeof(struct pt_regs)的大小=18*4=72字节,将svc32堆栈指针向低地址 |
ARMLinux中断机 相关文章:
- ARM Linux中断机制之中断的初始化(11-10)
- ARM Linux中断机制之中断的申请(11-10)
- ARM Linux中断机制之中断处理(11-09)
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)