星期六, 4月 12, 2008

arm linux 启动流程之 解压内核

引用自: http://blog.csdn.net/dansen_xu/archive/2007/08/13/1740738.aspx

Author-------Dansen-----xzd2734@163.com

从后往前看下编译生成zImage的过程,我们可以找到程序的入口还是那个很重要
链接文件,找到它,生成zImage所在的目录是kernelarcharmbootcompressed
Make过程为....ld -p -X -T vmlinux.lds head.o misc.o head-s3c2410.o piggy.o
libgcc.o -o vmlinux
然后是用二进制工具objcopy把vmlinux制作成可执行的二进制映像文件zImage
这样在我们就去kernelarcharmbootcompressed目录下去找到vmlinux.lds文件
如果没有编译就不会有这个文件,因为它也是在编译过程生成的,由同一目录下的
vmlinux.lds.in生成,打开这个文件
ENTRY(_start)
SECTIONS
{
. = LOAD_ADDR;
_load_addr = .;

. = TEXT_START;
_text = .;

.text : {
_start = .;
*(.start)
*(.text)
........
入口是_start,而且入口就直接定义在这个文件中了
入口直接接着.start段,所以程序开始是从.start段开始执行的
如果看看vmlinux.lds的生成过程就应该能找到LOAD_ADDR和TEXT_START的值
实际上这两个值是由其他两个变量赋给的 ZRELADDR 和 ZTEXTADDR
在kernelarcharmbootMakefile中我们可以找到这两个变量的值
ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif
所以
LOAD_ADDR = 0x30008000
TEXT_START = 0x30008000
看一下vmlinux.lds吧
ENTRY(_start)
SECTIONS
{
. = 0x30008000;
_load_addr = .;

. = 0;
_text = .;
显然LOAD_ADDR被赋值了0x30008000
看一下TEXT_START怎么成0了,我想这应该是一个偏移吧,偏移是0
所以它还是0x30008000
接着下来就从head.s来开始看代码吧
.section ".start", #alloc, #execinstr
/*
* sort out different calling conventions
*/
.align
start:
.type start,#function
.rept 8
mov r0, r0
.endr

b 1f
.word 0x016f2818 @ Magic numbers to help the loader
.word start @ absolute load/run zImage address
.word _edata @ zImage end address
1: mov r7, r1 @ save architecture ID
这里一定就是程序的入口了,一般汇编程序的含义就看看英文注释就是了
有一个要注意的地方,不是一个汇编文件就是属于一个段的,不是说先执行完了
head.s再去执行head-s3c2410.s,还是要注意链接的段,显然head.s
不一会就开始了另一个段.text
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
subs r0, r0, r1 @ calculate the delta offset
而我们的head-s3c2410.s呢
.section ".start", #alloc, #execinstr
__S3C2410_start:
bic r2, pc, #0x1f
add r3, r2, #0x4000 @ 16 kb is quite enough...
还是属于.start段的,所以顺序执行下来时先执行head-s3c2410.s,然后再去执行
.text段。head-s3c2410.s主要是cpu的一些初始化工作。接着下来我们会需要把内核
接压缩,先说说为什么吧。还是注意到上面生成zImage的文件中有一个piggy.o,往上
追寻可以看到是piggy.o由那个真正的内核vmlinux生成的,这个vmlinux才是启动后一直在
运行的内核,原本很大,压缩以后可以方便地放在flash中,当然其实不压缩跳到它的
入口也就可以运行了。解压的内核是准备从LOAD_ADDR = 0x30008000开始的4M空间,会覆盖
我们的当前运行的代码,那样就先把内核解压到我们这个zImage+分配堆栈0x10000的最后
cmp r4, r2 //r4 是LOAD_ADDR=0x30008000
bhs wont_overwrite //r2 是当前代码的最底部 这里当然不会跳转
add r0, r4, #4096*1024 @ 4MB largest kernel size
cmp r0, r5 //r5 也是0x30008000
bls wont_overwrite //不会跳转

mov r5, r2 //r2是(user_stack+4096)在zImage的最后+0x10000
mov r0, r5
mov r3, r7 //machine type
bl decompress_kernel
有了r5,r0,r7作为参数,就可以调用misc.c中的decompress_kernel函数进行解压缩了
这个函数调用的gunzip函数时gcc的库函数,所以在源码中找不到的
解压在r5开始的地方,函数返回的是r0解压得到的长度。这时候我们需要对代码经行调整
add r1, r5, r0 @ end of decompressed kernel
adr r2, reloc_start
ldr r3, LC1 //LC1: .word reloc_end - reloc_start
add r3, r2, r3
1: ldmia r2!, {r8 - r13} @ copy relocation code
stmia r1!, {r8 - r13}
ldmia r2!, {r8 - r13}
stmia r1!, {r8 - r13}
cmp r2, r3 //这里就把从reloc_start到reloc_end这段我们需要的代码放到了
blo 1b //解压内核的最后,而在下面我们会将zImage都覆盖掉
bl cache_clean_flush
add pc, r5, r0 //调到调整后的reloc_start,在decompressed kernel后
reloc_start: add r8, r5, r0 //r5解压内核开始的地方 r0解压内核的长度
debug_reloc_start
mov r1, r4 //r4=0x30008000
1:
.rept 4
ldmia r5!, {r0, r2, r3, r9 - r13} @ relocate kernel
stmia r1!, {r0, r2, r3, r9 - r13}
.endr

cmp r5, r8

blo 1b //这样就又把解压的真正内核移到了0x30008000处

call_kernel: bl cache_clean_flush

bl cache_off

mov r0, #0

mov r1, r7 @ restore architecture number

mov pc, r4 @ call kernel

上面就是跳到0x30008000这里去执行真正的内核了吧


0 意見: