ARMLinux中断源码分析2中断处理流程Word格式文档下载.docx
《ARMLinux中断源码分析2中断处理流程Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《ARMLinux中断源码分析2中断处理流程Word格式文档下载.docx(25页珍藏版)》请在冰豆网上搜索。
![ARMLinux中断源码分析2中断处理流程Word格式文档下载.docx](https://file1.bdocx.com/fileroot1/2023-1/8/37f70fb2-e489-4ddb-a284-4e73f50d7df0/37f70fb2-e489-4ddb-a284-4e73f50d7df01.gif)
2.__vectors_start:
3.
swi
SYS_ERROR0
4.
b
vector_und
+
stubs_offset
5.
ldr
pc,
.LCvswi
6.
vector_pabt
7.
vector_dabt
8.
vector_addrexcptn
9.
vector_irq
stubs_offset@中断入口,vector_irq
10.
vector_fiq
11.
12.
.globl
__vectors_end
13.__vectors_end:
vector_irq+stubs_offset为中断的入口点,此处之所以要加上stubs_offset,是为了实现位置无关编程。
首先分析一下stubs_offset(宏是如何计算的:
.equ
stubs_offset,__vectors_start+0x200-__stubs_start
在第3节中已经提到,内核启动时会将异常向量表拷贝到0xFFFF_0000,将异常向量处理程序的stub拷贝到0xFFFF_0200。
图5-1描述了异常向量表和异常处理程序搬移前后的内存布局。
图5-1
异常向量表和异常处理程序搬移前后对比
当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±
32M)写入指令码。
由于内核启动时中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成bvector_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,所以中断入口点为“b
vector_irq+stubs_offset”,其中减去irq_PC是由汇编器在编译时完成的。
vector_irq处理函数
在分析vector_irq处理函数之前,先了解一下当一个异常或中断导致处理器模式改变时,ARM处理器内核的处理流程如下图所示:
中断刚发生时,处理器处于irq模式。
在__stubs_start和__stubs_end之间找到vector_irq处理函数的定义vector_stubirq,IRQ_MODE,4,其中vector_stub是一个宏(在arch/arm/kernel/entry_armv.S中定义),为了分析更直观,我们将vector_stub宏展开如下:
1./*
2.
*
Interruptdispatcher
*/
vector_irq:
.if
sub
lr,
#4
@在中断发生时,lr指向最后执行的指令地址加上8。
只有在当前指令执行完毕后,才进入中断处理,所以返回地址应指向下一条指令,即(lr-4)处。
.endif
8.
9.@
@Saver0,
lr_<
exception>
(parentPC
and
spsr_<
11.
@
(parentCPSR
@
13.
stmia
sp,
{r0,
lr}
@保存r0,
lr到irq模式下的栈中
14.
mrs
spsr
15.
str
[sp,
#8]
@保存spsr到irq模式下的栈中
16.
17.
18.
@Prepare
for
SVC32mode.
IRQsremaindisabled.
19.
20.
r0,
cpsr
21.
eor
#(
IRQ_MODE^SVC_MODE
@设置成SVC模式,但未切换
22.
msr
spsr_cxsf,
r0@保存到spsr_irq中
23.
24.
25.
@thebranchtablemustimmediatelyfollow
this
code
26.
27.
#0x0f@lr存储着上一个处理器模式的cpsr值,lr
=
lr&
0x0f取出用于判断发生中断前是用户态还是核心态的信息,该值用于下面跳转表的索引。
28.
mov
sp@将irq模式下的sp保存到r0,作为参数传递给即将调用的__irq_usr或__irq_svc
29.
[pc,
lsl
#2]
@pc指向当前执行指令地址加8,即跳转表的基址。
lr作为索引,由于是4字节对齐,所以lr
lr
<
2.
30.
movs
lr@branchtohandler
in
SVCmode
31.
@当mov指令后加“s”且目标寄存器为pc时,当前模式下的spsr会被复制到cpsr,从而完成模式切换(从irq模式切换到svc模式)并且跳转到pc指向的指令继续执行
32.ENDPROC(vector_irq
33.
34.
.long
__irq_usr
@0
(USR_26/USR_32
35.
__irq_invalid
@1
(FIQ_26/FIQ_32
36.
@2
(IRQ_26/IRQ_32
37.
__irq_svc
@3
(SVC_26/SVC_32
38.
@4
39.
@5
40.
@6
41.
@7
42.
@8
43.
@9
44.
@a
45.
@b
46.
@c
47.
@d
48.
@e
49.
@f
__irq_usr
如果发生中断前处于用户态则进入__irq_usr,其定义如下(arch/arm/kernel/entry_armv.S):
1..align
2.__irq_usr:
usr_entry@保存中断上下文,稍后分析
kuser_cmpxchg_check
5.#ifdefCONFIG_TRACE_IRQFLAGS
bl
trace_hardirqs_off
7.#endif
get_thread_infotsk@获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk(r9)(在entry-header.S中定义)
9.#ifdefCONFIG_PREEMPT@如果定义了抢占,增加抢占数值
r8,
[tsk,
#TI_PREEMPT]
@获取preempt计数器值
add
r7,
#1
@preempt加1,标识禁止抢占
@将加1后的结果写入进程内核栈的变量中
13.#endif
irq_handler@调用中断处理程序,稍后分析
15.#ifdefCONFIG_PREEMPT
16.
@获取preempt计数器值
@将preempt恢复到中断前的值
teq
r7@比较中断前后preempt是否相等
strne
[r0,
-r0]
@如果不等,则产生异常(向地址0写入数据)?
20.#endif
21.#ifdefCONFIG_TRACE_IRQFLAGS
trace_hardirqs_on
23.#endif
why,
#0@r8=0
ret_to_user@中断处理完成,恢复中断上下文并返回中断产生的位置,稍后分析
UNWIND(.fnend
27.ENDPROC(__irq_usr
宏定义usr_entry(保护上下文到栈
上面代码中的usr_entry是一个宏定义,主要用于保护上下文到栈中:
1..macro
usr_entry
UNWIND(.fnstart
UNWIND(.cantunwind
@dontunwindtheuserspace
#S_FRAME_SIZE@ATPCS中,堆栈被定义为递减式满堆栈,所以首先让sp向下移动#S_FRAME_SIZE(pt_regs结构体size),准备向栈中存放数据。
此处的sp是svc模式下的栈指针。
stmib
{r1
-
r12}
6.
ldmia
r3}
#S_PC
@here
interlockavoidance
r4,
#-1
"
10.
r1,
[sp]
@savethe
real"
r0copied
@fromtheexceptionstack
13.
@Wearenowreadytofill
theremainingblanksonthestack:
@r2
alreadyfixedup
correctreturn/restart
@r3
@r4
orig_r0
(seept_regsdefinition
ptrace.h
@Also,
separatelysavesp_usr
lr_usr
23.
{r2
r4}
stmdb
{sp,
lr}^@将user模式下的sp和lr保存到svc模式的栈中
25.
@Enablethealignmenttrap
while
kernelmode
alignment_trapr0
30.
32.
@ClearFPtomarkthefirststackframe
33.
zero_fp
.endm
上面的这段代码主要是在填充结构体pt_regs,在include/asm/ptrace.h中定义:
1.struct
pt_regs{
longuregs[18];
3.};
4.
5.#defineARM_cpsr
uregs[16]
6.#defineARM_pc
uregs[15]
7.#defineARM_lr
uregs[14]
8.#defineARM_sp
uregs[13]
9.#defineARM_ip
uregs[12]
10.#defineARM_fp
uregs[11]
11.#defineARM_r10
uregs[10]
12.#defineARM_r9
uregs[9]
13.#defineARM_r8
uregs[8]
14.#defineARM_r7
uregs[7]
15.#defineARM_r6
uregs[6]
16.#defineARM_r5
uregs[5]
17.#defineARM_r4
uregs[4]
18.#defineARM_r3
uregs[3]
19.#defineARM_r2
uregs[2]
20.#defineARM_r1
uregs[1]
21.#defineARM_r0
uregs[0]
22.#defineARM_ORIG_r0
uregs[17]
usr_entry宏填充pt_regs结构体的过程如图5-2所示,先将r1~r12保存到ARM_r1~ARM_ip(绿色部分),然后将产生中断时的r0寄存器内容保存到ARM_r0(蓝色部分),接下来将产生中断时的下一条指令地址lr_irq、spsr_irq和r4保存到ARM_pc、ARM_cpsr和ARM_ORIG_r0(红色部分),最后将用户模式下的sp和lr保存到ARM_sp
和ARM_lr
中。
图5-2usr_entry宏填充pt_regs结构体
__irq_svc
如果发生中断前处于核心态则进入__irq_svc,其定义如下(arch/arm/kernel/entry_armv.S):
2.__irq_svc:
svc_entry@保存中断上下文
8.#ifdefCONFIG_PREEMPT
get_thread_infotsk
14.
16.#ifdefCONFIG_PREEMPT
@恢复中断前的preempt计数器
#TI_FLAGS]
@获取flags
#0
@判断preempt是否等于0
movne
@如果preempt不等于0,r0=0
tst
#_TIF_NEED_RESCHED@将r0与#_TIF_NEED_RESCHED做“与操作”
blne
svc_preempt@如果不等于0,说明发生内核抢占,需要重新调度。
24.
#S_PSR]
@irqsarealreadydisabled
r0
27.#ifdefCONFIG_TRACE_IRQFLAGS
#PSR_I_BIT
bleq
30.#endif
svc_exitr4
@恢复中断上下文,稍后分析。
33.ENDPROC(__irq_svc
宏定义svc_entry(保护中断上下文到栈
其中svc_entry是一个宏定义,主要用于保护中断上下文到栈中。
svc_entry主要是在当前堆栈上分配一个pt_regs结构,把r0-r15以及cpsr等保存到这个结构中,在进入irq_handler时,sp指向pt_regs底端:
svc_entry,
stack_hole=0
UNWIND(.save{r0
pc}
#(S_FRAME_SIZE
\stack_hole
SPFIX(
bicne
r5,
#S_SP
addne
lr
18.
@r0
sp_svc
@r1
lr_svc
{r0
svc_entry宏填充pt_regs结构体的过程如图5-2所示,先将r1~r12保存到ARM_r1~ARM_ip(绿色部分),然后将产生中断时的r0寄存器内容保存到ARM_r0(蓝色部分),由于是在