ARM linux解析之压缩内核zImage的启动过程
区别的,这就是为何前面要区分ZTEXTADDR和ZBSSADDR的原因了。
好了,再看下面这两句的区别,如果这个地方弄明白了,那么,下面的内容就会变得很简单,往下看:
restart:adr r0,LC0
addr0,pc,#0x10C
LC0:.wordLC0@ r1
dcd0x17C
故可知,当zImage加到0x28运行时,PC值为:0x28070,这个时候r0=0x2817C
而通过ldmiar0, {r1, r2, r3, r6, r10, r11, r12}加载内存值后,r1=0x17C
那么我们看一看这句:sub r0, r0, r1@ calculate the delta offset的值是多少?如下:
r0=0x2817C-0x17C =0x28
see~~~看出来什么没有,这个就是我们的加载zImage运行的内存起始地址,这个很重要,后面就要靠它知道我们当前的代码在哪里,搬移到哪里。然后再下一条指令把堆栈指针设置好。然后再把实际代码偏移量加在r6=_edata和(r10=input_data_end-4)上面,这就是实际的内存中的地址。好继续往下看:
ldrbr9, [r10, #0]
ldrblr, [r10, #1]
orr r9, r9, lr, lsl #8
ldrblr, [r10, #2]
ldrbr10, [r10, #3]
orr r9, r9, lr, lsl #16
orr r9, r9, r10, lsl #24
压缩的工具会把所压缩后的文件的最后加上用小端格式表示的4个字节的尾,用来存储所压内容的原始大小,这个信息很要,是我们后面分配空间,代码重定位的重要依据。这里为何要一个字节,一个字节地取,只因为要兼容ARM代码使用大端编译的情况,保证读取的正确无误。好了,再往下:
#ifndef CONFIG_ZBOOT_ROM
add sp, sp, r0
add r10, sp, #0x10
#else
movr10, r6
#endif
我们这里在RAM中运行,所以加上重定位SP的指针,加上偏移里,变成实际所在内存的堆栈指针地址。这里主要是为了后面的检查代码是否要进行重定位的时候所提前设置的,因为如果代码不重定位,就不会再设堆栈指针了,重定位的话,则还要重设一次。然后再在堆栈指针的上面开辟一块64K大小的空间,用于解压内核时的临时buffer。
再往下看:
add r10, r10, #16384//16K MMU页表也不能被覆盖哦,否则解压到复盖后,ARM就挂了。
cmpr4, r10
bhswont_overwrite
add r10, r4, r9
ARM(cmpr10, pc)
THUMB(movlr, pc)
THUMB(cmpr10, lr)
blswont_overwrite这段的检测有点绕人,两种情况都画个图看一下,如图.6所示,下面我们来看分析两种不会覆盖的情况:
第一种情况是加载运行的zImage在下,解压后内核运行地址zreladdr在上,这种情况如果最上面的64k的解压buffer不会覆盖到内核前的16k页表的话,就不用重定位代码跳到wont_overwrite执行。
第二种情况是加载运行的zImage在上,而解压的内核运行地址zreladdr在下面,只要最后解压后的内核的大小加上zreladdr不会到当前pc值,则也不会出现代码覆盖的情况,这种情况下,也不用重位代码,直接跳到wont_overwrite执行就可以了。
图.6内核的两种解压不要重定位的情况
可以我们一般加载的zImage的地址,和最后解压的zreladdr的地址是相同的,那么,就必然会发生代码覆盖的问题,这时候就要进行代码的自搬移和重定位。具体实现如下:
add r10, r10, #((reloc_code_end-restart+ 256) & ~255)
bicr10, r10, #255
adr r5, restart
bicr5, r5, #31
sub r9, r6, r5@ size to copy
add r9, r9, #31@ rounded up to a multiple
bicr9, r9, #31@ ... of 32 bytes
add r6, r9, r5
add r9, r9, r10
1:ldmdbr6!, {r0 - r3, r10 - r12, lr}
cmpr6, r5
stmdbr9!, {r0 - r3, r10 - r12, lr}
bhi 1b
这段代码就是实现代码的自搬移,最开始两句是取得所要搬移代码的大小,进行了256字节的对齐,注释上说了,为了避免偏移很小时产生自我覆盖(这个地方暂没有想明白,不过不影响下面分析)。这里还是再画个图表示一下整个搬移过程吧,以zImage加载地下和zreladdr都为0x28为例,其他的类似。
图.7 zImage的代码自搬移和内核解压的全程图解
图.7中我已经标好了序号,代码的自搬移和内核解的整个过程都在这里面下面一步步来分解:
①.首先计算要搬移的代码的.text段代码的大小,从restart开始,到reloc_code_end结束,这个就是剩下的.text段的内容,这段内容是接在打开cache的函数之后的。然后把这段代码搬到核实际解压后256字节对齐的边界,然后进行搬移,搬移时一次搬运32个字节,故存有搬移大小的r9寄存器进行了一下32字节对齐的扩展。
②.搬移完成后,会保存一下新旧代码间的offset值,存于r6中。再重新设置一下新的堆栈的地址,位置如图所示,代码如下:
subr6, r9, r6
#ifndef CONFIG_ZBOOT_ROM
addsp, sp, r6
#endif
③.然后进行cache的flush,因为马上要进行代码的跳转了,接着就计算新的restart在哪里,接着跳过去执行新
ARMlinux解析压缩内核zImage启动过 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)