2010年(7)
分类: 嵌入式
2010-05-18 15:13:54
三 响应中断
首先在分析源码之前,让我们了解一些原理性的东西, 我们都知道在处理中断要保存当前现场状态,然后才能处理中断,处理完之后还要把现场状态恢复过来才能返回到被中断的地方继续执行,这里要说明的是在指令跳转到中断向量的地方开始执行之前,CPU帮我们做了哪些事情:
R14_irq = 要执行的下条指令地址 + 4 //这里的下条指令是相对于被中断指令的下条。即返回地址
SPSR_irq = CPSR //保存的现场状态,r0到r12要由我们软件来保存(如果需要的话)。
CPSR[4:0] = 0b10010 //进入中断模式
CPSR[5] = 0 //在ARM模式下执行(不是Thumb下)
CPSR[7] = 1 //关掉IRQ中断, FIQ还是开着
PC = 0Xffff0018 / 0x00000018 //根据异常向量表的位置,跳转到特定的中断向量处去执行。
更详细的关于异常处理的细节可参考<
接下来我们在来分析watchdog产生中断后的处理流程:
当watchdog超时时将会产生中断,中断号就是IRQ_WDT,当产生中断时,系统将从跳转表中的中断位置开始运行,对于我们这篇文章来说:是从0xffff0000 + 24处开始运行。 这个地址的指令是:
b vector_irq + stubs_offset
即直接跳转到 vector_irq 处去运行。这些都在中断初始化的时候分析过了。
我们来看 vector_irq,它是通过宏vector_stub来定义的:
arch/arm/kernel/entry-armv.S:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 /*这是个宏定义*/
/*下面这些都是不同模式下的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)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
来看宏vector_stub
arch/arm/kernel/entry-armv.S:
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
这样展开后 vector_irq 如下所示:
arch/arm/kernel/entry-armv.S:
vector_irq:
.if 4
@ lr保存的是被打断指令处地址+8的值,(看上面的分析,由 PC 得到), 这里-4则就是中断
@ 处理完后的返回地址, 在中断处理完后该值会赋给 PC
sub lr, lr, #4
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@ r0后面会用到所以要保存。
stmia sp, {r0, lr} @ save r0, lr,保存r0,lr到栈上,这里的栈是中断模式下的。
mrs lr, spsr @获取spsr的值,该值保存了被中断处执行环境的状态(参考上面的分析)
str lr, [sp, #8] @ save spsr, 保存到栈上
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #( IRQ_MODE ^ SVC_MODE)
msr spsr_cxsf, r0 @把spsr设置成管理模式
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode @ pc = lr, cpsr = spsr
.endm
movs 的目的对象如果是pc的话,则还会把spsr赋值给cpsr,上面我们看到spsr被设成管理模式,因此这条语句过后的代码也就跑在了管理模式下。
此时的栈情况如下:
可以看到该汇编代码主要是把被中断的代码在执行过程中的状态(cpsr), 返回地址(lr)等保存在中断模式下的栈里,然后进入到管理模式下去执行中断,同时令r0 = sp,这样可以在管理模式下找到该地址,进而获取spsr等信息。该汇编代码最终根据被中断的代码所处的模式跳转到相应的处理程序中去。
注意管理模式下的栈和中断模式下的栈不是同一个。同时由于在上面的代码中栈指针(sp)没有进行移位,因此即使后面的代码没对这个栈进行出栈操作,也不会因为不断的产生中断而导致栈溢出。
下面我们以用户模式产生中断为例,它将跳转到__irq_usr(看vector_irq的定义)
arch/arm/kernel/entry-armv.S:
__irq_usr:
usr_entry @宏,保存各寄存器,便于返回的时候恢复
get_thread_info tsk @获取保存当前task信息的地址
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get 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]
teq r0, r7
strne r0, [r0, -r0]
#endif
mov why, #0
b ret_to_user @返回
先看usr_entry
arch/arm/kernel/entry-armv.S:
.macro usr_entry
sub sp, sp, #S_FRAME_SIZE @栈指针下移,空出一段空间存放各寄存器
stmib sp, {r1 - r12} @保存r1到r12的值
ldmia r0, {r1 - r3} @把r0指向的地址处的值传给r1到r13,即r1=r0,r2=lr,r3=spsr。
add r0, sp, #S_PC @ here for interlock avoidance @不会描述,看下面的图吧
mov r4, #-1 @ "" "" "" ""
str r1, [sp] @ save the "real" r0 copied @r1存放的是实际r0的值,这里就是存储
@ from the exception stack @r0的值
#if __LINUX_ARM_ARCH__ < 6 && !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
#ifndef CONFIG_MMU
#warning "NPTL on non MMU needs fixing"
#else
@ make sure our user space atomic helper is aborted
cmp r2, #TASK_SIZE
bichs r3, r3, #PSR_Z_BIT
#endif
#endif
@
@ We are now ready to fill in the remaining blanks on the stack:
@
@ r2 - lr_
@ r3 - spsr_
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
@ Also, separately save sp_usr and lr_usr
@
stmia r0, {r2 - r4} @存储返回地址,现场状态等(被中断代码处的)
stmdb r0, {sp, lr}^ @存储用户模式下的sp,lr,好像这里的顺序和规范上倒了一下J
@
@ Enable the alignment trap while in kernel mode
@
alignment_trap r0
@
@ Clear FP to mark the first stack frame
@
zero_fp
.endm
这个宏主要就是保存各个寄存器值到栈上相应的位置,这个宏执行完后的栈如下所示:
S_FRAME_SIZE, S_PC在arch/arm/kernel/Asm-offsets.c:中定义
DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));
DEFINE(S_PC, offsetof(struct pt_regs, ARM_pc));
include/asm-arm/Ptrace.h:
struct pt_regs {
long uregs[18];
};
#define ARM_pc uregs[15]
呵呵,pt_regs中对应的就是上面栈上的18个寄存器,ARM_pc是pc寄存器存放在这个数组中的偏移。
接着看get_thread_info, 它也是个宏,用来获取当前线程的地址。在我的一篇linux启动代码分析里曾写过线程的定义方式:
include/linux/Sched.h:
union thread_union {
struct thread_info thread_info; /*线程属性*/
unsigned long stack[THREAD_SIZE/sizeof(long)]; /*栈*/
};
由它定义的线程是8K字节对齐的, 并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp低13位清0(8K边界)来获取当前thread_info对象的地址。
arch/arm/kernel/entry-armv.S:
.macro get_thread_info, rd
mov \rd, sp, lsr #13
mov \rd, \rd, lsl #13
.endm
调用该宏后寄存器tsk里存放的就是当前线程的地址了, tsk是哪个寄存器呢,呵呵我们在看:
arch/arm/kernel/entry-header.S:
tsk .req r9 @ current thread_info
呵呵,tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。