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地址之后)。

