红联Linux门户
Linux帮助

基于Linux2.6.38.8内核zImage文件的自解压详解

发布时间:2014-11-23 22:02:41来源:linux网站作者:wavemcu

Linux内核编译完成后会生成zImage内核镜像文件。zImage是如何解压的呢?本文将结合关键代码,讲解zImage的解压过程。还是先来看看zImage的组成吧。在内核编译完成后会在arch/arm/boot/下生成zImage。


在arch/arm/boot/Makefile中,如下代码:

# arch/arm/boot/Makefile 

# This file is included by the global makefile so that you can add your own 
# architecture-specific flags and dependencies. 

# This file is subject to the terms and conditions of the GNU General Public 
# License.  See the file "COPYING" in the main directory of this archive 
# for more details. 

# Copyright (C) 1995-2002 Russell King 

MKIMAGE   := $(srctree)/scripts/mkuboot.sh 

ifneq ($(MACHINE),) 
include $(srctree)/$(MACHINE)/Makefile.boot 
endif 

# Note: the following conditions must always be true: 
#   ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET) 
#   PARAMS_PHYS must be within 4MB of ZRELADDR 
#   INITRD_PHYS must be in RAM 
ZRELADDR:= $(zreladdr-y) 
PARAMS_PHYS := $(params_phys-y) 
INITRD_PHYS := $(initrd_phys-y) 

export ZRELADDR INITRD_PHYS PARAMS_PHYS 

targets := Image zImage xipImage bootpImage uImage 

ifeq ($(CONFIG_XIP_KERNEL),y) 

$(obj)/xipImage: vmlinux FORCE 
$(call if_changed,objcopy) 
@echo '  Kernel: $@ is ready (physical address: $(CONFIG_XIP_PHYS_ADDR))' 

$(obj)/Image $(obj)/zImage: FORCE 
@echo 'Kernel configured for XIP (CONFIG_XIP_KERNEL=y)' 
@echo 'Only the xipImage target is available in this case' 
@false 

else 

$(obj)/xipImage: FORCE 
@echo 'Kernel not configured for XIP (CONFIG_XIP_KERNEL!=y)' 
@false 

$(obj)/Image: vmlinux FORCE 
$(call if_changed,objcopy) 
@echo '  Kernel: $@ is ready' 

$(obj)/compressed/vmlinux: $(obj)/Image FORCE 
$(Q)$(MAKE) $(build)=$(obj)/compressed $@ 

$(obj)/zImage:$(obj)/compressed/vmlinux FORCE 
$(call if_changed,objcopy) 
@echo '  Kernel: $@ is ready' 

endif 

quiet_cmd_uimage = UIMAGE  $@ 
cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A arm -O linux -T kernel \ 
-C none -a $(LOADADDR) -e $(STARTADDR) \ 
-n 'Linux-$(KERNELRELEASE)' -d {1}lt; $@ 

ifeq ($(CONFIG_ZBOOT_ROM),y) 
$(obj)/uImage: LOADADDR=$(CONFIG_ZBOOT_ROM_TEXT) 
else 
$(obj)/uImage: LOADADDR=$(ZRELADDR) 
endif 

$(obj)/uImage: STARTADDR=$(LOADADDR) 

$(obj)/uImage:$(obj)/zImage FORCE 
$(call if_changed,uimage) 
@echo '  Image $@ is ready' 

$(obj)/bootp/bootp: $(obj)/zImage initrd FORCE 
$(Q)$(MAKE) $(build)=$(obj)/bootp $@ 
@: 

$(obj)/bootpImage: $(obj)/bootp/bootp FORCE 
$(call if_changed,objcopy) 
@echo '  Kernel: $@ is ready' 

PHONY += initrd FORCE 
initrd: 
@test "$(INITRD_PHYS)" != "" || \ 
(echo This machine does not support INITRD; exit -1) 
@test "$(INITRD)" != "" || \ 
(echo You must specify INITRD; exit -1) 

install: $(obj)/Image 
$(CONFIG_SHELL) $(srctree)/$(src)/install.sh $(KERNELRELEASE) \ 
$(obj)/Image System.map "$(INSTALL_PATH)" 

zinstall: $(obj)/zImage 
$(CONFIG_SHELL) $(srctree)/$(src)/install.sh $(KERNELRELEASE) \ 
$(obj)/zImage System.map "$(INSTALL_PATH)" 

zi: 
$(CONFIG_SHELL) $(srctree)/$(src)/install.sh $(KERNELRELEASE) \ 
$(obj)/zImage System.map "$(INSTALL_PATH)" 

i: 
$(CONFIG_SHELL) $(srctree)/$(src)/install.sh $(KERNELRELEASE) \ 
$(obj)/Image System.map "$(INSTALL_PATH)" 

subdir- := bootp compressed


我们将上面的部分内容抓取出来,独立来分析,这样就不会被其它的代码干扰到,见下面的代码:

$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
@echo '  Kernel: $@ is ready'
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo '  Kernel: $@ is ready'
由此可见,zImage是由arch/arm/boot/compressed/vmlinux二进制化得到的,与此同时在arch/armboot/compressed/Makefile中:
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \
$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) FORCE
$(call if_changed,ld)
@:

$(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
$(call if_changed,$(suffix_y))

$(obj)/piggy.$(suffix_y).o:$(obj)/piggy.$(suffix_y) FORCE
其中Image是由内核顶层目录下的vmlinux二进制化后得到的,其中在arch/armboot/compressed/Makefile链接选项中有个 –fpic参数:
EXTRA_CFLAGS  := -fpic -fno-builtin
EXTRA_AFLAGS  := -Wa,-march=all


总结一下zImage的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码(head.o misc.o),组成的。见下面的代码:


# linux/arch/arm/boot/compressed/Makefile 

# create a compressed vmlinuz image from the original vmlinux 

...................... 
...................... 

# We now have a PIC decompressor implementation.  Decompressors running 
# from RAM should not define ZTEXTADDR.  Decompressors running directly 
# from ROM or Flash must define ZTEXTADDR (preferably via the config) 
# FIXME: Previous assignment to ztextaddr-y is lost here. See SHARK 
ifeq ($(CONFIG_ZBOOT_ROM),y) 
ZTEXTADDR   := $(CONFIG_ZBOOT_ROM_TEXT) 
ZBSSADDR:= $(CONFIG_ZBOOT_ROM_BSS) 
else 
ZTEXTADDR   := 0 
ZBSSADDR:= ALIGN(8) 
endif 

SEDFLAGS= s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/ 

suffix_$(CONFIG_KERNEL_GZIP) = gzip 
suffix_$(CONFIG_KERNEL_LZO)  = lzo 
suffix_$(CONFIG_KERNEL_LZMA) = lzma 

targets := vmlinux vmlinux.lds \ 
piggy.$(suffix_y) piggy.$(suffix_y).o \ 
font.o font.c head.o misc.o $(OBJS) 

# Make sure files are removed during clean 
extra-y += piggy.gzip piggy.lzo piggy.lzma lib1funcs.S 

targets := vmlinux vmlinux.lds \
piggy.$(suffix_y) piggy.$(suffix_y).o \
font.o font.c head.o misc.o $(OBJS)


那么内核是从什么地方开始运行的呢?这个要看lds文件。其实zImage的生成经历了两次链接过程:一是顶层vmlinux的生成,在arch/arm/boot/vmlinux.lds(这个lds文件是由arch/arm/kernel/vmlinux.lds.S生成的)中;另一次是arch/arm/boot/compressed/vmlinux的生成,在arch/arm/boot/compressed/vmlinux.lds.in中。zImage的入口点应该由arch/arm/boot/compressed/vmlinux.lds.in决定。从中可以看出入口点为‘_start’

/* 
*  linux/arch/arm/boot/compressed/vmlinux.lds.in 

*  Copyright (C) 2000 Russell King 

* This program is free software; you can redistribute it and/or modify 
* it under the terms of the GNU General Public License version 2 as 
* published by the Free Software Foundation. 
*/ 
OUTPUT_ARCH(arm) 
ENTRY(_start) 
SECTIONS 

/DISCARD/ : { 
*(.ARM.exidx*) 
*(.ARM.extab*) 
/* 
* Discard any r/w data - this produces a link error if we have any, 
* which is required for PIC decompression.  Local data generates 
* GOTOFF relocations, which prevents it being relocated independently 
* of the text/got segments. 
*/ 
*(.data) 

. = TEXT_START; 
_text = .; 

.text : { 
_start = .; 
*(.start) 
*(.text) 
*(.text.*) 
*(.fixup) 
*(.gnu.warning) 
*(.rodata) 
*(.rodata.*) 
*(.glue_7) 
*(.glue_7t) 
*(.piggydata) 
. = ALIGN(4); 

_etext = .; 

/* Assume size of decompressed image is 4x the compressed image*/ 
_image_size = (_etext - _text)* 4; 

_got_start = .; 
.got: {*(.got) } 
_got_end = .; 
.got.plt: {*(.got.plt) } 
_edata = .; 

. = BSS_START; 
__bss_start = .; 
.bss: {*(.bss) } 
_end = .; 

. = ALIGN(8); /* the stack must be 64-bit aligned*/ 
.stack  : {*(.stack) } 

.stab 0 : {*(.stab) } 
.stabstr 0  : {*(.stabstr) } 
.stab.excl 0: {*(.stab.excl) } 
.stab.exclstr 0   : {*(.stab.exclstr) } 
.stab.index 0 : {*(.stab.index) } 
.stab.indexstr 0  : {*(.stab.indexstr) } 
.comment 0  : {*(.comment) } 
}


在arch/arm/boot/compressed/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-r3
7: 建立C程序运行需要的缓存
8: 这时r2是缓存的结束地址,r4是kernel的最后执行地址,r5是kernel境象文件的开始地址
9: 用文件misc.c的函数decompress_kernel(),解压内核于缓存结束的地方(r2地址之后)。