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