《LINUX3.0内核源代码分析》第二章:中断和异常 【转】
转自:http://blog.chinaunix.net/uid-25845340-id-2982887.html
摘要:第二章主要讲述linux如何处理ARM cortex A9多核处理器的中断、异常。介绍了中断向量表的入口、通用的中断处理代码、中断和软中断、延迟处理、中断异常的返回过程。
第二章内容较多,会分几个部分讲述。本部分主要讲进入、退出中断的过程,这部分代码涉及的都是汇编部分。
法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友(scxby@163.com)发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。
本连载文章并不是为了形成一本适合出版的书籍,而是为了向有一定内核基本的读者提供一些linux3.0源码分析。因此,请读者结合《深入理解LINUX内核》第三版阅读本连载。
由于我的主要工作不是BSP,对CPU体系结构不算太熟悉。如果非要说熟悉哪种CPU的话,应该是对MIPS熟悉一点。ARM方面纯粹是临阵磨枪,为了写本系列文章,前两个月临时看了一下相关书籍。如有不清楚或者错误的地方,敬请大家指出,先谢过了^-^。
请读者先看看《ARM嵌入式开发》第9章,对ARM的6种异常有所了解。并明白在进入中断和异常时,硬件都完成了哪些事情。
1.1.1 中断向量和简单中断处理
ARM中断向量表在entry-armv.S中,如下:
__vectors_start:
ARM( swi SYS_ERROR0 )/* reset异常 */
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset/* 未定义指令 */
W(ldr) pc, .LCvswi + stubs_offset/* 系统调用 */
W(b) vector_pabt + stubs_offset/* 指令预取异常 */
W(b) vector_dabt + stubs_offset/* 数据访问中止异常 */
W(b) vector_addrexcptn + stubs_offset/* 保留 */
W(b) vector_irq + stubs_offset/* 中断 */
W(b) vector_fiq + stubs_offset/* 快速中断 */
.globl __vectors_end
__vectors_end:
不象MIPS,ARM中断向量表中每一个中断向量只能存储一条指令,因此必须使用一条跳转指令,跳转到各自的处理程序。当然,为了提高快速中断的处理速度,可以将它的处理代码直接跟随在中断向量表后面。但是linux没有这样实现。
在这8个向量中, vector_addrexcptn和vector_fiq比较简单:
vector_fiq:
disable_fiq /* 简单的禁止fiq,这样,中断处理退回后,不会再次产生fiq中断了。也就是说,FIQ中断只可能产生一次。 */
subs pc, lr, #4 /* lr指向当前异常地址+8的地方,这里将其减去4,即是退出异常时,要返回的地址。这里直接返回。 */
/*=============================================================================
* Address exception handler
*-----------------------------------------------------------------------------
* These aren't too critical.
* (they're not supposed to happen, and won't happen in 32-bit data mode).
*/
/**
* 根据注释,这里是处理地址异常,它不但不重要,而且不大可能产生。因此就是一个死循环,将系统挂死在这里。
* 根据《ARM嵌入式系统开发》所述,这是一个保留异常。可能真的不大可能发生。
*/
vector_addrexcptn:
b vector_addrexcptn
reset异常更简单,它仅仅是模拟调用一次SYS_ERROR0,但这应该是内核初始化完成之后,才这样简单。在flash上的复位异常是整个初始化的入口,应该非常复杂。
ARM( swi SYS_ERROR0 )/* reset异常,简单的调用SYS_ERROR0系统调用即可 */
THUMB( svc #0 )/* 应该不会运行到这里,呵呵,这仅仅是我的猜想 */
THUMB( nop )
这8个中断异常入口,除系统调用异常外,都是使用b指令进行跳转。系统调用异常使用是这样的:
W(ldr) pc, .LCvswi + stubs_offset
由于系统调用异常的代码编译在其他文件中,其入口地址与异常向量相隔较远,使用b指令无法跳转过去。 因此将其地址存放到LCvswi中,并从内存地址中加载其入口地址。这样,系统调用的速度稍微慢一点。
1.1.2 从汇编跳转到C代码
未定义指令异常、指令预取异常、数据访问中止异常、中断的处理代码分别是vector_und、vector_pabt、vector_dabt和vector_irq。这几个函数是由以下代码生成的:
vector_stub irq, IRQ_MODE, 4
vector_stub dabt, ABT_MODE, 8
vector_stub pabt, ABT_MODE, 4
vector_stub und, UND_MODE
我们以vector_stub irq, IRQ_MODE, 4为例,看看vector_stub生成了什么代码:
/**
* 生成通用中断、异常处理代码的宏。
* correction用于调整lr的值。这是因为进入异常时,pc指针是发生异常时的指针后面8个字节或者12个字节处。
* 不同的异常需要跳转到不同的返回地址。有的需要重新执行指令,有的则需要跳到下一条指令处。
*/
.macro vector_stub, name, mode, correction=0
/* 将异常入口强制进行32字节对齐,32字节是一个缓存行的大小。这应当是出于性能的考虑。 */
.align 5
vector_\name:
/* 需要调整返回值,则递减lr寄存器 */
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
@
/**
* 将r0,lr保存到堆栈中。这里并没有移动堆栈指针。
* 这是因为:每种处理器模式都有自己的堆栈。接下来系统会切换到svc模式,将堆栈切换到每个任务的系统堆栈去。
* 执行后,[sp] = r0, [sp+4]=lr,这里保存r0和lr是因为后面要使用这两个寄存器,即这两个寄存器会被破坏。
* 请注意:中断和异常并不会保存所有寄存器。
*/
stmia sp, {r0, lr} @ save r0, lr
/**
* spsr是异常发生前的状态寄存器,退出异常后,需要根据它恢复现场,因此需要将它保存起来。
* 首先将它装载到lr寄存器,再将它存储到[sp+8]处。
*/
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
/**
* 以下三句,是准备将处理器模式设置为SVC32模式。这样,当前堆栈也会切换到SVC32模式下的堆栈。
*/
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
/**
* lr中保存了异常前的状态,与0x0f and后,可以得到异常前的处理器模式。
*/
and lr, lr, #0x0f
/* 1f就是宏生成的代码后面的跳转表,这里根据异常前的处理器模式,决定跳转到哪一个处理代码 */
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
/* sp是SVC32模式下的堆栈指针,这里将它移到r0中,就可以作为C函数的第一个参数,即C函数中的pt_regs参数 */
mov r0, sp
/* pc指针此时指向了1f,即跳转表,因此将它加上lr<<2,就可以按处理器模式进行跳转了 */
ARM( ldr lr, [pc, lr, lsl #2] )
/* 这条指令是从异常返回,由于我们修改了spsr寄存器,因此会进入SVC32模式,并不是真的从异常返回了 */
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
.align 2
@ handler addresses follow this label
1:
.endm
分析完vector_stub宏代码,我们再看看中断处理函数是如何生成的:
/**
* 借助宏vector_stub生成vector_irq主体代码
*/
vector_stub irq, IRQ_MODE, 4
/**
* 下面的跳转表必须紧跟在vector_stub宏后面,参见前文对vector_stub的分析。
*/
/* 从用户态进入中断的处理函数 */
.long __irq_usr @ 0 (USR_26 / USR_32)
/* 错误,不应该从FIQ状态进入IRQ状态 */
.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
.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
接下来我们看看__irq_invalid,这段代码一般情况不应当被调用。
__irq_invalid:
/**
* 将所有寄存器保存到堆栈中,并将BAD_IRQ作为错误原因写入r1寄存器。
*/
inv_entry BAD_IRQ
/**
* 跳转到通用错误处理
*/
b common_invalid
ENDPROC(__irq_invalid)
common_invalid代码如下:
common_invalid:
/**
* 如果需要栈帧,就将fp设置为0,这样在进行堆栈回溯时,就可以知道这里的堆栈是一个中断的栈帧了。
*/
zero_fp
/**
* r0保存的是中断栈开始的地方,将中断前的r0-r2寄存器现场恢复到r4-r6中。
*/
ldmia r0, {r4 - r6}
/**
* 调整r0,使其指向中断现场的PC
*/
add r0, sp, #S_PC @ here for interlock avoidance
mov r7, #-1 @ "" "" "" ""
/**
* 将中断前的r0存到sp中。
*/
str r4, [sp] @ save preserved r0
/* 这里没有看清楚,飘过。清楚的同学发一个邮件给我scxby@163.com */
stmia r0, {r5 - r7} @ lr_,
@ cpsr_, "old_r0"
/* sp是SVC32模式上的堆栈地址,指向pt_regs,即中断前的寄存器现场 */
mov r0, sp
/**
* 跳转到C处理函数,这里编译脚本应当有处理,这样才能确保bad_mode与当前指令相近。否则b指令跳不过去。
*/
b bad_mode
ENDPROC(__und_invalid)
1.1.1.1 从用户态进入中断
_irq_usr函数的第一步是保存用户态寄存器现场到svc32堆栈中,这是通过调用usr_enry来实现的:
.macro usr_entry
UNWIND(.fnstart )
UNWIND(.cantunwind ) @ don't unwind the user space
/**
* 将svc32堆栈指针向低地址方向移动一个pt_regs结构大小,用于保存寄存器现场。
*/
sub sp, sp, #S_FRAME_SIZE
/**
* 向svc32堆栈中保存寄存器现场。
*/
ARM( stmib sp, {r1 - r12} )
THUMB( stmia sp, {r0 - r12} )
/**
* r0是中断栈指针,从其中取出中断前的r0-r2现场放到r1-r4中。
*/
ldmia r0, {r1 - r3}
add r0, sp, #S_PC @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
/**
* 从中断栈中取出真实的r0存放到pt_regs->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
@
/**
* 将中断异常栈中取出中断前ARM_pc、ARM_cpsr保存到svc32栈中。
*/
stmia r0, {r2 - r4}
/**
* 将栈指针和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
@
/**
* 将fp设置为0,这样可以标示一个中断栈帧。
*/
zero_fp
.endm
中断处理的主要过程如下:
/**
* 从用户态进入中断。
*/
__irq_usr:
/**
* 将寄存器现场保存起来。
*/
usr_entry
/**
* 对低版本的ARM核来说,用户态无法实现原子比较交换。如果用户态在处理原子比较交换的过程中发生中断,需要特殊处理,略过。
*/
kuser_cmpxchg_check
/**
* 如果打开了IRQSOFF_TRACER检测开关,则在这里记录下关中断的时间。这在实时系统中比较有用。
* 请记住,系统运行到这里,仍然是处于关中断状态的。
*/
#ifdef CONFIG_IRQSOFF_TRACER
bl trace_hardirqs_off
#endif
/**
* 根据当前sp指针,将该指针最右边13位清0,获得当前任务的thread_info。
*/
get_thread_info tsk
#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]
/**
* 并将r8中的值保存回去。相当于将前一步递增的抢占计数减回去了。
*/
str r8, [tsk, #TI_PREEMPT]
/**
* r0,r7是调用irq_handler前后的抢占计数,这里进行比较,是防止驱动的ISR程序没有配对操作抢占计数导致系统错误。
*/
teq r0, r7
/**
* 如果抢占计数被破坏,则强制写入0.
*/
ARM( strne r0, [r0, -r0] )
THUMB( movne r0, #0 )
THUMB( strne r0, [r0] )
#endif
/**
* 从中断退回用户态。
*/
mov why, #0
b ret_to_user_from_irq
UNWIND(.fnend )
ENDPROC(__irq_usr)
在没有配置MULTI_IRQ_HANDLER 的情况下,irq_handler的逻辑很简单,就是简单的调用arch_irq_handler_default。
.macro arch_irq_handler_default
get_irqnr_preamble r5, lr
/**
* 将中断号读取到r0寄存器。
*/
1: get_irqnr_and_base r0, r6, r5, lr
/**
* 如果还存在中断,就将sp作为第二个参数,调用asm_do_IRQ。sp目前指向pt_regs。
*/
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
/**
* 这里将lr设置为get_irqnr_and_base的第二条指令,因为第二次循环时,不必执行其第一条指令(加载寄存器基址)
*/
adrne lr, BSYM(1b)
/**
* 将中断号、pt_regs(中断前的寄存器现场)传递给asm_do_IRQ。
* 请注意,当asm_do_IRQ返回时,会返回到get_irqnr_and_base处,这里相当于是一个循环处理,直到所有中断都已经处理完毕才退出循环。
*/
bne asm_do_IRQ
#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r6) and base (r5) are
* preserved from get_irqnr_and_base above
*/
/**
* 这里是从寄存器中读取ipi标志
*/
ALT_SMP(test_for_ipi r0, r6, r5, lr)
ALT_UP_B(9997f)
movne r1, sp
/**
* 同理,这里也是将返回地址设置为ALT_SMP的第二条指令,构造成一个循环。
*/
adrne lr, BSYM(1b)
/**
* 只要存在IPI就调用do_IPI,并循环直到处理完所有IPI。
*/
bne do_IPI
#ifdef CONFIG_LOCAL_TIMERS
/**
* 同理,这里循环处理多核系统中的本地时钟中断。
*/
test_for_ltirq r0, r6, r5, lr
movne r0, sp
adrne lr, BSYM(1b)
bne do_local_timer
#endif
#endif
9997:
.endm
至此,我们已经将汇编部分分析完毕。在跳转到C代码前,汇编代码会将中断前的现场保存到堆栈中,并形成一个pt_regs结构传给C函数。最终,会循环调用asm_do_IRQ、do_IPI、do_local_timer。仅仅在多核下,才可能处理IPI和local_timer。
1.1.1.1 退回用户态
从中断返回到用户态是由ret_to_user_from_irq进行处理的,在恢复寄存器现场前,需要处理抢占、检查信号等等。
/**
* 从中断返回用户态,在软中断或者中断处理函数退出时,系统确保已经关闭了中断。
*/
ENTRY(ret_to_user_from_irq)
/**
* 从任务的TI_FLAGS标志判断是否需要处理抢占或者信号。
*/
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
/**
* 处理抢占或者信号
*/
bne work_pending
/**
* 运行到这里,说明没有抢占或者信号需要处理,或者已经处理完毕,开始退回用户态了。
*/
no_work_pending:
/**
* 退回用户态时,必然会打开中断,因此这里记录下打开中断的事实,供调试用。
*/
#if defined(CONFIG_IRQSOFF_TRACER)
asm_trace_hardirqs_on
#endif
/* perform architecture specific actions before user return */
/**
* 在返回用户态前,处理各个体系结构的钩子,对我们分析的单板来说,没有钩子需要处理。
*/
arch_ret_to_user r1, lr
/**
* 恢复寄存器现场,并切回用户态。
*/
restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)
在切换回用户态前,需要处理抢占和信号:
work_pending:
/**
* 检查任务的_TIF_NEED_RESCHED,如果置位,则说明需要处理任务抢占,在这里调度到高优先级任务。
*/
tst r1, #_TIF_NEED_RESCHED
bne work_resched
/**
* 接着处理信号。
*/
tst r1, #_TIF_SIGPENDING|_TIF_NOTIFY_RESUME
/**
* 没有信号需要处理,则跳转到no_work_pending并退回用户态。
*/
beq no_work_pending
mov r0, sp @ 'regs'
mov r2, why @ 'syscall'
tst r1, #_TIF_SIGPENDING @ delivering a signal?
movne why, #0 @ prevent further restarts
/**
* 这里处理信号
*/
bl do_notify_resume
/**
* 然后重新关中断并判断是否有更多任务需要处理。
*/
b ret_slow_syscall @ Check work again
/**
* 这里处理抢占,注意这里可以调用schedule,而不用调用preempt_schedule。这是有原因的。
* 另外,这个标号也不能随意移到其他地方。因为调用schedule后,流程会转到ret_slow_syscall。
* ret_slow_syscall上会关中断,然后将中断、异常返回流程重新处理一次。
* 需要关中断的原因,是schedule函数会强制将中断打开。
*/
work_resched:
bl schedule
/*
* "slow" syscall return path. "why" tells us if this was a real syscall.
*/
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq @ disable interrupts
1.1.1.2 从svc32模式进入中断
当中断嵌套或者中断打断系统调用等异常时,中断会从svc32模式进入中断。在开始中断处理前,系统仍然需要保存寄存器现场。这是通过调用宏svc_entry来实现的。
/**
* 当从svc模式进入中断处理程序时,使用本宏保存寄存器现场到堆栈中,并形成pt_regs结构传递给C函数。
*/
.macro svc_entry, stack_hole=0
UNWIND(.fnstart )
UNWIND(.save {r0 - pc} )
/**
* 将当前指针向低地址移动,以保存寄存器现场。这里减去4是为了将sp指向pt_regs中r1的位置。
*/
sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
#ifdef CONFIG_THUMB2_KERNEL/* 新内核支持将内核编译为THUMB-2,以节省代码空间,我们的系统不支持这个功能 */
SPFIX( str r0, [sp] ) @ temporarily saved
SPFIX( mov r0, sp )
SPFIX( tst r0, #4 ) @ test original stack alignment
SPFIX( ldr r0, [sp] ) @ restored
#else
SPFIX( tst sp, #4 )
#endif
SPFIX( subeq sp, sp, #4 )
/**
* 将r1-r12保存到堆栈中。
*/
stmia sp, {r1 - r12}
/**
* r0,sp,lr,spsr已经被汇编代码使用,因此需要根据r0从中断栈(我们目前正在使用的是svc栈)中取出
*/
ldmia r0, {r1 - r3}
/**
* r5指向pt_regs的ARM_sp即r13
*/
add r5, sp, #S_SP - 4 @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
/**
* 将r0调整到刚进入宏的位置
*/
add r0, sp, #(S_FRAME_SIZE + \stack_hole - 4)
SPFIX( addeq r0, r0, #4 )
/**
* 保存r0,同时将sp向下调整4字节,现在sp指向pt_regs了。
*/
str r1, [sp, #-4]! @ save the "real" r0 copied
@ from the exception stack
mov r1, lr
@
@ We are now ready to fill in the remaining blanks on the stack:
@
@ r0 - sp_svc
@ r1 - lr_svc
@ r2 - lr_, already fixed up for correct return/restart
@ r3 - spsr_
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
stmia r5, {r0 - r4}/* 将中断栈中的数据保存到pt_regs */
.endm
当不是从用户态进入中断时,中断处理代码要稍显复杂一点,主要是需要处理抢占:
/**
* 从svc32模式进入中断的处理过程
*/
__irq_svc:
/**
* 首先保存寄存器现场。
*/
svc_entry
/**
* 进入中断后,系统自动将中断关闭,这里调用trace_hardirqs_off记录下中断被关闭的事实,用于跟踪调试。
*/
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
#ifdef CONFIG_PREEMPT
/**
* 对可抢占内核来说,这里将任务的抢占计数加1,在整个中断处理过程中,进程都不能被抢占。
*/
get_thread_info tsk
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
/**
* 恢复抢占计数。
*/
str r8, [tsk, #TI_PREEMPT] @ restore preempt count
/**
* 将任务的TI_FLAGS标志加载到r0中,这样后面会根据r0判断_TIF_NEED_RESCHED,以处理任务抢占
*/
ldr r0, [tsk, #TI_FLAGS] @ get flags
/**
* 如果在进入中断前,系统处于系统调用状态,那么抢占计数就可能为0.
* 这里比较抢占计数是否为0,如果为0,则进行抢占处理。
*/
teq r8, #0 @ if preempt count != 0
/**
* 如果系统关抢占了,那么强制针r0清0,这样就不可能调用svc_preempt
*/
movne r0, #0 @ force flags to 0
/**
* 如果系统没有关抢占,并且任务存在_TIF_NEED_RESCHED标志,则调用svc_preempt处理抢占。
*/
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
/* 在此中断处于关闭状态,从pt_regs中获得中断前的SPSR寄存器,接下来将会用这个寄存器恢复状态。 */
ldr r4, [sp, #S_PSR] @ irqs are already disabled
/* 如果恢复状态后,将会打开中断,则调用trace_hardirqs_on进行跟踪 */
#ifdef CONFIG_TRACE_IRQFLAGS
tst r4, #PSR_I_BIT
bleq trace_hardirqs_on
#endif
/**
* 退回svc32模式
*/
svc_exit r4 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
处理抢占的代码并不复杂,如下:
#ifdef CONFIG_PREEMPT
svc_preempt:
mov r8, lr
/**
* 这里调用preempt_schedule_irq处理抢占调度,今后在分析调度时,将会详细介绍这个函数。
*/
1: bl preempt_schedule_irq @ irq en/disable is done inside
/**
* preempt_schedule_irq返回时,会重新将中断关闭,这里加载TI_FLAGS标志是安全的。
*/
ldr r0, [tsk, #TI_FLAGS] @ get new tasks TI_FLAGS
tst r0, #_TIF_NEED_RESCHED
/**
* 如果任务没有抢占标志,那么退回上层继续处理,恢复寄存器现场,返回上层中断。
*/
moveq pc, r8 @ go again
/**
* 否则表示任务再次被抢占,循环处理抢占。
*/
b 1b
#endif
1.1.1.3 退回svc32模式
从中断退出的代码如下:
#ifndef CONFIG_THUMB2_KERNEL
.macro svc_exit, rpsr
msr spsr_cxsf, \rpsr /* 恢复rpsr */
#if defined(CONFIG_CPU_V6)
/**
* 恢复r0寄存器
*/
ldr r0, [sp]
/**
* 由于发生了中断,需要执行strex指令,这样上层中断中的spinlock会认为排它性装载失效,重启spinlock循环。
* 在mips等体系结构中,这是由硬件完成的。可能ARM硬件不能完成这件事。
*/
strex r1, r2, [sp] @ clear the exclusive monitor
/**
* 恢复所有寄存器,并恢复cpsr。将处理器状态切回中断前。
*/
ldmib sp, {r1 - pc}^ @ load r1 - pc, cpsr
#elif defined(CONFIG_CPU_32v6K)
clrex @ clear the exclusive monitor
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#else
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#endif
.endm
至此,进入中断和退出中断的基本流程已经梳理完成。我们将在下一部分讲中断处理函数的C语言部分。包含ISR和软中断的处理。