用户模式下的中断处理
先来回顾一下中断发生时系统的处理过程。当中断发生时,系统跳转到vector_irq处执行,它获得返回地址,在sp指针(中断模式下的栈,临时性的)所指的地方保存r0、lr和spsr,之后进入SVC模式,并根据中断产生时CPU的模式,以模式的低4位值为索引,来取相应的处理程序的地址,从而进入中断的处理过程。r0中保存中断时中断模式的SP的值。还是在这里补充一点ARM的CPU模式的东西好。ARM处理器的最低5位用来指示处理当前所在的模式。各模式对应的模式值如下(在arch/arm/include/asm/ptrace.h中):
#define USR_MODE 0x00000010
#define FIQ_MODE 0x00000011
#define IRQ_MODE 0x00000012
#define SVC_MODE 0x00000013
#define ABT_MODE 0x00000017
#define UND_MODE 0x0000001b
#define SYSTEM_MODE 0x0000001f
中断发生时,CPU处于用户模式下,则当然会调用__irq_usr例程,
.align 5
__irq_usr:
usr_entry//宏,保存各寄存器,便于返回的时候恢复
kuser_cmpxchg_check
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
ARM( strne r0,
[r0, -r0] )
THUMB( movne r0,
#0 )
THUMB( strne r0,
[r0] )
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
mov why, #0
b ret_to_user//返回
UNWIND(.fnend )
ENDPROC(__irq_usr)
先来看usr_entry,在arch/arm/kernel/entry-armv.S中:
.macro usr_entry
UNWIND(.fnstart )
UNWIND(.cantunwind ) @ don't unwind the user
space
sub sp, sp, #S_FRAME_SIZE
// stmib,每次传送前地址加四
ARM( stmib sp, {r1 - r12} )
THUMB( stmia sp, {r0 - r12} )
//把r0指向的地址处的值传给r1到r13,即r1=r0,r2=lr,r3=spsr。
ldmia r0, {r1 - r3}
add r0, sp, #S_PC @
here for interlock avoidance
mov r4, #-1 @ ""
"" "" ""
// r1存放的是实际r0的值,这里就是存储实际的r0
str r1, [sp] @
save the "real" r0 copied
@
from the exception stack
@
@
We are now ready to fill in the remaining blanks on the stack:
@
@ r2 - lr_, already fixed up
for correct return/restart
@ r3 - spsr_
@ r4 - orig_r0 (see pt_regs definition in
ptrace.h)
@
@
Also, separately save sp_usr and lr_usr
@
//存储返回地址,现场状态等(被中断代码处的)
stmia r0, {r2 - r4}
//存储用户模式下的sp,lr,ARM体系结构中用户模式和系统模式共用同一个
// SP和LR
ARM( stmdb r0, {sp, lr}^ )
THUMB( store_user_sp_lr
r0, r1, S_SP - S_PC )
@
@
Enable the alignment trap while in kernel mode
@
alignment_trap
r0
@
@
Clear FP to mark the first stack frame
@
zero_fp
asm_trace_hardirqs_off
.endm
这个宏和svc_entry一样,主要也是保存各个寄存器值到栈上相应的位置。__irq_usr中间的代码和__irq_svc一样,这里就不再赘述了。__irq_usr处理完中断后,调用ret_to_user来返回到用户模式,这个例程在arch/arm/kernel/entry-common.S下定义:
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq @ disable interrupts
ldr r1, [tsk, #TI_FLAGS]@获取thread_info中flags域的值
tst r1, #_TIF_WORK_MASK@判断task是否被阻塞
bne work_pending@根据需要进行进程的切换。
no_work_pending:
/*
perform architecture specific actions before user return */
//宏,空的在arch/arm/mach-s3c2410/include/mach/entry-macro.S中
//定义
arch_ret_to_user r1, lr
restore_user_regs
fast = 0, offset = 0
ENDPROC(ret_to_user)
宏restore_user_regs在arch/arm/kernel/entry-header.S中定义:
.macro restore_user_regs, fast = 0, offset = 0
//获取被中断代码处的状态(cpsr)
ldr r1, [sp, #\offset + S_PSR] @ get calling cpsr
ldr lr, [sp, #\offset + S_PC]! @ get pc
// spsr里保存好被中断代码处的状态(cpsp)
msr spsr_cxsf, r1 @
save in spsr_svc
#if defined(CONFIG_CPU_32v6K)
clrex @ clear the exclusive monitor
#elif defined (CONFIG_CPU_V6)
strex r1, r2, [sp] @
clear the exclusive monitor
#endif
.if \fast
ldmdb sp, {r1 - lr}^ @
get calling r1 - lr
.else
ldmdb sp, {r0 - lr}^ @
get calling r0 - lr
.endif
@栈地址恢复,避免多个中断后溢出
add sp, sp, #S_FRAME_SIZE - S_PC
//返回被中断代码处继续执行,并把spsr赋给cpsr,即恢复被中断处
//的现场状态。这样CPU又可以从被中断的地方继续执行了,而且这个
//时候所有的寄存器值(r0到r12),包括状态寄存器值(cpsr)都是源
//码被中断时的值。
movs pc, lr @ return & move spsr_svc into cpsr
.endm
恢复寄存器,完成中断处理,回到用户模式。
顺便看下work_pending,在arch/arm/kernel/entry-common.S中:
work_pending:
tst r1,
#_TIF_NEED_RESCHED @判断是否需要调度进程
bne work_resched @进程调度
tst r1,
#_TIF_SIGPENDING|_TIF_NOTIFY_RESUME
beq no_work_pending @无需调度,返回
mov r0,
sp @ 'regs'
mov r2,
why @ 'syscall'
bl do_notify_resume
b ret_slow_syscall @ Check work again
work_resched:
bl schedule
由该汇编代码可知,如果在用户模式下产生中断的话,在返回的时候,会根据需要进行进程调度,而从代码可知,如果中断发生在管理等内核模式下的话是不会进行进程调度的。
Ok,中断的流程大体就是这样的,c函数里的中断流程在系统模式下的中断处理里已经说的很多了,这里不再赘述。