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

相关推荐
pk_xz1234562 小时前
Shell 脚本中变量和字符串的入门介绍
linux·运维·服务器
小珑也要变强2 小时前
Linux之sed命令详解
linux·运维·服务器
Lary_Rock4 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
云飞云共享云桌面6 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq6 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮7 小时前
Linux 使用中的问题
linux·运维
dsywws8 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零9 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
小林熬夜学编程11 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
程思扬11 小时前
为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
linux·服务器·网络·经验分享·后端·网络协议·1024程序员节