Preface导言
我在这儿用一些篇幅来描绘一下arm体系结构下Linux中怎样来初始化中止向量表的,由于这个办法很具有通用性,我把它叫做代码大移动。您说搬代码谁不会阿,不便是复制吗,确实如此,可是复制也有技巧。复制很简单啦,其实便是memcpy,这不必提,我在这儿想说的是,你怎样把你的代码规划成能随意复制的,换句专业的术语,叫与方位无关的代码,拷到哪都能用。我曾经也用过相似的办法作发动,今日拿来说说。
Scenario 1榜首场景copy
咱们先看实践动作。代码的方位在arch/arm/traps.c中,kernel version: 2.6.27。这个是初始化部分的代码,setup_arch()->early_trap_init().了解初始化部分的朋友们或许见到过这段代码。
void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
&nsp;extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end – __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end – __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end – __stubs_start);
memcpy((void *)vectors + 0x1000 – kuser_sz, __kuser_helper_start, kuser_sz);
…
}
实践copy动作一望而知,便是两个memcpy(第三个实践上是复制一些其他东西,原理是相同的,这儿不提了). Copy的源是vectors,这个值是CONFIG_VECTORS_BASE,一般来讲,是0xffff0000,当然你能够依据硬件的设定自己制造这个值。把什么东西往那copy呢?榜首部分是从__vectors_start到__vectors_end之间的代码,第二部分是从__stubs_start到__stubs_end之间的代码,而第二部分是copy到vectors + 0x200开始的方位。也便是说,两部分之间的间隔是0x200,即512个字节。
咱们来看__vectors_start,__vectors_end,font face=”Times New Roman”>__stubs_start,__stubs_end到底是什么东西,只需知道它们在哪里界说的,就知道怎样回事了。
Scenario 2第二场景主角闪亮上台
它们埋伏在arch/arm/kernel/entry-armv.S中,这个文件是arm中各个形式的进口代码,了解arm的朋友们知道arm有几种形式,不知道的自己查查,不说了。咱们取一个片断,和咱们的论述相关的部分。为了让咱们看得更清楚,我删掉了部分代码和注释,把骨干凸显出来。有爱好的朋友能够检查源代码,研讨悉数,里边仍是比较有内在的。
.globl__stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stubirq, IRQ_MODE, 4
//请留意这儿:vector_stub是一个宏,打开后是一块代码,下面是个跳转表,咱们将代码结//构打开,大致是这样的结构: (后边的vector_stubdabt, ABT_MODE, 8等打开进程全相同,在此略过不提)
// ——————————– begin打开
.align5
vector_irq:
sublr, lr, 4
@ Save r0, lr_
@ (parent CPSR)
@
stmiasp, {r0, lr}@ save r0, lr
mrslr, spsr
strlr, [sp, #8]@ save spsr
@ Prepare for SVC32 mode.IRQs remain disabled.
@
mrsr0, cpsr
eorr0, r0, IRQ_MODE ^ SVC_MODE)
msrspsr_cxsf, r0
@ the branch table must immediately follow this code
@
andlr, lr, #0x0f
movr0, sp
ldrlr, [pc, lr, lsl #2]
movspc, lr@ branch to handler in SVC mode
// ——————————– end打开
.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@f
/*
* Data abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stubdabt, ABT_MODE, 8
.long__dabt_usr@0(USR_26 / USR_32)
.long__dabt_invalid@1(FIQ_26 / FIQ_32)
.long__dabt_invalid@2(IRQ_26 / IRQ_32)
.long__dabt_svc@3(SVC_26 / SVC_32)
。。。
.long__dabt_invalid@f
/*
* Prefetch abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stubpabt, ABT_MODE, 4
.long__pabt_usr@0 (USR_26 / USR_32)
.long__pabt_invalid@1 (FIQ_26 / FIQ_32)
.long__pabt_invalid@2 (IRQ_26 / IRQ_32)
.long__pabt_svc@3 (SVC_26 / SVC_32)
。。。
.long__pabt_invalid@f
/*
* Undef instr entry dispatcher
* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/
vector_stubund, UND_MODE
.long__und_usr@0 (USR_26 / USR_32)
.long__und_invalid@1 (FIQ_26 / FIQ_32)
.long__und_invalid@2 (IRQ_26 / IRQ_32)
.long__und_svc@3 (SVC_26 / SVC_32)
。。。
.long__und_invalid@f
.align5
vector_fiq:
disable_fiq
subspc, lr, #4
vector_addrexcptn:
bvector_addrexcptn
/*
* We group all the following data together to optimise
* for CPUs with separate I & D caches.
*/
.align5
.LCvswi:
.wordvector_swi
.globl__stubs_end
__stubs_end:
.equstubs_offset, __vectors_start + 0x200 – __stubs_start
.globl__vectors_start
__vectors_start:
swiSYS_ERROR0
bvector_und + stubs_offset
ldrpc, .LCvswi + stubs_offset
bvector_pabt + stubs_offset
bvector_dabt + stubs_offset
bvector_addrexcptn + stubs_offset
bvector_irq + stubs_offset
bvector_fiq + stubs_offset
.globl__vectors_end
__vectors_end:
为了让咱们看得更清,我把代码的结构再次简化成这样:
.globl__stubs_start
__stubs_start:
.align5
vector_irq:
[code part]//打开代码
[jump table part]//地址跳转表
。。。
.align5
vector_dabt:
[code part]
[jump table part]
。。。
.align5
vector_ pabt:
[code part]
[jump table part]
。。。
.align5
vector_und:
[code part]
[jump table part]
。。。
.align5
vector_fiq:
。。。
.globl__stubs_end
__stubs_end:
.globl__vectors_start
__vectors_start:
swiSYS_ERROR0
bvector_und + stubs_offset
ldrpc, .LCvswi + stubs_offset
bvector_pabt + stubs_offset
bvector_dabt + stubs_offset
bvector_addrexcptn + stubs_offset
bvector_irq + stubs_offset
bvector_fiq + stubs_offset
.globl__vectors_end
__vectors_end:
在这儿我不花过多的篇幅去解说代码的意思,这不是本文的意图,只需你把结构看清,就到达意图了。但我会花点时刻研讨一下打开代码部分(蓝色)的特征,这部分代码是与方位无关的代码,咱们略微研讨一下,它为什么会这么写。
.align5
vector_irq:
[code part]//打开代码
[jump table part]//地址跳转表
。。。
首要这部分代码大致都是相同的结构,前面是一些代码,后边跟着一个跳转表。跳转表里边界说了一些地址。咱们截取这部分看
。。。
@ the branch table must immediately follow this code
@
andlr, lr, #0x0f(1)// lr中当时存储了上一个状况寄存器的值,对后几位做与,
//便是取在中止前处在用户态仍是核心态,这个值用作跳
//转表的索引
movr0, sp(2)//用做他用,sp值当榜首个参数传给后边函数
ldrlr, [pc, lr, lsl #2](3)// pc是当时履行指令地址加8,即跳转表的基地址,lr是索引
//很好的技巧,取pc找当时地址什么时候都没错
movpc, lr@ branch to handler in SVC mode
[jump table]
.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)
真实的跳转在最终一句完结,咱们都看得很清楚。跳到哪里去了,假如中止曾经是svc形式,就会跳到__irq_svc。咱们发现这儿不会直接用b(bl,bx等)个,
ü一是b跳转后边是个偏移,而这个偏移是有约束的,不能太大
ü二是b跳转后边的偏移你不知道在代码复制后仍是不是那个姿态,由于咱们要搬移代码,所以假如你不能确认搬移后的偏移不变,那你就用肯定地址,而上面的代码前三句便是算出肯定地址来,然后用肯定地址赋值给pc直接完结跳转。
这些都是一些技巧,总归你要留意的是写方位无关的代码时涉及到跳转部分,用b跳转仍是直接赋成肯定地址(经过跳转表完成),假如你不能确保搬移后的偏移共同,写这部分就要留意了,要用一些技巧的。
咱们能够去用gcc的-fPIC和-S选项汇编一个小的函数看看,fPIC便是与方位无关选项,信任编译过动态库的人都了解,看看它是怎样做的。你会发现殊途同归。
Scenario 3第三场景大搬移
我用一个章节来介绍大搬移的进程,以及一些在搬移中Linux呈现的问题及解决方案。我把整个的搬移进程做成一张图里,然后评论了一些技能细节。咱们看到这是一个巨大无比的图,咱们这章节的所论述的内容都在图里。
咱们将搬移前的代码安排称为Code/Load视图,由于这是代码中的(或image中的)安排状况,把搬移后的代码安排称为Exec视图,反映的是代码履行时代码在内存中的状况。我方才讲过了榜首场景的状况,忘了的回到榜首场景中去看,两个memcpy的履行进程在图中也有表明,便是蓝色和赤色的带箭头的虚线,这便是代码从code view到exec view的复制进程,一望而知,不必多说。
现在呈现了一个问题,便是咱们发现在__vector_start和__vector_end之间的代码有点奇怪,咱们再次摘到这儿来看:
.equstubs_offset, __vectors_start + 0x200 – __stubs_start
.globl__vectors_start
__vectors_start:
swiSYS_ERROR0
bvector_und + stubs_offset
ldrpc, .LCvswi + stubs_offset
bvector_pabt + stubs_offset
bvector_dabt + stubs_offset
bvector_addrexcptn + stubs_offset
bvector_irq + stubs_offset
bvector_fiq + stubs_offset
.globl__vectors_end
__vectors_end:
在第二个场景中咱们说过,这叫做方位无关的代码,由于要复制到其他当地。并且里边都是跳转指令。咱们发现了除了第三个行代码用了肯定地址进行了跳转,其它都是用的b跳转。举个比如,bvector_dabt + stubs_offset,(vector_dabt在__stubs_start和__stubs_end之间),假如你用b vector_dabt,这肯定是有问题的,由于copy之后exec view的安排(map)是不相同的,所以b后这个偏移就不对了。这儿面,咱们就要对这个偏移进行一次调整。Stubs_offset便是这个调整值,是能够核算出来的,具体的核算进程在图中讲得比较清楚,这儿不提了。咱们能够在图中看到具体的推导进程。
其实虽然ldrpc, .LCvswi + stubs_offset这条指令用的是肯定地址跳转,用得跳转表的办法,但找地址的进程也用到了这个技能。咱们看到
.align5
.LCvswi:
.wordvector_swi
.LCvswi这个方位存储的是一个地址,便是要跳到这个当地。.align 5的意思是32字节对齐,这个是确保cache line对齐的,不提了。在exec view中找这个地址,就得加上个offset.原理是相同的,由于.LCvswi在__stubs_start和__stubs_end之间,这个区域被搬移了,不能直接用这个标签地址了,vector_swi没有被搬移,所以能够直接用。
总结一下。我觉得我要讲的东西虽然是Linux中的技能细节,描绘确实是代码搬移进程原理和留意事项。其实更重要的是,咱们怎么把这一个进程倒过来,即在涉及到代码搬移的场合中怎么进行规划,怎么运用这些技能完成这一规划进程。你能够遵从这样的辅导过程:
1.画出那个大图来,按自己的要求确认Code view和Exec view,规划搬移区段和规划搬移的方位
2.写出要搬移的代码,运用方位无关的技能(上面说到的)进行编码和查验
3.用相似memcpy的代码进行搬移