linux內核啓動內核解壓過程分析

http://blog.chinaunix.net/uid-20672257-id-2891129.html

內核編譯完成後會生成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文件前建議先看看前面的Linux下的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;
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章