Linux:内核解压缩过程简析

文章目录

  • [1. 前言](#1. 前言)
  • [2. 背景](#2. 背景)
  • [3. zImage 的构建过程](#3. zImage 的构建过程)
  • [4. 内核引导过程](#4. 内核引导过程)
  • [5. 内核解压缩过程](#5. 内核解压缩过程)
  • [6. 内核加压缩过程小结](#6. 内核加压缩过程小结)
  • [7. 参考资料](#7. 参考资料)

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文基于 ARM32架构 + Linux 4.14内核 进行分析,且仅讨论 zImage 格式的解压缩过程。

3. zImage 的构建过程

要理解内核镜像的解压缩过程,首先要了解内核镜像的建立过程。下面来看内核镜像 zImage 的构建过程。

c 复制代码
# arch/arm/boot/Makefile

# arch/arm/boot/Image 由 内核 ELF 文件 vmlinux 通过 objcopy 生成:
#         objcopy
# vmlinux =======> arch/arm/boot/Image
$(obj)/Image: vmlinux FORCE
	$(call if_changed,objcopy)

# arch/arm/boot/compressed/vmlinux 依赖于 arch/arm/boot/Image:
# 修改了 arch/arm/boot/Image 必须更新 arch/arm/boot/compressed/vmlinux
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
	$(Q)$(MAKE) $(build)=$(obj)/compressed $@

# arch/arm/boot/zImage 由 arch/arm/boot/compressed/vmlinux 通过 objcopy 生成:
#                                  objcopy
# arch/arm/boot/compressed/vmlinux =======> arch/arm/boot/zImage
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
	$(call if_changed,objcopy)
c 复制代码
# arch/arm/boot/compressed/Makfile

# 内核解压缩程序主干代码
AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
HEAD = head.o
OBJS += misc.o decompress.o

...

# 不同的内核配置,会使用不同的压缩算法对内核进行压缩
compress-$(CONFIG_KERNEL_GZIP) = gzip
compress-$(CONFIG_KERNEL_LZO)  = lzo
compress-$(CONFIG_KERNEL_LZMA) = lzma
compress-$(CONFIG_KERNEL_XZ)   = xzkern
compress-$(CONFIG_KERNEL_LZ4)  = lz4

...

# 生成 解压缩程序 arch/arm/boot/compressed/vmlinux:
# {head.o,piggy.o,misc.o,decompress.o,...} ==> arch/arm/boot/compressed/vmlinux
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \
		$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \
		$(bswapsdi2) $(efi-obj-y) FORCE
	@$(check_for_multiple_zreladdr)
	$(call if_changed,ld)
	@$(check_for_bad_syms)

# 将内核 Image 文件压缩成 piggy_data 文件:
# arch/arm/boot/Image ==> arch/arm/boot/compressed/piggy_data
$(obj)/piggy_data: $(obj)/../Image FORCE
	$(call if_changed,$(compress-y))

# piggy.S ==> piggy.o
# arch/arm/boot/compressed/piggy.S 包含了 piggy_data (压缩的内核镜像 Image)
$(obj)/piggy.o: $(obj)/piggy_data

...
c 复制代码
/* arch/arm/boot/compressed/piggy.S */

	.section .piggydata,#alloc
	.globl input_data
input_data:
	.incbin "arch/arm/boot/compressed/piggy_data" /* 被压缩后的内核 Image */
	.globl input_data_end
input_data_end:

$(call if_changed,objcopy) 用来调用 objcopy,简单的看下它是怎么工作的:

c 复制代码
# scripts/Kbuild.include

arg-check = $(if $(strip $(cmd_$@)),,1)

make-cmd = $(call escsq,$(subst $(pound),$$(pound),$(subst $$,$$$$,$(cmd_$(1)))))

any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)

# 在合适的条件下,调用命令 cmd_XXX (如 cmd_objcopy)
# Execute command if command has changed or prerequisite(s) are updated.
if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
	@set -e;                                                             \
	$(echo-cmd) $(cmd_$(1));                                             \
	printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
c 复制代码
# scripts/Makefile.lib

# Objcopy
# ---------------------------------------------------------------------------

quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@

内核代码根目录下 Makefile ,定义了 OBJCOPY

c 复制代码
# 内核代码根目录下 Makefile

OBJCOPY  = $(CROSS_COMPILE)objcopy
...

具体架构目录的 Makefile ,定义了 OBJCOPYFLAGS

c 复制代码
# arch/arm/boot/Makefile

# -O binary  : 输出文件(Image,zImage) 的 BFD 格式为 binary
# -R .comment:移除输入文件(vmlinux) 中 的 注释段
# -S         : 移除输入文件(vmlinux) 中 的 符号信息、重定义信息、调试信息
OBJCOPYFLAGS :=-O binary -R .comment -S
...

通过上面的简单分析,可以将 zImage 的构建过程总结如下图:

bash 复制代码
               编译+链接                   objcopy                      压缩(gzip,lzo,lzma,...)
1. linux源代码 ---------> vmlinux(elf文件) -------> arch/arm/boot/Image -----------------------> piggy_data
                                     编译
2. piggy.S(包含 piggy_data 压缩内核) ------> piggy.o
                                              链接                                   objcopy
3. (head.o,misc.o,decompress.o,...) + piggy.o ----> arch/arm/boot/compressed/vmlinux ------> arch/arm/boot/zImage

4. 内核引导过程

BootLoader 开始,内核的引导过程,可简单概括如下:

bash 复制代码
BootLoader -> 内核解压程序 -> 内核

在本文限定的上下文中,BootLoader 可以是 U-BOOT 等其它引导程序,内核解压程序arch/arm/boot/zImage(开头一部分),内核为 arch/arm/boot/Image

5. 内核解压缩过程

内核解压程序 的链接脚本 arch/arm/boot/compressed/vmlinux.ld.S 片段

c 复制代码
OUTPUT_ARCH(arm)
ENTRY(_start) // 指定内核解压程序入口
SECTIONS
{
	...
	
	. = TEXT_START;
	_text = .;

	.text : {
		_start = .;
		*(.start) // 解压程序入口位置
		*(.text)
		*(.text.*)
		*(.fixup)
		*(.gnu.warning)
		*(.glue_7t)
		*(.glue_7)
	}

	...
}

了解到解压程序的入口位置在 .start 代码段,我们从这里开始分析内核解压过程。一开始,会将处理器设置为 SVC 模式,并禁用 FIQ 和 IRQ 中断,以及保存一下上下文(如保存 CPU 架构 和 DTB 数据物理地址 等)

c 复制代码
// arch/arm/boot/compressed/head.S

	/* 内核解压程序入口 */
	.section ".start", #alloc, #execinstr

	.align
 AR_CLASS(	.arm	)
start:
	.type	start,#function
		// 重复 7 条 nop 指令
		.rept	7
		__nop
		.endr
#ifndef CONFIG_THUMB2_KERNEL // ARM 指令模式内核(非 Thumb 指令模式)
		mov	r0, r0 // 第 8 条空指令
#else
		...
#endif
		W(b)	1f

		// 一些 MAGIC 数字数据,
		// 以及 UEFI 启动的 数据(本文不讨论 UEFI,ARM32 没见过用  UEFI 模式启动的)
		...

1:
 AR_CLASS(	mrs	r9, cpsr	) // 读取程序状态寄存器 cpsr 到 r9
 		...
 		/*
		 * BootLoader
		 * . 从 r1 传递硬件架构 ID
		 * . 从 r2 传递 DTB 物理地址
		 * 后续的代码会破坏 r1, r2 的值,这里先:
		 * 保存 硬件架构 ID 到 r7
		 * 保存 DTB 地址到 r8
		 */
		mov	r7, r1			@ save architecture ID
		mov	r8, r2			@ save atags pointer

#ifndef CONFIG_CPU_V7M
		mrs	r2, cpsr		@ get current mode
		tst	r2, #3			@ not user?
		bne	not_angel		// 如果不是 User 模式,跳转到 not_angle 标号处
		...
not_angel:
		safe_svcmode_maskall r0 /* 将处理器设置为 SVC 模式, 同时禁用 FIQ & IRQ */
		msr	spsr_cxsf, r9		@ Save the CPU boot mode in
								@ SPSR		
#endif

然后是确定内核被解压后放置地址到寄存器 r4

c 复制代码
// arch/arm/boot/compressed/head.S

		.text
		/* 设定 内核 被解压缩后 的 加载地址 到 r4 */
#ifdef CONFIG_AUTO_ZRELADDR
		mov	r4, pc
		/*
		 * 将加载向下对齐到 128MB: 
		 * 这要求内核镜像被加载到所在物理内存 (128MB - TEXT_OFFSET) 位置开始及往上空间.
		 */
		and	r4, r4, #0xf8000000
		/* 
		 * TEXT_OFFSET 由两个 Makefile 一起定义: 
		 * (1) arch/arm/Makefile)
		 *     textofs-y	:= 0x00008000
		 *     ...
		 *     TEXT_OFFSET := $(textofs-y)
		 *     ...
		 *     export	TEXT_OFFSET GZFLAGS MMUEXT
		 * (2) arch/arm/boot/compressed/Makefile
		 *     AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
		 */
		add	r4, r4, #TEXT_OFFSET /* 设定 解压后 内核的加载地址到 r4 */
#else
		...
#endif

接下来,看 解压后的内核 和 内核解压程序中解压相关部分代码,是否存在空间重叠如果两者存在空间重叠将 解压缩程序中解压相关部分代码 重定位 到解压后的内核 之后的空间上去。来看细节:

c 复制代码
// arch/arm/boot/compressed/head.S

		/*
		 * 比较 解压程序当前运行地址 和 解压后内核加载起始地址:
		 * if (r0 < r4) { // 解压程序当前运行地址 < 解压后内核加载起始地址
		 * 		r0 = 解压程序当前结束位置地址(尾部向后扩展了部分空间)
		 * 		if (r4 < r0) // 内核加载起始地址 < 解压程序当前结束位置地址
		 * 			r4 |= 1 // 标记解压过程未使用 cache 加速
		 * 		else // 内核加载起始地址 >= 解压程序当前结束位置地址: 两者无空间重叠
		 * 			blcs cache_on // 开启 cache 加速解压过程
		 * } else { // 解压程序当前运行地址 >= 解压后内核加载起始地址: : 两者无空间重叠
		 * 		blcs cache_on // 开启 cache 加速解压过程
		 * }
		 * 从上面逻辑看到,如果 解压程序 和 解压后内核 位置在空间上没有重叠,则开启 cache
		 * 加速解压过程,这样做的原因,可能有更高的命中率 ???
		 */
		mov	r0, pc // r0: 解压程序当前运行地址 + 4
		cmp	r0, r4 // 比较 解压程序当前运行地址 和 解压后内核起始加载地址
		ldrcc	r0, LC0+32
		addcc	r0, r0, pc
		cmpcc	r4, r0 // 比较 解压后内核起始加载地址 和 解压程序当前结束地址
		orrcc	r4, r4, #1		@ remember we skipped cache_on
		blcs	cache_on

restart:	adr	r0, LC0 /* r0: LC0 的 当前运行时地址 */
		/*
		 * r1 : LC0 的 链接地址
		 * r2 : __bss_start 的 链接地址
		 * r3 : _end 的 链接地址
		 * r6 : _edata 的 链接地址
		 * r10: input_data_end 的 链接地址, 即 紧邻 压缩内核(piggy_data) 后的
		 *      4字节 的 链接地址,该地址开始的 4个字节存储了压缩前 内核的大小. 
		 *      详见 piggy.S
		 * r11: _got_start 的 链接地址
		 * r12: _got_end 的 链接地址
		 */
		ldmia	r0, {r1, r2, r3, r6, r10, r11, r12}
		/* sp : 指向预分配 4K 的堆栈空间 .L_user_stack 底部链接地址,
		 *      即 .L_user_stack_end 的链接地址 (堆栈向低地址增长)
		 */
		ldr	sp, [r0, #28]

		// r0: LC0 当前运行时地址 - LC0 的链接地址
		sub	r0, r0, r1		@ calculate the delta offset
		// r6: _edata 运行时地址 (解压缩程序 当前运行时 结束地址)
		add	r6, r6, r0		@ _edata
		// r10: input_data_end 当前运行时地址
		add	r10, r10, r0		@ inflated kernel size location

		// 读取压缩前内核大小到 r9 (即 arch/arm/boot/Image 的大小) 
		ldrb	r9, [r10, #0]
		ldrb	lr, [r10, #1]
		orr	r9, r9, lr, lsl #8
		ldrb	lr, [r10, #2]
		ldrb	r10, [r10, #3]
		orr	r9, r9, lr, lsl #16
		orr	r9, r9, r10, lsl #24

#ifndef CONFIG_ZBOOT_ROM
		/* malloc space is above the relocated stack (64k max) */
		add	sp, sp, r0 // 修正 sp 堆栈指针:指向栈空间底部 .L_user_stack_end 的 当前运行时地址
		add	r10, sp, #0x10000 // 移动到距离 .L_user_stack_end 64K 的位置
#else
		...
#endif

		mov	r5, #0			@ init dtb size to 0
#ifdef CONFIG_ARM_APPENDED_DTB
		// 早期支持 dts 的内核,要求 DTB 紧贴在内核之后的位置,后期的内核不再有这个要求
#endif

/* 
 * 检查 解压缩程序 和 解压后内核 是否存在空间重叠的情形。
 * 解压缩程序 和 解压后内核 位置不重叠 有如下 两种 情形:
 * (1) 解压缩程序 整个在 解压后内核 之前
 *      -------------
 *     |             |
 *     | 解压缩程序   |
 *     |             | 
 *     \-------------\
 *     \             \
 *     |-------------|
 *     |             |
 *     |  解压后内核  |
 *     |             |
 *      -------------
 *
 * (2) 解压缩程序 整个在 解压后内核 之后
 *      -------------
 *     |             |
 *     | 解压后内核   |
 *     |             | 
 *     \-------------\
 *     \             \
 *     |-------------|
 *     |             |
 *     | 解压缩程序   |
 *     |             |
 *      -------------
 * 我们注意到, 检查代码中,解压缩程序顶部是以 wont_overwrite 
 * 为边界, 为什么? 因为如果存在除 (1) 或 (2) 之外的覆盖情形,如果覆盖
 * 的不是 wont_overwrite 之后、用来解压缩的代码部分,前面这些已经运行
 * 过的代码,即使覆盖了也无所谓,因为已经用不着了。
 */
		/* 检验 是否是 情形 (1) */
		add	r10, r10, #16384
		cmp	r4, r10
		bhs	wont_overwrite // 情形 (1): 不需要做 解压缩程序 重定位,进入解压缩过程
		/* 检验 是否是 情形 (2) */
		add	r10, r4, r9 // r10: 解压后内核 结束地址
		adr	r9, wont_overwrite
		cmp	r10, r9
		bls	wont_overwrite // 情形 (2): 不需要做 解压缩程序 重定位,进入解压缩过程

/*
 * 解压缩程序(wont_overwrite 之后的解码代码) 和 解压后内核
 * 存在空间重叠,需要对 解压缩程序 进行重定位,然后重新检测,
 * 并最终进入解码 wont_overwrite 后的解压逻辑。
 * 不管是什么情形的重叠,都是将 解压缩程序(区间
 * [restart,reloc_code_end] 部分) 重定位到 内核 后面位置。
 */
		/*
		 * Bump to the next 256-byte boundary with the size of
		 * the relocation code added. This avoids overwriting
		 * ourself when the offset is small.
		 */
		add	r10, r10, #((reloc_code_end - restart + 256) & ~255)
		bic	r10, r10, #255

		/* Get start of code we want to copy and align it down. */
		adr	r5, restart /* r5: restart 标号 的 当前运行时地址 */
		bic	r5, r5, #31
		
		...

		// 当前的上下文:
		// r6: _edata 运行时地址 (解压缩程序 运行时 结束地址)
		// r5: restart 标号的 运行时地址
		// r10: 解压后内核 结束地址

		// r9: 解压缩程序 需重定位的  代码数据 大小 (向上、向下均对齐后的大小)
		sub	r9, r6, r5		@ size to copy
		add	r9, r9, #31		@ rounded up to a multiple
		bic	r9, r9, #31		@ ... of 32 bytes
		add	r6, r9, r5 // r6: 解压缩程序 需重定位的代码数据 【旧的】 结束地址
		add	r9, r9, r10 // r9: 解压缩程序 【新的】 重定位起始地址

		// 将 解压缩程序 重定位到 新的位置: 由 高地址 向 低地址 逆向拷贝
1:		ldmdb	r6!, {r0 - r3, r10 - r12, lr}
		cmp	r6, r5
		stmdb	r9!, {r0 - r3, r10 - r12, lr}
		bhi	1b

		/* Preserve offset to relocated code. */
		// r6: 重定位后,新、旧 解压缩程序 开始位置 之间的距离
		sub	r6, r9, r6

#ifndef CONFIG_ZBOOT_ROM
		/* cache_clean_flush may use the stack, so relocate it */
		add	sp, sp, r6 /* 随着重定位 解压缩程序 到新位置,4K .L_user_stack 堆栈也需要重定位 */
#endif

		bl	cache_clean_flush // cache 清理

		badr	r0, restart // r0: restart 当前的 运行时地址
		add	r0, r0, r6 // r0: restart 重定位后、新的 运行时地址
		mov	pc, r0 /* 重定位 解压缩程序 后, 跳转 restart 标号处 重新执行 */

上述过程中,涉及的数据、堆栈、链接脚本如下:

c 复制代码
// arch/arm/boot/compressed/head.S

		// 解压程序代码、数据的边界标记符号
		.align	2
		.type	LC0, #object
LC0:		.word	LC0			@ r1 // +0
		.word	__bss_start		@ r2 // +4
		.word	_end			@ r3 // +8
		.word	_edata			@ r6 // +12
		.word	input_data_end - 4	@ r10 (inflated size location) // + 16
		.word	_got_start		@ r11 // + 20
		.word	_got_end		@ ip // +24
		.word	.L_user_stack_end	@ sp // +28
		.word	_end - restart + 16384 + 1024*1024 // +32
		.size	LC0, . - LC0 // +36

		...

		// 解压过程使用的堆栈空间
		.align
		.section ".stack", "aw", %nobits
.L_user_stack:	.space	4096
.L_user_stack_end:
c 复制代码
// arch/arm/boot/compressed/piggy.S

/* SPDX-License-Identifier: GPL-2.0 */
	.section .piggydata,#alloc
	.globl	input_data
input_data:
	/*
	 * arch/arm/boot/compressed/Makefile:
	 *
	 * $(obj)/piggy_data: $(obj)/../Image FORCE
	 * 		$(call if_changed,$(compress-y))
	 *
	 * $(obj)/piggy.o: $(obj)/piggy_data
	 *
	 * 被压缩后的内核 Image, 压缩程序(如 gzip) 会在 piggy_data 的
	 * 最后 4个字节 储存 压缩前内核的大小(即 arch/arm/boot/Image 的大小) 。
	 */
	.incbin	"arch/arm/boot/compressed/piggy_data"
	.globl	input_data_end
input_data_end:
c 复制代码
// 链接脚本:arch/arm/boot/compressed/vmlinux.ld.S

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) // 这里很重要,因为解压涉及到重定位,允许包 r/w 数据影响到程序重定位
	}

	. = TEXT_START;
	_text = .;

	.text : {
		_start = .;
		*(.start)
		*(.text)
		*(.text.*)
		*(.fixup)
		*(.gnu.warning)
		*(.glue_7t)
		*(.glue_7)
	}
	.rodata : {
		*(.rodata)
		*(.rodata.*)
	}
	.piggydata : { // 内核 Image 压缩数据
		*(.piggydata)
	}

	. = ALIGN(4);
	_etext = .; // 只读代码、数据结束位置

	// GOT(Global Offset Table) :PIC(位置无关代码)重定位表
	.got.plt		: { *(.got.plt) }
	_got_start = .;
	.got			: { *(.got) }
	_got_end = .;

	/* ensure the zImage file size is always a multiple of 64 bits */
	/* (without a dummy byte, ld just ignores the empty section) */
	.pad			: { BYTE(0); . = ALIGN(8); }

	...
	
	_edata = .;

	.image_end (NOLOAD) : {
		_edata_real = .;	
	}

	_magic_sig = ZIMAGE_MAGIC(0x016f2818);
	_magic_start = ZIMAGE_MAGIC(_start);
	_magic_end = ZIMAGE_MAGIC(_edata);

	. = BSS_START;
	__bss_start = .;
	.bss			: { *(.bss) }
	_end = .; // 解压程序结束位置

	. = ALIGN(8);		/* the stack must be 64-bit aligned */
	/*
	 * 解压缩程序使用的堆栈段.
     * arch/arm/boot/compressed/head.S:
     *		.align
     *		.section ".stack", "aw", %nobits
     * .L_user_stack:  .space  4096
     * .L_user_stack_end:
     * 这是 解压缩程序 中 【唯一一个】.stack 段。
     */
	.stack		: { *(.stack) }

	...
}

完成了空间覆盖检测、重定位工作后,最后剩下的就是内核的解压了,看具体细节:

c 复制代码
// arch/arm/boot/compressed/head.S

wont_overwrite:
/*
 * If delta is zero, we are running at the address we were linked at.
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = kernel execution address (possibly with LSB set)
 *   r5  = appended dtb size (0 if not present)
 *   r7  = architecture ID
 *   r8  = atags pointer
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 */
/*
 * r0: 运行时地址 - 链接地址
 */
		orrs	r1, r0, r5
		beq	not_relocated

		add	r11, r11, r0 // r11: _got_start (GOT 表起始位置) 当前运行时地址
		add	r12, r12, r0 // r12: _got_end (GOT 表结束位置) 当前运行时地址

#ifndef CONFIG_ZBOOT_ROM
		/*
		 * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
		 * we need to fix up pointers into the BSS region.
		 * Note that the stack pointer has already been fixed up.
		 */
		// 修正 BSS 段的位置
		add	r2, r2, r0 // r2: BSS 段起始位置 (__bss_start) 当前运行时地址
		add	r3, r3, r0 // r3: BSS 段结束位置 当前运行时地址

		/*
		 * Relocate all entries in the GOT table.
		 * Bump bss entries to _edata + dtb size
		 */
		/* 遍历修正所有 GOT 表项: GOT[i] += (运行时地址 - 链接地址) */
1:		ldr	r1, [r11, #0]		@ relocate entries in the GOT
		add	r1, r1, r0		@ This fixes up C references
		cmp	r1, r2			@ if entry >= bss_start &&
		cmphs	r3, r1			@       bss_end > entry
		addhi	r1, r1, r5		@    entry += dtb size // GOT[i] 再次修正: GOT[i] += DTB 大小
		str	r1, [r11], #4		@ next entry
		cmp	r11, r12
		blo	1b

		/* bump our bss pointers too */
		add	r2, r2, r5 // 再次 BSS 段起始位置 (__bss_start): GOT[i] += DTB 大小
		add	r3, r3, r5 // 再次 BSS 段结束位置: GOT[i] += DTB 大小
#else
		...
#endif

		// 清 0 整个 BSS 段
not_relocated:	mov	r0, #0
1:		str	r0, [r2], #4		@ clear bss
		str	r0, [r2], #4
		str	r0, [r2], #4
		str	r0, [r2], #4
		cmp	r2, r3
		blo	1b

		/*
		 * Did we skip the cache setup earlier?
		 * That is indicated by the LSB in r4.
		 * Do it now if so.
		 */
		tst	r4, #1
		bic	r4, r4, #1
		blne	cache_on

/*
 * The C runtime environment should now be setup sufficiently.
 * Set up some pointers, and start decompressing.
 *   r4  = kernel execution address
 *   r7  = architecture ID
 *   r8  = atags pointer
 */
/* 解压内核调用 C 函数 decompress_kernel() ,需设置好 C 运行时环境。 */
// 解压内核到 r4 指向的地址
		mov	r0, r4
		mov	r1, sp			@ malloc space above stack
		add	r2, sp, #0x10000	@ 64k max
		mov	r3, r7
		bl	decompress_kernel // 解压缩内核到 r4 指向的位置
		bl	cache_clean_flush
		bl	cache_off

#ifdef CONFIG_ARM_VIRT_EXT
		...
		bne	__enter_kernel		@ boot kernel directly
		...
#else
		b	__enter_kernel // 准备跳转到解压后的内核
#endif

		...

__enter_kernel:
		mov	r0, #0			@ must be 0
		// 恢复保存的 BootLoader 传递的 CPU 架构、DTB 物理地址 到 r1, r2
		mov	r1, r7			@ restore architecture number
		mov	r2, r8			@ restore atags pointer
		// 跳转到内核入口执行: arch/arm/boot/head.S 中 ENTRY(stext) 处执行
 ARM(		mov	pc, r4		)	@ call kernel

reloc_code_end:

6. 内核加压缩过程小结

我们用一幅流程图来简单小结下内核的解压缩过程。如下:

7. 参考资料

https://www.man7.org/linux/man-pages/man1/objcopy.1.html

相关推荐
虾..17 小时前
Linux 软硬链接和动静态库
linux·运维·服务器
Evan芙17 小时前
Linux常见的日志服务管理的常见日志服务
linux·运维·服务器
hkhkhkhkh12319 小时前
Linux设备节点基础知识
linux·服务器·驱动开发
HZero.chen20 小时前
Linux字符串处理
linux·string
张童瑶20 小时前
Linux SSH隧道代理转发及多层转发
linux·运维·ssh
汪汪队立大功12320 小时前
什么是SELinux
linux
石小千20 小时前
Linux安装OpenProject
linux·运维
柏木乃一20 小时前
进程(2)进程概念与基本操作
linux·服务器·开发语言·性能优化·shell·进程
Lime-309020 小时前
制作Ubuntu 24.04-GPU服务器测试系统盘
linux·运维·ubuntu
百年渔翁_肯肯21 小时前
Linux 与 Unix 的核心区别(清晰对比版)
linux·运维·unix