Linux内核源码分析--内核启动之zImage自解压过程

时间:2021-02-14 16:47:40

 

参考:

http://blog.chinaunix.net/uid-20543672-id-3018233.html

Linux内核编译流程分析

linux2.6内核启动分析--李枝果(不看是你的损失^_^)

文档下载地址:

http://files.cnblogs.com/pengdonglin137/Linux%E5%86%85%E6%A0%B8%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90--%E5%86%85%E6%A0%B8%E5%90%AF%E5%8A%A8%E4%B9%8BzImage%E8%87%AA%E8%A7%A3%E5%8E%8B%E8%BF%87%E7%A8%8B.rar

关于内核自解压完毕后,执行start_kernel的分析,参见:

http://www.cnblogs.com/pengdonglin137/p/3632698.html

 

内核版本:3.0.8

相关文件: 

arch/arm/boot/compressed/head.S

arch/arm/boot/compressed/vmlinux.lds

arch/arm/boot/compressed/piggy.gzip

 

这里仅对内核自解压进行简要分析,详细的分析可以阅读参考博客文档。

zImage来历

 

顶层vmlinux ---->

    arch/arm/boot/Image --->

         arch/arm/boot/compressed/piggy.gz --->

                   arch/arm/boot/compressed/vmlinux --->

                                              arch/arm/boot/zImage

如果要分析zImage的反汇编反汇编文件,可将arch/arm/boot/compressed/vmlinux进行反汇编,

arm-linux-xxx-objdump –d vmlinux > vmlinux.dis

对顶层的vmlinux反汇编得到的是未压缩的内核的反汇编文件,这个vmlinux才是真正的Linux内核。

 

piggy.gz压缩文件的特点

 

gzip -f -9 < Image > piggy.gz

 

在piggy.gz的结尾四个字节表示的是 Image 镜像的大小,并且是以小端格式存放的。下面我们验证一下:

Linux内核源码分析--内核启动之zImage自解压过程

可以看到,Image的大小是6806148B,十六进制值就是67DA84,接下来看看piggy.gz的结尾:

Linux内核源码分析--内核启动之zImage自解压过程

Linux内核源码分析--内核启动之zImage自解压过程

可以看到,确实是将0x67DA84以小端的格式存放在了piggy.gz的结尾四字节中了。

vmlinux.lds

   1:  /*
   2:   *  linux/arch/arm/boot/compressed/vmlinux.lds.in
   3:   *
   4:   *  Copyright (C) 2000 Russell King
   5:   *
   6:   * This program is free software; you can redistribute it and/or modify
   7:   * it under the terms of the GNU General Public License version 2 as
   8:   * published by the Free Software Foundation.
   9:   */
  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:    _got_start = .;
  47:    .got            : { *(.got) }
  48:    _got_end = .;
  49:    .got.plt        : { *(.got.plt) }
  50:    _edata = .;
  51:   
  52:    . = ALIGN(8);
  53:    __bss_start = .;
  54:    .bss            : { *(.bss) }
  55:    _end = .;
  56:   
  57:    . = ALIGN(8);        /* the stack must be 64-bit aligned */
  58:    .stack        : { *(.stack) }
  59:   
  60:    .stab 0        : { *(.stab) }
  61:    .stabstr 0        : { *(.stabstr) }
  62:    .stab.excl 0        : { *(.stab.excl) }
  63:    .stab.exclstr 0    : { *(.stab.exclstr) }
  64:    .stab.index 0        : { *(.stab.index) }
  65:    .stab.indexstr 0    : { *(.stab.indexstr) }
  66:    .comment 0        : { *(.comment) }
  67:  }
  68:   

 

arch/arm/boot/compressed/head.S

   1:  .section ".start", #alloc, #execinstr
   2:  /*
   3:  * 清理不同的调用约定
   4:  */
   5:  .align
   6:  .arm @ 启动总是进入ARM状态
   7:  start:
   8:  .type start,#function
   9:  .rept 7
  10:  mov r0, r0
  11:  .endr
  12:  ARM( mov r0, r0 )
  13:  ARM( b 1f )
  14:  THUMB( adr r12, BSYM(1f) )
  15:  THUMB( bx r12 )
  16:  .word 0x016f2818 @ 用于boot loader的魔数
  17:  .word start @ 加载/运行zImage的绝对地址(编译时确定), 在vmlinux.lds中可以看到,zImage的链接起始地址是0
  18:  .word _edata @ zImage结束地址,分析vmlinux.lds可以看到,_edata是 .got 段的结束地址,后面紧接的就是.bss段和.stack段
  19:  THUMB( .thumb )
  20:  1: mov r7, r1 @ 保存构架ID到r7(此前由bootloader放入r1)
  21:     mov r8, r2 @ 保存内核启动参数地址到r8(此前由bootloader放入r2)
  22:  #ifndef __ARM_ARCH_2__
  23:  /*
  24:  * 通过Angel调试器启动 - 必须进入 SVC模式且关闭FIQs/IRQs
  25:  * (numeric definitions from angel arm.h source).
  26:  * 如果进入时在user模式下,我们只需要做这些
  27:  */
  28:  mrs r2, cpsr @ 获取当前模式
  29:  tst r2, #3 @ 判断是否是user模式
  30:  bne not_angel
  31:  mov r0, #0x17 @ angel_SWIreason_EnterSVC
  32:  ARM( swi 0x123456 ) @ angel_SWI_ARM  swi会产生软中断,会跳入中断向量表,这个向量表用的是bootloader的,因为在head.S中并没有建立新的向量表
  33:  THUMB( svc 0xab ) @ angel_SWI_THUMB
  34:  not_angel:
  35:  mrs r2, cpsr @ 关闭中断
  36:  orr r2, r2, #0xc0 @ 以保护调试器的运作 关闭IRQ和FIQ
  37:  msr cpsr_c, r2
  38:  #else
  39:  teqp pc, #0x0c000003@ 关闭中断(此外bootloader已设置模式为SVC)
  40:  #endif

 

GOT表是什么?

GOT(Global Offset Table)表中每一项都是本运行模块要引用的一个全局变量或函数的地址。可以用GOT表来间接引用全局变量、函数,也可以把GOT表的首地址作为一个基准,用相对于该基准的偏移量来引用静态变量、静态函数。

更多介绍请参考:

http://blog.sina.com.cn/s/blog_54f82cc201011oqy.html

http://blog.sina.com.cn/s/blog_54f82cc201011oqv.html

接着分析head.S

   1:  /*
   2:  * 注意一些缓存的刷新和其他事务可能需要在这里完成
   3:  * - is there an Angel SWI call for this?
   4:  */
   5:  /*
   6:  * 一些构架的特定代码可以在这里被连接器插入,
   7:  * 但是不应使用 r7(保存构架ID), r8(保存内核启动参数地址), and r9.
   8:  */
   9:  .text
  10:  /*
  11:  * 此处确定解压后的内核映像的绝对地址(物理地址),保存于r4
  12:  * 由于配置的不同可能有的结果
  13:  * (1)定义了CONFIG_AUTO_ZRELADDR
  14:  *      ZRELADDR是已解压内核最终存放的物理地址
  15:  *      如果AUTO_ZRELADDR被选择了, 这个地址将会在运行是确定:
  16:  *      将当pc值和0xf8000000做与操作,
  17:  *      并加上TEXT_OFFSET(内核最终存放的物理地址与内存起始的偏移)
  18:  *      这里假定zImage被放在内存开始的128MB内
  19:  * (2)没有定义CONFIG_AUTO_ZRELADDR
  20:  *      直接使用zreladdr(此值位于arch/arm/mach-xxx/Makefile.boot文件确定)
  21:  */
  22:  #ifdef CONFIG_AUTO_ZRELADDR
  23:  @ 确定内核映像地址
  24:  mov r4, pc
  25:  and r4, r4, #0xf8000000
  26:  add r4, r4, #TEXT_OFFSET
  27:  #else
  28:  ldr r4, =zreladdr                             以我的板子为例,它的值是0x80008000,即它的物理内存起始地址是0x80000000
  29:  #endif
  30:  bl cache_on /* 开启缓存(以及MMU) */         关于cache_on这部分,我在这里就不分析了
  31:  restart: adr r0, LC0   获取LC0的运行地址,而不是链接地址
  32:  ldmia r0, {r1, r2, r3, r6, r10, r11, r12}
       /*
            依次将LC0的链接地址放入r1中,bss段的链接起始和结束地址__bss_start和_end分别放入r2和r3中
            _edata的链接地址放入r6中,piggy.gz的倒数第四个字节的地址放入r10中, got表的起始和结束链接地址分别放入r11和r12中
      */

33: ldr sp, [r0, #28]   此时r0中存放的还是LC0的运行地址,所以加28后正好是LC0数组中的.L_user_stack_end的值(栈的结束地址),他在head.S的结尾定义

reloc_code_end:

        .align
        .section ".stack", "aw", %nobits
.L_user_stack:    .space    4096
.L_user_stack_end:

  34:  /*
  35:  * 我们可能运行在一个与编译时定义的不同地址上,
  36:  * 所以我们必须修正变量指针
  37:  */
  38:  sub r0, r0, r1 @ 计算偏移量                              编译地址与运行地址的差值,以此来修正其他符号的地址
  39:  add r6, r6, r0 @ 重新计算_edata                          获得_edata的实际运行地址
  40:  add r10, r10, r0 @ 重新获得压缩后的内核大小数据位置      获取Image大小存放的实际物理地址    
  41:  /*
  42:  * 内核编译系统将解压后的内核大小数据
  43:  * 以小端格式
  44:  * 附加在压缩数据的后面(其实是“gzip -f -9”命令的结果)
  45:  * 下面代码的作用是将解压后的内核大小数据正确地放入r9中(避免了大小端问题)
  46:  */
  47:  ldrb r9, [r10, #0]      以我们的例子,此时r9为0x84
  48:  ldrb lr, [r10, #1]                    lr为 0xDA
  49:  orr r9, r9, lr, lsl #8                r9为0xDA84
  50:  ldrb lr, [r10, #2]                    lr为0x67
  51:  ldrb r10, [r10, #3]                   r10是0x00
  52:  orr r9, r9, lr, lsl #16               r9为0x67DA84
  53:  orr r9, r9, r10, lsl #24              r9是0x0067DA84
  54:  /*
  55:  * 下面代码的作用是将正确的当前执行映像的结束地址放入r10
  56:  */
  57:  #ifndef CONFIG_ZBOOT_ROM
  58:  /* malloc 获取的内存空间位于重定向的栈指针之上 (64k max) */
  59:  add sp, sp, r0           使用r0修正sp,得到堆栈的实际结束物理地址,为什么是结束地址?因为栈是向下生长的
  60:  add r10, sp, #0x10000    执行这句之前sp中存放的是栈的结束地址,执行后,r10中存放的是堆空间的结束物理地址
  61:  #else
  62:  /*
  63:  * 如果定义了 ZBOOT_ROM, bss/stack 是非可重定位的,
  64:  * 但有些人依然可以将其放在RAM中运行,
  65:  * 这时我们可以参考 _edata.
  66:  */
  67:  mov r10, r6
  68:  #endif
  69:  /*
  70:  * 检测我们是否会发生自我覆盖的问题
  71:  * r4 = 解压后的内核起始地址(最终执行位置)   在我们的例子中r4就是0x80008000
  72:  * r9 = 解压后内核的大小                       即arch/arm/boot/Image的大小,0x67DA84
  73:  * r10 = 当前执行映像的结束地址, 包含了 bss/stack/malloc 空间(假设是非XIP执行的), 上面已经分析了r10存放的是堆空间的结束地址
  74:  * 我们的基本需求是:
  75:  * (若最终执行位置r4在当前映像之后)r4 - 16k 页目录 >= r10 -> OK
  76:  * (若最终执行位置r4在当前映像之前)r4 + 解压后的内核大小 <= 当前位置 (pc) -> OK
  77:  *  如果上面的条件不满足,就会自我覆盖,必须先搬运当前映像
  78:  */
  79:  add r10, r10, #16384
  80:  cmp r4, r10         @ 假设最终执行位置r4在当前映像之后
  81:  bhs wont_overwrite
  82:  add r10, r4, r9     @ 假设最终执行位置r4在当前映像之前
  83:  ARM( cmp r10, pc )  @ r10 = 解压后的内核结束地址注:这里的r4+r9计算出的大小并不包含栈和堆空间
  84:  THUMB( mov lr, pc )
  85:  THUMB( cmp r10, lr )
  86:  bls wont_overwrite

 

下面是LC0区域的数据内容:

   1:          .align    2
   2:          .type    LC0, #object
   3:  LC0:        .word    LC0            @ r1
   4:          .word    __bss_start        @ r2
   5:          .word    _end     @ r3
   6:          .word    _edata             @ r6
   7:          .word    input_data_end - 4    @ r10 (inflated size location)
   8:          .word    _got_start        @ r11
   9:          .word    _got_end    @ ip
  10:          .word    .L_user_stack_end    @ sp
  11:          .size    LC0, . - LC0

还记得piggy.gz的结尾四个字节的含义吗?他表示的是arch/arm/boot/Image的大小,在LC0区域中:

.word    input_data_end - 4    @ r10 (inflated size location)

的意思:

input_data_end 在piggy.gzip.S中定义:

   1:      .section .piggydata,#alloc
   2:      .globl    input_data
   3:  input_data:
   4:      .incbin    "arch/arm/boot/compressed/piggy.gzip"
   5:      .globl    input_data_end
   6:  input_data_end:

所以,"input_data_end – 4" 表示的是piggy.gz的倒数第四个字节的地址,也是为了便于获得Image的大小。

接着分析head.S:

   1:  /*
   2:  * 将当前的映像重定向到解压后的内核之后(会发生自我覆盖时才执行,否则就被跳过)
   3:  * r6 = _edata(已校正)
   4:  * r10 = 解压后的内核结束地址   对于我的例子: r10就是  0x80008000 + 0x67DA84(arch/arm/boot/Image的大小)
          这样可以保证一次重定位就可以达到预期目标
   5:  * 因为我们要把当前映像向后移动, 所以我们必须由后往前复制代码,
   6:  * 以防原数据和目标数据的重叠
   7:  */
   8:  /*
   9:  * 将解压后的内核结束地址r10扩展(reloc_code_end - restart),
  10:  * 并对齐到下一个256B边界。
  11:  * 这样避免了当搬运的偏移较小时的自我覆盖
  12:  */
  13:      add r10, r10, #((reloc_code_end - restart + 256) & ~255) 
  14:      bic r10, r10, #255
  15:  /* 获取需要搬运的当前映像的起始位置r5,并向下做32B对齐. */
  16:      adr r5, restart
  17:      bic r5, r5, #31
  18:      sub r9, r6, r5  @ _edata - restart(已向下对齐)= 需要搬运的大小
  19:      add r9, r9, #31
  20:      bic r9, r9, #31 @ 做32B对齐 ,r9 = 需要搬运的大小
  21:      add r6, r9, r5  @ r6 = 当前映像需要搬运的结束地址,由于r5和r9都已经对齐,所以r6也对齐了。
          /* 上面对齐以后,有利于后面的代码拷贝,而且对齐的同时又保证了拷贝代码的完整性,至少没有少拷贝代码*/
  22:      add r9, r9, r10 @ r9 = 当前映像搬运的目的地的结束地址
  23:  /* 搬运当前执行映像,不包含 bss/stack/malloc 空间*/
  24:      1: ldmdb r6!, {r0 - r3, r10 - r12, lr}
  25:      cmp r6, r5
  26:      stmdb r9!, {r0 - r3, r10 - r12, lr}
  27:      bhi 1b
  28:  /* 保存偏移量,用来修改sp和实现代码跳转 */
  29:      sub r6, r9, r6            求要搬移的代码的原始地址与目的地址的偏移量
  30:      #ifndef CONFIG_ZBOOT_ROM
  31:  /* cache_clean_flush 可能会使用栈,所以重定向sp指针 */
  32:      add sp, sp, r6            修正sp地址,修正后sp指向重定向后的代码的栈区的结束地址(栈向下生长),栈区后面紧跟的就是堆空间
  33:      #endif
  34:      bl cache_clean_flush @ 刷新缓存
  35:  /* 通过搬运的偏移和当前的实际 restart 地址来实现代码跳转*/
  36:      adr r0, BSYM(restart)    获得restart的运行地址
  37:      add r0, r0, r6           获得重定向后的代码的restart的物理地址
  38:      mov pc, r0               跳到重定向后的代码的restart处开始执行
  39:  /* 在上面的跳转之后,程序又从restart开始。
  40:  * 但这次在检查自我覆盖的时候,新的执行位置必然满足
  41:  * 最终执行位置r4在当前映像之前,r4 + 压缩后的内核大小 <= 当前位置 (pc)
  42:  * 所以必然直接跳到了下面的wont_overwrite执行
  43:  */
  44:  wont_overwrite:
  45:  /*
  46:  * 如果delta(当前映像地址与编译时的地址偏移)为0, 我们运行的地址就是编译时确定的地址.
  47:  * r0 = delta
  48:  * r2 = BSS start(编译值)
  49:  * r3 = BSS end(编译值)
  50:  * r4 = 内核最终运行的物理地址
  51:  * r7 = 构架ID(bootlodaer传递值)
  52:  * r8 = 内核启动参数指针(bootlodaer传递值)
  53:  * r11 = GOT start(编译值)
  54:  * r12 = GOT end(编译值)
  55:  * sp = stack pointer(修正值)
  56:  */
  57:      teq r0, #0 @测试delta值
  58:      beq not_relocated @如果delta为0,无须对GOT表项和BSS进行重定位
  59:      add r11, r11, r0 @重定位GOT start
  60:      add r12, r12, r0 @重定位GOT end
  61:      #ifndef CONFIG_ZBOOT_ROM
  62:  /*
  63:  * 如果内核配置 CONFIG_ZBOOT_ROM = n,
  64:  * 我们必须修正BSS段的指针
  65:  * 注意:sp已经被修正
  66:  */
  67:      add r2, r2, r0 @重定位BSS start
  68:      add r3, r3, r0 @重定位BSS end
  69:  /*    
  70:  * 重定位所有GOT表的入口项
  71:  */
  72:      1: ldr r1, [r11, #0] @ 重定位GOT表的入口项
  73:      add r1, r1, r0 @ 这个修正了 C 引用
  74:      str r1, [r11], #4
  75:      cmp r11, r12
  76:      blo 1b
  77:      #else
  78:  /*
  79:  * 重定位所有GOT表的入口项.
  80:  * 我们只重定向在(已重定向后)BSS段外的入口
  81:  */
  82:      1: ldr r1, [r11, #0] @ 重定位GOT表的入口项
  83:      cmp r1, r2 @ entry < bss_start ||
  84:      cmphs r3, r1 @ _end < entry table
  85:      addlo r1, r1, r0 @ 这个修正了 C 引用
  86:      str r1, [r11], #4
  87:      cmp r11, r12
  88:      blo 1b
  89:      #endif
  90:  /*
  91:  * 至此当前映像的搬运和调整已经完成
  92:  * 可以开始真正的工作的
  93:  */
  94:      not_relocated: mov r0, #0
  95:      1: str r0, [r2], #4 @ 清零 bss(初始化BSS段)
  96:      str r0, [r2], #4
  97:      str r0, [r2], #4
  98:      str r0, [r2], #4
  99:      cmp r2, r3
 100:      blo 1b
 101:  /*
 102:  * C运行时环境已经充分建立.
 103:  * 设置一些指针就可以解压内核了.
 104:  * r4 = 内核最终运行的物理地址
 105:  * r7 = 构架ID
 106:  * r8 = 内核启动参数指针
 107:  *
 108:  * 下面对r0~r3的配置是decompress_kernel函数对应参数
 109:  * r0 = 解压后的输出位置首地址
 110:  * r1 = 可用RAM空间首地址  即堆空间的起始地址
 111:  * r2 = 可用RAM空间结束地址  即堆空间的结束地址
 112:  * r3 = 构架ID
 113:  * 就是这个decompress_kernel(C函数)输出了"Uncompressing Linux..."
 114:  * 以及" done, booting the kernel.\n"
 115:  */
 116:      mov r0, r4
 117:      mov r1, sp @ malloc 获取的内存空间位于栈指针之上
 118:      add r2, sp, #0x10000 @ 64k max
 119:      mov r3, r7
 120:      bl decompress_kernel
 121:  /*
 122:  * decompress_kernel(misc.c)--调用-->
 123:  * do_decompress(decompress.c)--调用-->
 124:  * decompress(../../../../lib/decompress_xxxx.c根据压缩方式的配置而不同)
 125:  */
 126:  /*
 127:  * 以下是为跳入解压后的内核,再次做准备(恢复解压前的状态)
 128:  */
 129:      bl cache_clean_flush
 130:      bl cache_off@ 数据缓存必须关闭(内核的要求)
 131:      mov r0, #0 @ r0必须为0
 132:      mov r1, r7@ 恢复构架ID到r1
 133:      mov r2, r8 @ 恢复内核启动参数指针到r2
 134:      mov pc, r4 @ 跳入解压后的内核映像(Image)入口(arch/arm/kernel/head.S)

 

下面简要看一下解压缩程序的调用过程:

arch/arm/boot/compressed/misc.c

   1:  void
   2:  decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
   3:          unsigned long free_mem_ptr_end_p,
   4:          int arch_id)
   5:  {
   6:      int ret;
   7:   
   8:      output_data        = (unsigned char *)output_start;    /*解压地址*/
   9:      free_mem_ptr        = free_mem_ptr_p;               /*堆栈起始地址*/
  10:      free_mem_end_ptr    = free_mem_ptr_end_p;           /*堆栈结束地址*/
  11:      __machine_arch_type    = arch_id;                      /*uboot传入的machid*/
  12:   
  13:      arch_decomp_setup();
  14:   
  15:      putstr("Uncompressing Linux...");
  16:      ret = do_decompress(input_data, input_data_end - input_data,
  17:                  output_data, error);
  18:      if (ret)
  19:          error("decompressor returned an error");
  20:      else
  21:          putstr(" done, booting the kernel.\n");
  22:  }

 

arch/arm/boot/compressed/decompressed.c

   1:  #  define Tracec(c,x)
   2:  #  define Tracecv(c,x)
   3:  #endif
   4:   
   5:  #ifdef CONFIG_KERNEL_GZIP
   6:  #include "../../../../lib/decompress_inflate.c"
   7:  #endif
   8:   
   9:  #ifdef CONFIG_KERNEL_LZO
  10:  #include "../../../../lib/decompress_unlzo.c"
  11:  #endif
  12:   
  13:  #ifdef CONFIG_KERNEL_LZMA
  14:  #include "../../../../lib/decompress_unlzma.c"
  15:  #endif
  16:   
  17:  int 
do_decompress
(u8 *input, int len, u8 *output, void (*error)(char *x))
  18:  {
  19:      return 
decompress
(input, len, NULL, NULL, output, NULL, error);
  20:  }

 

lib/decompress_inflate.c

   1:  #ifdef STATIC
   2:  /* Pre-boot environment: included */
   3:   
   4:  /* prevent inclusion of _LINUX_KERNEL_H in pre-boot environment: lots
   5:   * errors about console_printk etc... on ARM */
   6:  #define _LINUX_KERNEL_H
   7:   
   8:  #include "zlib_inflate/inftrees.c"
   9:  #include "zlib_inflate/inffast.c"
  10:  #include "zlib_inflate/inflate.c"
  11:   
  12:  #else /* STATIC */
  13:  /* initramfs et al: linked */
  14:   
  15:  #include <linux/zutil.h>
  16:   
  17:  #include "zlib_inflate/inftrees.h"
  18:  #include "zlib_inflate/inffast.h"
  19:  #include "zlib_inflate/inflate.h"
  20:   
  21:  #include "zlib_inflate/infutil.h"
  22:   
  23:  #endif /* STATIC */
  24:   
  25:  #include <linux/decompress/mm.h>
  26:   
  27:  #define GZIP_IOBUF_SIZE (16*1024)
  28:   
  29:  static int INIT nofill(void *buffer, unsigned int len)
  30:  {
  31:      return -1;
  32:  }
  33:   
  34:  /* Included from initramfs et al code */
  35:  STATIC int INIT gunzip(unsigned char *buf, int len,
  36:                 int(*fill)(void*, unsigned int),
  37:                 int(*flush)(void*, unsigned int),
  38:                 unsigned char *out_buf,
  39:                 int *pos,
  40:                 void(*error)(char *x)) {
  41:      u8 *zbuf;
  42:      struct z_stream_s *strm;
  43:      int rc;
  44:      size_t out_len;
  45:   
  46:      rc = -1;
  47:      if (flush) {
  48:          out_len = 0x8000; /* 32 K */
  49:          out_buf = malloc(out_len);
  50:      } else {
  51:          out_len = 0x7fffffff; /* no limit */
  52:      }
  53:      if (!out_buf) {
  54:          error("Out of memory while allocating output buffer");
  55:          goto gunzip_nomem1;
  56:      }
  57:   
  58:      if (buf)
  59:          zbuf = buf;
  60:      else {
  61:          zbuf = malloc(GZIP_IOBUF_SIZE);
  62:          len = 0;
  63:      }
  64:      if (!zbuf) {
  65:          error("Out of memory while allocating input buffer");
  66:          goto gunzip_nomem2;
  67:      }
  68:   
  69:      strm = malloc(sizeof(*strm));
  70:      if (strm == NULL) {
  71:          error("Out of memory while allocating z_stream");
  72:          goto gunzip_nomem3;
  73:      }
  74:   
  75:      strm->workspace = malloc(flush ? zlib_inflate_workspacesize() :
  76:                   sizeof(struct inflate_state));
  77:      if (strm->workspace == NULL) {
  78:          error("Out of memory while allocating workspace");
  79:          goto gunzip_nomem4;
  80:      }
  81:   
  82:      if (!fill)
  83:          fill = nofill;
  84:   
  85:      if (len == 0)
  86:          len = fill(zbuf, GZIP_IOBUF_SIZE);
  87:   
  88:      /* verify the gzip header */
  89:      if (len < 10 ||
  90:         zbuf[0] != 0x1f || zbuf[1] != 0x8b || zbuf[2] != 0x08) {
  91:          if (pos)
  92:              *pos = 0;
  93:          error("Not a gzip file");
  94:          goto gunzip_5;
  95:      }
  96:   
  97:      /* skip over gzip header (1f,8b,08... 10 bytes total +
  98:       * possible asciz filename)
  99:       */
 100:      strm->next_in = zbuf + 10;
 101:      strm->avail_in = len - 10;
 102:      /* skip over asciz filename */
 103:      if (zbuf[3] & 0x8) {
 104:          do {
 105:              /*
 106:               * If the filename doesn't fit into the buffer,
 107:               * the file is very probably corrupt. Don't try
 108:               * to read more data.
 109:               */
 110:              if (strm->avail_in == 0) {
 111:                  error("header error");
 112:                  goto gunzip_5;
 113:              }
 114:              --strm->avail_in;
 115:          } while (*strm->next_in++);
 116:      }
 117:   
 118:      strm->next_out = out_buf;
 119:      strm->avail_out = out_len;
 120:   
 121:      rc = zlib_inflateInit2(strm, -MAX_WBITS);
 122:   
 123:      if (!flush) {
 124:          WS(strm)->inflate_state.wsize = 0;
 125:          WS(strm)->inflate_state.window = NULL;
 126:      }
 127:   
 128:      while (rc == Z_OK) {
 129:          if (strm->avail_in == 0) {
 130:              /* TODO: handle case where both pos and fill are set */
 131:              len = fill(zbuf, GZIP_IOBUF_SIZE);
 132:              if (len < 0) {
 133:                  rc = -1;
 134:                  error("read error");
 135:                  break;
 136:              }
 137:              strm->next_in = zbuf;
 138:              strm->avail_in = len;
 139:          }
 140:          rc = zlib_inflate(strm, 0);
 141:   
 142:          /* Write any data generated */
 143:          if (flush && strm->next_out > out_buf) {
 144:              int l = strm->next_out - out_buf;
 145:              if (l != flush(out_buf, l)) {
 146:                  rc = -1;
 147:                  error("write error");
 148:                  break;
 149:              }
 150:              strm->next_out = out_buf;
 151:              strm->avail_out = out_len;
 152:          }
 153:   
 154:          /* after Z_FINISH, only Z_STREAM_END is "we unpacked it all" */
 155:          if (rc == Z_STREAM_END) {
 156:              rc = 0;
 157:              break;
 158:          } else if (rc != Z_OK) {
 159:              error("uncompression error");
 160:              rc = -1;
 161:          }
 162:      }
 163:   
 164:      zlib_inflateEnd(strm);
 165:      if (pos)
 166:          /* add + 8 to skip over trailer */
 167:          *pos = strm->next_in - zbuf+8;
 168:   
 169:  gunzip_5:
 170:      free(strm->workspace);
 171:  gunzip_nomem4:
 172:      free(strm);
 173:  gunzip_nomem3:
 174:      if (!buf)
 175:          free(zbuf);
 176:  gunzip_nomem2:
 177:      if (flush)
 178:          free(out_buf);
 179:  gunzip_nomem1:
 180:      return rc; /* returns Z_OK (0) if successful */
 181:  }
 182:   
 183:  #define decompress gunzip

 

为了便于理解,可以参考下面一张图:

Linux内核源码分析--内核启动之zImage自解压过程

 

完!!