内核编译完成后会生成zImage内核镜像文件。关于bootloader加载zImage到内核,并且跳转到zImage开始地址运行zImage的过程,相信大家都很容易理解。但对于zImage是如何解压的过程,就不是那么好理解了。本文将结合部分关键代码,讲解zImage的解压过程。
  先看看zImage的组成吧。在内核编译完成后会在arch/arm/boot/下生成zImage
在arch/arm/boot/Makefile中:
 56 $(obj)/zImage:  $(obj)/compressed/vmlinux FORCE
 57         $(call if_changed,objcopy)
 58         @echo '  Kernel: $@ is ready'
由此可见,zImage的是elf格式的arch/arm/boot/compressed/vmlinux二进制化得到的
在arch/arm/boot/compressed/Makefile中:
104 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o     \
105                 $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) FORCE
106         $(call if_changed,ld)
107         @:
108
109 $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
110         $(call if_changed,$(suffix_y))
111
112 $(obj)/piggy.$(suffix_y).o:  $(obj)/piggy.$(suffix_y) FORCE
  其中Image是由内核顶层目录下的vmlinux二进制化后得到的。注意:arch/arm/boot/compressed/vmlinux是位置无关的,这个有助于理解后面的代码。链接选项中有个 -fpic参数:
 79 EXTRA_CFLAGS  := -fpic -fno-builtin
在说-fpic参数前先说一下位置无关代码,位置无关代码主要是在访问全局变量和全局函数的时候采用了位置无关的重定位方法,既依赖GOT和PLT来重定位.普通的重定位方法需要修改代码段,比如偏移地址0x100处需要重定位,loader就修改代码段的0x100处的内容,通过查找重定位信息得到具体的值.这种方法需要修改代码段的内容,对于动态连接库来说,其初衷是让多个进程共享代码段,若对其进行写操作,就回引起COW,从而失去共享.
-fPIC选项告诉编绎器使用GOT和PLT的方法重定位,这两个都是数据段,因此避免了COW,真正实现了共享.如果不用-fPIC,动态连接库依然可以使用,但其重定位方法为一般方法,必然会引起COW.但也无所谓,除了性能在COW时稍微受些影响,其他也没啥
  总结一下zImage的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码(head.o misc.o),组成的。
  下面就要看内核的启动了,那么内核是从什么地方开始运行的呢?这个当然要看lds文件啦。zImage的生成经历了两次大的链接过程:一次是顶层vmlinux的生成,由arch/arm/kernel/vmlinux.lds(这个lds文件是由arch/arm/kernel/vmlinux.lds.S生成的)决定;另一次是arch/arm/boot/compressed/vmlinux的生成,是由arch/arm/boot/compressed/vmlinux.lds(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)决定。zImage的入口点应该由arch/arm/boot/compressed/vmlinux.lds决定。从中可以看出入口点为‘_start’,在分析lds文件前建议先看看前面的 一文
 10 OUTPUT_ARCH(arm)
 11 ENTRY(_start)
 12 SECTIONS
 13 {
 14   /DISCARD/ : {
 15     *(.ARM.exidx*)
 16     *(.ARM.extab*)
 17     /*
 18      * Discard any r/w data - this produces a link error if we have any,
 19      * which is required for PIC decompression.  Local data generates
 20      * GOTOFF relocations, which prevents it being relocated independently
 21      * of the text/got segments.
 22      */
 23     *(.data)
 24   }
 25
 26   . = 0;
 27   _text = .;
 28
 29   .text : {
 30     _start = .;
 31     *(.start)
。。。。。。。。。。。
 69 }
在arch/arm/boot/compressed/head.S中找到入口点.。也就是说文件arch/arm/boot/compressed/head.S是linux内核启动过程执行的第一个文件。
启动zImage内核时,u-boot调用boot_zImage函数(前面部分略过,从boot_zImage函数讲起),该函数完成以下工作:
1.       设置内核由nand flash复制到sdram中的地址:0x30008000;
2.       调用copy_kernel_img 函数复制内核到sdram;
3.       设置Image magic number;
4.       设置传递给内核的参数地址为0x30001000;
5.       设置机器码为193;
6.       最后调用call_linux函数,将控制权彻底交给内核。
当完成了上述工作后,内核开始启动,zImage内核的入口程序为:arch/arm/boot/compressed/head.S
我们现在来分析一下这个文件
 123                 .section ".start", #alloc, #execinstr
 124 /*
 125  * sort out different calling conventions
 126  */
 127                 .align
 128 start:
 129                 .type   start,#function //type指定start这个符号是函数类型
 130                 .rept   8  //重复8次 mov r0, r0,
 131                 mov     r0, r0  //空操作,让前面所取指令得以执行。
 132                 .endr
 133
 134                 b       1f  //跳转
/*
魔数0x016f2818是在bootloader中用于判断zImage的存在,
而zImage的判别的magic number为0x016f2818,这个也是内核和bootloader约定好的。
*/
 135                 .word   0x016f2818              @ Magic numbers to help the      loader
 136                 .word   start                   @ absolute load/run zImage      address
 137                 .word   _edata                  @ zImage end address
//r1和r2中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。
 138 1:              mov     r7, r1                  @ save architecture ID
 139                 mov     r8, r2                  @ save atags pointer
 140
/*
这也标志着u-boot将系统完全的交给了OS,bootloader生命终止。之后会读取
cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;
而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入
*/
 141 #ifndef __ARM_ARCH_2__
 142                 /*
 143                  * Booting from Angel - need to enter SVC mode and disable
 144                  * FIQs/IRQs (numeric definitions from angel arm.h source).
 145                  * We only do this if we were in user mode on entry.
 146                  */
 147                 mrs     r2, cpsr                @ get current mode
 148                 tst     r2, #3                  @ not user?
 149                 bne     not_angel
 150                 mov     r0, #0x17               @ angel_SWIreason_EnterSVC//0x17是angel_SWIreason_EnterSVC半主机操作
 151  ARM(           swi     0x123456        )       @ angel_SWI_ARM //0x123456是arm指令集的半主机操作编号
 152  THUMB(         svc     0xab            )       @ angel_SWI_THUMB
 153 not_angel:
 154                 mrs     r2, cpsr                @ turn off interrupts to
 155                 orr     r2, r2, #0xc0           @ prevent angel from runnin     g
 156                 msr     cpsr_c, r2      //这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ
 157 #else
 158                 teqp    pc, #0x0c000003         @ turn off interrupts
 159 #endif
 160
 161                 /*
 162                  * Note that some cache flushing and other stuff may
 163                  * be needed here - is there an Angel SWI call for this?
 164                  */
 165
 166                 /*
 167                  * some architecture specific code can be inserted
 168                  * by the linker here, but it should preserve r7, r8, and r     9.
 169                  */
 170
/*
LC0表是链接文件arch/arm/boot/compressed/vmlinux.lds(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)的各段入口。
 10 OUTPUT_ARCH(arm)
 11 ENTRY(_start)
 12 SECTIONS
 13 {
 14   /DISCARD/ : {
 15     *(.ARM.exidx*)
 16     *(.ARM.extab*)
 17     /*
 18      * Discard any r/w data - this produces a link error if we have any,
 19      * which is required for PIC decompression.  Local data generates
 20      * GOTOFF relocations, which prevents it being relocated independently
 21      * of the text/got segments.
 22      */
 23     *(.data)
 24   }
 25
 26   . = 0;
 27   _text = .;
 28
 29   .text : {
 30     _start = .;
 31     *(.start)
 32     *(.text)
 33     *(.text.*)
 34     *(.fixup)
 35     *(.gnu.warning)
 36     *(.rodata)
 37     *(.rodata.*)
 38     *(.glue_7)
 39     *(.glue_7t)
 40     *(.piggydata)
 41     . = ALIGN(4);
 42   }
 43
 44   _etext = .;
 45
 46   /* Assume size of decompressed image is 4x the compressed image */
 47   _image_size = (_etext - _text) * 4;
 48
 49   _got_start = .;
 50   .got                  : { *(.got) }
 51   _got_end = .;
 52   .got.plt              : { *(.got.plt) }
 53   _edata = .;
 54
 55   . = ALIGN(4);
 56   __bss_start = .;
 57   .bss                  : { *(.bss) }
 58   _end = .;
 59
 60   .stack (NOLOAD)       : { *(.stack) }
 61
 62   .stab 0               : { *(.stab) }
 63   .stabstr 0            : { *(.stabstr) }
 64   .stab.excl 0          : { *(.stab.excl) }
 65   .stab.exclstr 0       : { *(.stab.exclstr) }
 66   .stab.index 0         : { *(.stab.index) }
 67   .stab.indexstr 0      : { *(.stab.indexstr) }
 68   .comment 0            : { *(.comment) }
 69 }
展开如下表:
zImage在内存中的初始地址为0x30008000,那么这个地址是怎么来的?
查看2410的 datasheet ,发现内存映射的基址是0x3000 0000 ,那么0x30008000又是如何来的呢?
在内核文档 Document/arm/Booting 文件中有:
5. Calling the kernel image
---------------------------
Existing boot loaders:          MANDATORY
New boot loaders:               MANDATORY
There are two options for calling the kernel zImage.  If the zImage
is stored in flash, and is linked correctly to be run from flash,
then it is legal for the boot loader to call the zImage in flash
directly.
The zImage may also be placed in system RAM (at any location) and
called there.  Note that the kernel uses 16K of RAM below the image
to store page tables.  The recommended placement is 32KiB into RAM.
看来在 image 下面用了32K(0x8000)的空间存放内核页表,
0x30008000就是2410的内核在 RAM 中的启动地址,这个地址就是这么来的。所以后面zreladdr(内核运行地址)及bootloader中都定义相关的宏来定位到这个地址
1、初始状态
链接文件arch/arm/boot/compressed/vmlinux.lds中的连接地址都是位置无关的,即都是以0地址为偏移的。而此时内核已被bootloader搬移到了SDRAM中。链接地址应该加上这个偏移。
*/
 171                 .text
 172                 adr     r0, LC0 //指令adr是基于PC的值来获取标号LC0的地址的,LC0在后面第315行定义,由于内核已被搬移,标号LC0的地址不是以地址0为偏移的,这中间就存在一个固定的地址偏移,在s3c2410中是0x30008000.
 173                 ldmia   r0, {r1, r2, r3, r5, r6, r11, ip}
 174                 ldr     sp, [r0, #28]
 175 #ifdef CONFIG_AUTO_ZRELADDR
 176                 @ determine final kernel image address
 177                 and     r4, pc, #0xf8000000
 178                 add     r4, r4, #TEXT_OFFSET
 179 #else
 180                 ldr     r4, =zreladdr
/*zreladdr内核运行地址,相关定义如下:
arch/arm/boot/Makefile
 24 ZRELADDR    := $(zreladdr-y)
 25 PARAMS_PHYS := $(params_phys-y)
arch/arm/mach-s3c2410/Makefile.boot
  1 ifeq ($(CONFIG_PM_H1940),y)
  2         zreladdr-y              := 0x30108000
  3         params_phys-y   := 0x30100100
  4 else
  5         zreladdr-y              := 0x30008000
  6         params_phys-y   := 0x30000100
  7 endif
*/
 181 #endif
 182                 subs    r0, r0, r1              @ calculate the delta offse     t
//这里获得当前运行地址与链接地址的偏移量,存入r0中为0x30008000.如果不需要重定位,即内核没有进行过搬移,就跳转。如果内核代码是存于NANDflash中的是需要搬移的。
 183
 184                                                 @ if delta is zero, we are
 185                 beq     not_relocated           @ running at the address we
 186                                                 @ were linked at.
 187
 188                 /*
 189                  * We're running at a different address.  We need to fix
 190                  * up various pointers:
 191                  *   r5 - zImage base address (_start)
 192                  *   r6 - size of decompressed image
 193                  *   r11 - GOT start
 194                  *   ip - GOT end
 195                  */
 196                 add     r5, r5, r0  //修改内核映像基地址此时r5=0x30008000
 197                 add     r11, r11, r0 //修改got表的起始和结束位置
 198                 add     ip, ip, r0
 199
 200 #ifndef CONFIG_ZBOOT_ROM
 201                 /*
 202                  * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
 203                  * we need to fix up pointers into the BSS region.
 204                  *   r2 - BSS start
 205                  *   r3 - BSS end
 206                  *   sp - stack pointer
 207                  */
//S3C2410平台是需要一下调整的,将bss段以及堆栈的地址都进行调整
 208                 add     r2, r2, r0
 209                 add     r3, r3, r0
 210                 add     sp, sp, r0
 211
 212                 /*
 213                  * Relocate all entries in the GOT table.
 214                  *///修改GOT(全局偏移表)表。根据当前的运行地址,修正该表 
 215 1:              ldr     r1, [r11, #0]           @ relocate entries in the G     OT
 216                 add     r1, r1, r0              @ table.  This fixes up the
 217                 str     r1, [r11], #4           @ C references.
 218                 cmp     r11, ip
 219                 blo     1b
 220 #else
 221
 222                 /*
 223                  * Relocate entries in the GOT table.  We only relocate
 224                  * the entries that are outside the (relocated) BSS region.
 225                  *///S3C2410平台不会进入该分支,只对got表中在bss段以外的符号进行重定位
 226 1:              ldr     r1, [r11, #0]           @ relocate entries in the G     OT
 227                 cmp     r1, r2                  @ entry < bss_start ||
 228                 cmphs   r3, r1                  @ _end < entry
 229                 addlo   r1, r1, r0              @ table.  This fixes up the
 230                 str     r1, [r11], #4           @ C references.
 231                 cmp     r11, ip
 232                 blo     1b
 233 #endif
 234
//下面的代码,如果运行当前运行地址和链接地址相等,则不需进行重定位。直接清除bss段
 235 not_relocated:  mov     r0, #0
 236 1:              str     r0, [r2], #4     //清BSS段,所有的arm程序都需要做这些的
 237                 str     r0, [r2], #4
 238                 str     r0, [r2], #4
 239                 str     r0, [r2], #4
 240                 cmp     r2, r3
 241                 blo     1b
 242
 243                 /*
 244                  * The C runtime environment should now be setup
 245                  * sufficiently.  Turn the cache on, set up some
 246                  * pointers, and start decompressing.
 247                  */
 248                 bl      cache_on //打开cache
 249
 250                 mov     r1, sp                  @ malloc space above stack
 251                 add     r2, sp, #0x10000        @ 64k max 分配一段解压函数需要的内存缓冲,参见下图。
 252
 253 /*
 254  * Check to see if we will overwrite ourselves.
 255  *   r4 = final kernel address
 256  *   r5 = start of this image
 257  *   r6 = size of decompressed image
 258  *   r2 = end of malloc space (and therefore this image)
 259  * We basically want:
 260  *   r4 >= r2 -> OK
 261  *   r4 + image length <= r5 -> OK
 262  */
 263                 cmp     r4, r2  //r4为内核执行地址,此时为0X30008000,r2此时为用户栈定,即解压函数所需内存缓冲的开始处,显然r4 < r2所以不会跳转。
 264                 bhs     wont_overwrite
 265                 add     r0, r4, r6
 266                 cmp     r0, r5
//r5是内核映像的开始地址0X30008000,r6为内核映像大小,r4为解压后内核开始地址,此时为0X30008000,r5为解压前映存放的开始位置,此时也为0X30008000。下面的判断是,看解压后的内核是不是会覆盖未解压的映像。显然是覆盖的,所以是不会跳转的。注意:内核映像解压后不会超过解压前的4倍大小。
 267                 bls     wont_overwrite
 268
 269                 mov     r5, r2     @ decompress after malloc space//此时r2为解压函数缓冲区的尾部地址。
 270                 mov     r0, r5 //r0为映像解压后存放的起始地址,解压后的内核就紧接着存放在解压函数缓冲区的尾部。
 271                 mov     r3, r7 //r7中存放的是architecture ID,对于SMDK2410这个值为193; 
/*
解压函数是用C语言实现的在文件arch/arm/boot/compressed/misc.c中。
解压函数的是个参数是有r0~r3传入的。r0~r3的值在上面已初始化,再对照上面表格就很清楚了。
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
                unsigned long free_mem_ptr_end_p,
                int arch_id)
output_start:指解压后内核输出的起始位置,此时它的值参考上面的图表,紧接在解压缓冲区后;
free_mem_ptr_p:解压函数需要的内存缓冲开始地址;
free_mem_ptr_end_p:解压函数需要的内存缓冲结束地址,共64K;
arch_id :architecture ID,对于SMDK2410这个值为193; 
*/
 272                 bl      decompress_kernel
/*下图是head.S调用misc.c中的decompress_kernel刚解压完内核后,还没有进行重定位时的情况
解压完毕后,内核长度返回值存放在r0寄存器里。在内核末尾空出128字节的栈空间,并且使其长度128字节对齐。*/
 273
 274                 add     r0, r0, #127 + 128      @ alignment + stack
 275                 bic     r0, r0, #127            @ align the kernel length
 276 /*
 277  * r0     = 解压后内核的长度
 278  * r1-r3  = 没使用
 279  * r4     = 内核执行地址
 280  * r5     = decompressed kernel start解压后内核的起始地址,如上面初始化 mov r5, r2
 281  * r7     = architecture ID  处理器ID
 282  * r8     = atags pointer 标记列表地址
 283  * r9-r12,r14 = corrupted
 284  */
/*
上面只是将内核临时解压到了一个位置,下面还要将它重定位到0X30008000处。
标号reloc_start下面有一段重定位内核的程序。为了在内核重定位的过程中不至于将这段用于
重定位的代码给覆盖了,就先将这段用于内核重定位的代码搬到另一个地方,如下表。
*/
 285                 add     r1, r5, r0  //r1就是解压后内核代码的结束位置,下面就是将这段重定位代码搬移到r1地址处。
 286                 adr     r2, reloc_start //重定位代码起始地址
 287                 ldr     r3, LC1 //用于内核重定位的代码的长度
 288                 add     r3, r2, r3 //重定位代码的结束地址
 289 1:              ldmia   r2!, {r9 - r12, r14}    @ copy relocation code//将这段重定位代码搬移到r1地址处,如上表。
 290                 stmia   r1!, {r9 - r12, r14}
 291                 ldmia   r2!, {r9 - r12, r14}
 292                 stmia   r1!, {r9 - r12, r14}
 293                 cmp     r2, r3
 294                 blo     1b
 295                 mov     sp, r1
 296                 add     sp, sp, #128            @ relocate the stack//改变堆栈指针位置。
 297
 298                 bl      cache_clean_flush //搬移完成后刷新cache,因为代码地址变化了不能让cache再命中被内核覆盖的老地址。
 299  ARM(           add     pc, r5, r0              ) @ call relocation code//r0 + r5 就是被搬移后的内核重定位代码的开始位置,reloc_start。下面将会讲到。
 300  THUMB(         add     r12, r5, r0             )
 301  THUMB(         mov     pc, r12                 ) @ call relocation code
 302
 303 /*
 304  * We're not in danger of overwriting ourselves.  Do this the simple way.
 305  *
 306  * r4     = kernel execution address
 307  * r7     = architecture ID
 308  */
//如果内核映像没有被bootloader搬移过,上面程序就会跳到此处。
 309 wont_overwrite: mov     r0, r4
 310                 mov     r3, r7
 311                 bl      decompress_kernel
 312                 b       call_kernel
 313
 314                 .align  2
 315                 .type   LC0, #object
//这个表与文件arch/arm/kernel/vmlinux.lds.S(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)
 316 LC0:            .word   LC0                     @ r1
 317                 .word   __bss_start             @ r2
 318                 .word   _end                    @ r3
 319                 .word   _start                  @ r5
 320                 .word   _image_size             @ r6
 321                 .word   _got_start              @ r11
 322                 .word   _got_end                @ ip
 323                 .word   user_stack_end          @ sp
 324 LC1:            .word   reloc_end - reloc_start
 325                 .size   LC0, . - LC0
下面代码是将解压后的内核代码重定位。过程见下图
 //下面代码就是实现将解压后的内核代码搬到0X30008000处
 546 /*
 547  * All code following this line is relocatable.  It is relocated by
 548  * the above code to the end of the decompressed kernel image and
 549  * executed there.  During this time, we have no stacks.
 550  *
 551  * r0     = decompressed kernel length
 552  * r1-r3  = unused
 553  * r4     = kernel execution address
 554  * r5     = decompressed kernel start
 555  * r7     = architecture ID
 556  * r8     = atags pointer
 557  * r9-r12,r14 = corrupted
 558  */
 559                 .align  5
 560 reloc_start:    add     r9, r5, r0 // r0 + r5就是解压后内核代码的结束位置加128字节栈空间。
 561                 sub     r9, r9, #128            @ do not copy the stack
 562                 debug_reloc_start
 563                 mov     r1, r4 //r4为内核执行地址,即为0X30008000。
 564 1:
 565                 .rept   4  //将解压后的内核搬到r1处,即0X30008000处。
 566                 ldmia   r5!, {r0, r2, r3, r10 - r12, r14}       @ relocate      kernel
 567                 stmia   r1!, {r0, r2, r3, r10 - r12, r14}
 568                 .endr
 569
 570                 cmp     r5, r9
 571                 blo     1b
 572                 mov     sp, r1
 573                 add     sp, sp, #128            @ relocate the stack
 574                 debug_reloc_end
 575
//清除并关闭cache,清零r0,将machine ID存入r1,atags指针存入r2,再跳入0x30008000执行真正的内核Image
 576 call_kernel:    bl      cache_clean_flush
 577                 bl      cache_off
 578                 mov     r0, #0                  @ must be zero
 579                 mov     r1, r7                  @ restore architecture numb     er
 580                 mov     r2, r8                  @ restore atags pointer
 581                 mov     pc, r4                  @ call kernel
 582
此时内核解压已经完成。内核启动要执行的第二个文件就是arch/arm/kernel/head.S文件。
总结一下head.S会做些什么样的工作:
1、对于各种Arm CPU的DEBUG输出设定,通过定义宏来统一操作;
2、设置kernel开始和结束地址,保存architecture ID;
3、如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断
4、分析LC0结构delta offset,判断是否需要重载内核地址(r0存入偏移量,判断r0是否为零)。
5、需要重载内核地址,将r0的偏移量加到BSS region和GOT table中的每一项。对于位置无关的代码,程序是通过GOT表访问全局数据目标的,也就是说GOT表中中记录的是全局数据目标的绝对地址,所以其中的每一项也需要重载。
6、清空bss堆栈空间r2-r3l,建立C程序运行需要的缓存
7、这时r2是缓存的结束地址,r4是kernel的最后执行地址,r5是kernel境象文件的开始地址
8、用文件misc.c的函数decompress_kernel(),解压内核于缓存结束的地方(r2地址之后)。
  为了更清楚的了解解压的动态过程。我们用图表的方式描述下代码的搬运解压过程。然后再针对中间的一些关键过程阐述。
  zImage在内存中的初始地址为0x30008000(这个地址由bootloader决定,位置不固定)u-boot会将zImage镜像copy到sdram的0x30008000位置处。此时为初始状态,这里称为状态1。
1、初始状态
|.text   |  0x30008000开始,包含piggydata段(即压缩的内核段)
|. got   |   
|. data  |     
|.bss    |  
|.stack  |   4K大小
2、  head.S调用misc.c中的decompress_kernel刚解压完内核后,内存中的各段位置如下,状态2 
.text              | 0x30008000开始,包含piggydata段(即压缩的内核段)
. got              |
. data             |
.bss               |
.stack             |4K大小
解压函数所需缓冲区 |   64K大小
解压后的内核代码   | 小于4M
3、当如果head.S中有代码搬运工作时,即出现overwrite时,内存中的各段位置如下,此时会将head.S中的部分代码重定位,状态3
.text                        |0x30008000开始,包含piggydata段(即压缩的内核段)
. got                        |
. data                       |
.bss                         |
.stack                       | 4K大小
解压函数所需缓冲区           | 64K大小
解压后的内核代码             | 小于4M
head.S中的部分重定位代码代码  |   reloc_start至reloc_end
4、跳转到重定位后的reloc_start处,由reloc_start至reloc_end的代码复制解压后的内核代码到0x30008000处,并调用call_kernel跳转到0x30008000处执行。
解压后的内核  |  0x30008000开始
在通过head.S了解了动态过程后,我们可能会有几个问题:
  问题1:zImage是如何知道自己最后的运行地址是0x30008000的?
    问题2:调用decompress_kernel函数时,其4个参数是什么值及物理含义?
    问题3:解压函数是如何确定代码中压缩内核位置的? 
  先回答第1个问题
zImage在内存中的初始地址为0x30008000,那么这个地址是怎么来的?
查看2410的 datasheet ,发现内存映射的基址是0x3000 0000 ,那么0x30008000又是如何来的呢?
在内核文档 Document/arm/Booting 文件中有:
5. Calling the kernel image
---------------------------
Existing boot loaders:          MANDATORY
New boot loaders:               MANDATORY
There are two options for calling the kernel zImage.  If the zImage
is stored in flash, and is linked correctly to be run from flash,
then it is legal for the boot loader to call the zImage in flash
directly.
The zImage may also be placed in system RAM (at any location) and
called there.  Note that the kernel uses 16K of RAM below the image
to store page tables.  The recommended placement is 32KiB into RAM.
看来在 image 下面用了32K(0x8000)的空间存放内核页表,
再来先看看TEXT_OFFSET(
内核
在RAM中的
起始位置相对于
RAM起始地址偏移。值为0x00008000)它在
./arch/arm/Makefile中定义
118 textofs-y       := 0x00008000
222 TEXT_OFFSET := $(textofs-y)
此处,定义了Image的偏移地址。
0x30008000就是2410的内核在 RAM 中的启动地址,这个地址就是这么来的。所以后面zreladdr(内核运行地址)及bootloader中都定义相关的宏来定位到这个地址
在arch/arm/boot/Makefile文件中 ZRELADDR  := $(zreladdr-y)
arch/arm/boot/Makefile
 24 ZRELADDR    := $(zreladdr-y)
 25 PARAMS_PHYS := $(params_phys-y)
在arch/arm/mach-s3c2410/Makefile.boot中zreladdr-y := 0x30008000这个就是zImage的运行地址了,
arch/arm/mach-s3c2410/Makefile.boot
  1 ifeq ($(CONFIG_PM_H1940),y)
  2         zreladdr-y              := 0x30108000
  3         params_phys-y   := 0x30100100
  4 else
  5         zreladdr-y              := 0x30008000
  6         params_phys-y   := 0x30000100
  7 endif
在arch/arm/boot/compressed/head.S中有           
 180    ldr     r4, =zreladdr 内核就是用这种方式让代码知道最终运行的位置的 
此处定义了内核的物理地址和
u-boot
传递参数的地址,也就是加载地址。此处要和
u-boot
一致。
  接下来再回答第2个问题
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
                unsigned long free_mem_ptr_end_p,
                int arch_id)
output_start:指解压后内核输出的起始位置,此时它的值参考上面的图表,紧接在解压缓冲区后;
free_mem_ptr_p:解压函数需要的内存缓冲开始地址;
ulg free_mem_ptr_end_p:解压函数需要的内存缓冲结束地址,共64K;
arch_id :architecture ID,对于SMDK2410这个值为193; 
  最后回答第3个问题
  首先看看piggy.o是如何生成的,
打开arch/arm/boot/Makefile文件:
 49 $(obj)/Image: vmlinux FORCE
 50         $(call if_changed,objcopy)
 51         @echo '  Kernel: $@ is ready'
 56 $(obj)/zImage:  $(obj)/compressed/vmlinux FORCE
 57         $(call if_changed,objcopy)
 58         @echo '  Kernel: $@ is ready'
可以看出,zImage是vmlinux通过objcopy后生成的;打开arch/arm/boot/compressed/Makefie文件:
 63 suffix_$(CONFIG_KERNEL_GZIP) = gzip
 64 suffix_$(CONFIG_KERNEL_LZO)  = lzo
 65 suffix_$(CONFIG_KERNEL_LZMA) = lzma
 67 targets       := vmlinux vmlinux.lds \
 68                  piggy.$(suffix_y) piggy.$(suffix_y).o \
 69                  font.o font.c head.o misc.o $(OBJS)
./arch/arm/boot/Makefile:30:targets := Image zImage xipImage bootpImage uImage
104 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o     \
105                 $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) FORCE
106         $(call if_changed,ld)
107         @:
109 $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
110         $(call if_changed,$(suffix_y))
111
112 $(obj)/piggy.$(suffix_y).o:  $(obj)/piggy.$(suffix_y) FORCE
可以看出,当配置内核为GZIP方式时,zImage的压缩方式使用gzip。现在可以清楚的明白zImage和vmlinux的关系了,一句话总结一下:zImage就是vmlinux通过objcopy、gzip压缩后,得到的内核,其头部是由head.S misc.c组成的自解压代码。
而u-boot所一直追捧的uImage格式,只是在zImage的前面加上了40Byte的内核头部信息,由于本文主要讲解zImage,此处暂不对uImage进行过多的分析。这里内核默认采用gzip方式,所以第112行也就是 
piggy.gzip
$(obj)/piggy.gzip.o: $(obj)/piggy.gzip FORCE                     
piggy.gzip.o是由piggy.S生成的,咱们看看piggy.gzip.S的内容: 
        .section .piggydata,#alloc
        .globl  input_data
input_data:
        .incbin "arch/arm/boot/compressed/piggy.gzip"
        .globl  input_data_end
input_data_end:
  再看看misc.c中decompress_kernel函数吧,它将调用
    do_decompress(input_data, input_data_end - input_data,
            output_data, error);
解压内核。发现什么没?这里的input_data不正是piggy.S里的input_data吗?这个时候应该明白内核是怎样确定piggy.gz在zImage中的位置了吧。
最后说明一点的是do_decompress()在arch/arm/boot/compressed/decompresse.c中定义,它将调用decompress,进而调用bunzip2()或unlzma()来获取压缩内核代码。具体解压过程以后有时间再分析。
unsigned long
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
        unsigned long free_mem_ptr_end_p,
        int arch_id)
{
    unsigned char *tmp;
    output_data        = (unsigned char *)output_start;
    free_mem_ptr        = free_mem_ptr_p;
    free_mem_end_ptr    = free_mem_ptr_end_p;
    __machine_arch_type    = arch_id;
    arch_decomp_setup();
    tmp = (unsigned char *) (((unsigned long)input_data_end) - 4);
    output_ptr = get_unaligned_le32(tmp);
    putstr("Uncompressing Linux...");
    do_decompress(input_data, input_data_end - input_data,
            output_data, error);
    putstr(" done, booting the kernel.\n");
    return output_ptr;
}
decompress_kernel()先调用arch_decomp_setup()进行设置,初始化,实现的功能是检测CPU型号、使能看门狗(如果在配置内核时配置的前提下)和串口。然后使用在打印出信息“Uncompressing Linux...”后,之后调用do_decompress函数进行解压,do_decompress函数是在arch/arm/boot/compressed/Decompress.c文件中的。将内核放于指定的位置。最后打印出信息" done, booting the kernel." do_decompress函数其代码如下。
#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif
#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif
#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif
void do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{
    decompress(input, len, NULL, NULL, output, NULL, error);
}
当配置内核时选中GZIP方式,就会#include “../../../../lib/decompress_inflate.c”。文件最后一行为:
#define decompress gunzip
即:最后调用 gunzip函数对zImage进行解压。
当完成所有解压任务后,又将跳转回head.S文件中,执行call_kernel,将启动真正的Image.
arch/arm/plat-samsung/include/plat/uncompress.h
typedef unsigned int upf_t;    /* cannot include linux/serial_core.h */
/* uart setup */
static unsigned int fifo_mask;
static unsigned int fifo_max;
/* forward declerations */
static void arch_detect_cpu(void);
/* defines for UART registers */
#include <plat/regs-serial.h>
#include <plat/regs-watchdog.h>
/* working in physical space... */
#undef S3C2410_WDOGREG
#define S3C2410_WDOGREG(x) ((S3C24XX_PA_WATCHDOG + (x)))
/* how many bytes we allow into the FIFO at a time in FIFO mode */
#define FIFO_MAX     (14)
#define uart_base S3C_PA_UART + (S3C_UART_OFFSET * CONFIG_S3C_LOWLEVEL_UART_PORT)
static __inline__ void
uart_wr(unsigned int reg, unsigned int val)
{
    volatile unsigned int *ptr;
    ptr = (volatile unsigned int *)(reg + uart_base);
    *ptr = val;
}
static __inline__ unsigned int
uart_rd(unsigned int reg)
{
    volatile unsigned int *ptr;
    ptr = (volatile unsigned int *)(reg + uart_base);
    return *ptr;
}
/* we can deal with the case the UARTs are being run
 * in FIFO mode, so that we don't hold up our execution
 * waiting for tx to happen...
*/
static void putc(int ch)
{
    if (uart_rd(S3C2410_UFCON) & S3C2410_UFCON_FIFOMODE) {
        int level;
        while (1) {
            level = uart_rd(S3C2410_UFSTAT);
            level &= fifo_mask;
            if (level < fifo_max)
                break;
        }
    } else {
        /* not using fifos */
        while ((uart_rd(S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE) != S3C2410_UTRSTAT_TXE)
            barrier();
    }
    /* write byte to transmission register */
    uart_wr(S3C2410_UTXH, ch);
}
static inline void flush(void)
{
}
#define __raw_writel(d, ad)            \
    do {                            \
        *((volatile unsigned int __force *)(ad)) = (d); \
    } while (0)
/* CONFIG_S3C_BOOT_WATCHDOG
 *
 * Simple boot-time watchdog setup, to reboot the system if there is
 * any problem with the boot process
*/
#ifdef CONFIG_S3C_BOOT_WATCHDOG
#define WDOG_COUNT (0xff00)
static inline void arch_decomp_wdog(void)
{
    __raw_writel(WDOG_COUNT, S3C2410_WTCNT);
}
static void arch_decomp_wdog_start(void)
{
    __raw_writel(WDOG_COUNT, S3C2410_WTDAT);
    __raw_writel(WDOG_COUNT, S3C2410_WTCNT);
    __raw_writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128 | S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x80), S3C2410_WTCON);
}
#else
#define arch_decomp_wdog_start()
#define arch_decomp_wdog()
#endif
#ifdef CONFIG_S3C_BOOT_ERROR_RESET
static void arch_decomp_error(const char *x)
{
    putstr("\n\n");
    putstr(x);
    putstr("\n\n -- System resetting\n");
    __raw_writel(0x4000, S3C2410_WTDAT);
    __raw_writel(0x4000, S3C2410_WTCNT);
    __raw_writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128 | S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x40), S3C2410_WTCON);
    while(1);
}
#define arch_error arch_decomp_error
#endif
#ifdef CONFIG_S3C_BOOT_UART_FORCE_FIFO
static inline void arch_enable_uart_fifo(void)
{
    u32 fifocon = uart_rd(S3C2410_UFCON);
    if (!(fifocon & S3C2410_UFCON_FIFOMODE)) {
        fifocon |= S3C2410_UFCON_RESETBOTH;
        uart_wr(S3C2410_UFCON, fifocon);
        /* wait for fifo reset to complete */
        while (1) {
            fifocon = uart_rd(S3C2410_UFCON);
            if (!(fifocon & S3C2410_UFCON_RESETBOTH))
                break;
        }
    }
}
#else
#define arch_enable_uart_fifo() do { } while(0)
#endif
static void
arch_decomp_setup(void)
{
    /* we may need to setup the uart(s) here if we are not running
     * on an BAST... the BAST will have left the uarts configured
     * after calling linux.
     */
    arch_detect_cpu();
    arch_decomp_wdog_start();
    /* Enable the UART FIFOs if they where not enabled and our
     * configuration says we should turn them on.
     */
    arch_enable_uart_fifo();
}
arch/arm/mach-s3c2410/include/mach/uncompress.h
static inline int is_arm926(void)
{
    unsigned int cpuid;
    asm volatile ("mrc p15, 0, %0, c1, c0, 0" : "=r" (cpuid));
    return ((cpuid & 0xff0) == 0x260);
}
static void arch_detect_cpu(void)
{
    unsigned int cpuid;
    cpuid = *((volatile unsigned int *)S3C2410_GSTATUS1);
    cpuid &= S3C2410_GSTATUS1_IDMASK;
    if (is_arm926() || cpuid == S3C2410_GSTATUS1_2440 ||
        cpuid == S3C2410_GSTATUS1_2442 ||
        cpuid == S3C2410_GSTATUS1_2416 ||
        cpuid == S3C2410_GSTATUS1_2450) {
        fifo_mask = S3C2440_UFSTAT_TXMASK;
        fifo_max = 63 << S3C2440_UFSTAT_TXSHIFT;
    } else {
        fifo_mask = S3C2410_UFSTAT_TXMASK;
        fifo_max = 15 << S3C2410_UFSTAT_TXSHIFT;
    }
}