Linux内核编译完成后会生成zImage内核镜像文件。zImage是如何解压的呢?本文将结合关键代码,讲解zImage的解压过程。还是先来看看zImage的组成吧。在内核编译完成后会在arch/arm/boot/下生成zImage。
在arch/arm/boot/Makefile中,如下代码:
[plain]
- #
- # 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’
$(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),组成的。见下面的代码:
[plain]
- #
- # 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
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’
[plain]
- /*
- * 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地址之后)。