linux内核启动内核解压过程分析

    内核编译完成后会生成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刚解压完内核后,还没有进行重定位时的情况
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值