linux-2.6.38中断机制分析—中断入口代码分析
__vectors_start:
ARM( swi
SYS_ERROR0 )
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:
比如一旦发生中断就会跳转到:W(b)
vector_irq + stubs_offset
其中:.equ stubs_offset, __vectors_start +
0x200 - __stubs_start
之前有如下拷贝命令:
memcpy((void *)vectors,
__vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors +
0x200, __stubs_start, __stubs_end - __stubs_start);
上述拷贝是将中断向量表和中断向量表跳转的目的代码拷贝到新的位置(0xffff0000和0xffff0000+0x200处)
关于stubs_offset的取值是怎么来的,在网上搜了很多,但都差不多,各种解释让人难以理解,一方面是思维方式不同造成的,同时很多存在一些错误,以下给出本人的想法:
为什么要stubs_offset呢?
1.
对于B跳转指令的执行,由于是相对地址,即偏移地址,在重定位后,由于跳转指令所在位置与将要跳转到的目的位置的偏移量发生了变化,故要加一个偏移量
2.
对于W(ldr) pc, .LCvswi +
stubs_offset,为绝对地址跳转,跳转到的目的地址也并没有被重定位,故不会因此而需要偏移地址。之所以有偏移,是由于LCvswi地址被重定位了,定义如下
. LCvswi:
.word vector_swi
故也需要一个地址偏移
stubs_offset的计算:
首先我们要知道,上面重定位的两部分代码,在重定位前后,其每部分内部的相对位置是没有发生变化的,只是两部分之间的部分偏移发生了变化。如下图所示:
(irq_entry - _vectors_start)
= (irq_entry’ - _vectors_start’) =
V1
中断向量表
(vector_irq - _stubs_start) = (vector_irq’ - _stubs_start’) =
V2 目的代码
重定位前:
irq_entry = _vectors_start +
V1 IRQ向量表地址
vector_irq
= _stubs_start + V2
IRQ跳转目的地址
跳转地址与目的地址间的偏移(相对地址):
P1 = (vector_irq -
irq_entry) =(_stubs_start - _vectors_start)
+ ( V2 - V1)
重定位后:
irq_entry’ = _vectors_start’
+
V1 IRQ向量表地址
vector_irq’
= _stubs_start’ + V2
IRQ跳转目的地址
跳转地址与目的地址间的偏移(重定位后相对地址):
P2 = (vector_irq’ -
irq_entry’) =(_stubs_start’ - _vectors_start’)
+ ( V2 - V1)
由此可得出重定位后的相对地址和重定位前的相对地址变化量:
P = P2 – P1
= ((_stubs_start’ - _vectors_start’)
+ ( V2 - V1)) –
((_stubs_start -
_vectors_start)
+ ( V2 - V1))
= 0x200 -
(_stubs_start -
_vectors_start)
= __vectors_start + 0x200 -
__stubs_start
下面我们需要找到vector_irq的位置。
我们可以看到如下代码:
vector_stub irq, IRQ_MODE,
4
这其实是个宏:
.macro
vector_stub, name, mode, correction=0
.align
5
vector_\name:
.if
\correction
sub lr,
lr, #\correction
.endif
、
stmia sp,
{r0, lr}
mrs lr,
spsr
str lr,
[sp, #8]
mrs r0,
cpsr
eor r0,
r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr
spsr_cxsf, r0
and lr,
lr, #0x0f
THUMB( adr
r0, 1f )
THUMB( ldr
lr, [r0, lr, lsl #2] )
mov r0,
sp
ARM( ldr
lr, [pc, lr, lsl #2] )
movs pc,
lr
ENDPROC(vector_\name)
.align
2
1:
.endm
我们试着将其展开:
vector_irq:
stmia sp,
{r0, lr}
mrs lr,
spsr @lr=spsr
str lr,
[sp, #8]
mrs r0,
cpsr
eor r0,
r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr
spsr_cxsf, r0
and lr,
lr, #0x0f @取lr的最后四位,其标志进入中断之前处于什么模式
THUMB( adr
r0, 1f )
THUMB( ldr
lr, [r0, lr, lsl #2] )
mov r0,
sp
ARM( ldr
lr, [pc, lr, lsl #2] )
@lr=pc+lr*4
movs pc,
lr @pc=lr
ENDPROC(vector_irq)
.align
2
1:
.endm
接下来的代码是:
.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
会根据进入中断之前所在的模式不同而跳转到不同的位置,不如之前处于用户模式,则lr=0,所以会跳转到:
.long __irq_usr
处,我们就以这个为例继续分析:
__irq_usr
irq_handler
这是一个宏,我们看它的定义:
.macro
irq_handler
#ifdef
CONFIG_MULTI_IRQ_HANDLER
ldr r1,
=handle_arch_irq
mov r0,
sp
adr lr,
BSYM(9997f)
ldr pc,
[r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
由于没有定义:CONFIG_MULTI_IRQ_HANDLER,所以简化下就是执行:arch_irq_handler_default,这也是一个宏,我们看它的定义:
.macro
arch_irq_handler_default
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0,
r2, r6, lr
movne r1,
sp
adrne lr,
BSYM(1b)
bne
asm_do_IRQ @这就是我们的曙光了
9997:
.endm
接着就跳转到asm_do_IRQ处执行了,下一节继续!