• linux内核启动内核解压过程分析【转】


    转自:https://blog.csdn.net/hlzs_01/article/details/39369901

    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. ");
        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(" ");
        putstr(x);
        putstr(" -- System resetting ");

        __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;
        }
    }
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    GDC2017:Framegraph-extensible-rendering-architecture-in-frostbite
    17072802(UE4的批量Import)
    16011301(统计指令数影响耗时)
    16080401(面向摄像机的Instance模型)
    UE4编译_201701
    17020701(AnimDynamic继续)
    Python过滤utf8mb4无效字符
    Windows下安装MySQL-python
    使用Pycharm更新Github项目(到源项目)
    python异常:error: command 'gcc' failed: No such file or directory
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13856633.html
Copyright © 2020-2023  润新知