解压内核镜像

机器感知
一个专注于SLAM、Linux、算法等相关技术文章分享的公众号

步骤 0

uboot 将 zImage 复制到内存之后,跳转到 zImage 处开始执行,首先执行的代码是
arch/arm/boot/compressed/head.S 文件,首先是一些涉及不同体系结构调试相关的汇编宏定义

#ifdef DEBUG
#if defined(CONFIG_DEBUG_ICEDCC)
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_V6K) || defined(CONFIG_CPU_V7)
		.macro	loadsp, rb, tmp
		.endm
		.macro	writeb, ch, rb
		mcr	p14, 0, \ch, c0, c5, 0
		.endm
... 省略 ...
#endif
#endif
#endif

步骤 1

首先是保存 bootloader 传递过来的机器 ID 和 atags 起始地址

		@ 设置段名
		.section ".start", #alloc, #execinstr
		.align
		.arm				@ 设置指令为 arm 模式
start:
		.type	start,#function @ 声明为函数标签
		.rept	7
		mov	r0, r0
		.endr
   ARM(		mov	r0, r0		)
   ARM(		b	1f		)	@ 向下跳转
 THUMB(		adr	r12, BSYM(1f)	)
 THUMB(		bx	r12		)

		.word	0x016f2818	@ Magic numbers to help the loader
		.word	start		@ 程序其实地址,即 zImage 的地址
		.word	_edata	    @ zImage 结束地址
 THUMB(		.thumb			)
1:	  mov	r7, r1		  @ 保存 bootloader 传递过来的机器 ID
		mov	r8, r2			@ 保存 bootloader 传递过来的 atags 起始地址

步骤 2

关闭中断、进入 SVC 模式

#ifndef __ARM_ARCH_2__
		mrs	r2, cpsr		@ get current mode
		tst	r2, #3			@ not user?
		bne	not_angel
		mov	r0, #0x17		@ 进入 SVC 模式
 ARM(		swi	0x123456	)	@ angel_SWI_ARM
 THUMB(		svc	0xab		)	@ angel_SWI_THUMB
not_angel:
		mrs	r2, cpsr
		orr	r2, r2, #0xc0	@ 关闭 FIQ 和 IRQ
		msr	cpsr_c, r2
#else
		teqp	pc, #0x0c000003		@ turn off interrupts

步骤 3

为了加速解压过程,打开缓存

#ifdef CONFIG_AUTO_ZRELADDR
		@ 如果配置了运行时自动计算重定位地址
		@ 则根据当前 pc 位置和 TEXT_OFFSET 计算出解压位置
		@ TEXT_OFFSET 在 arch/arm/Makefile 中指定,表示的是相对于内存起始地址的偏移
		@ 定义为 textofs-y	:= 0x00008000	TEXT_OFFSET := $(textofs-y)
		mov	r4, pc
		and	r4, r4, #0xf8000000		@ 这里假设 zImage 是被放在内存的前 128MB 内的
		add	r4, r4, #TEXT_OFFSET	@ 得到 zImage 解压的物理地址
#else
		@ 这里是直接在 arch/arm/mach-s5p4418/Makefile.boot 中定义的
		@ 定义为 zreladdr-y	:= 0x40008000
		ldr	r4, =zreladdr @ r4 存放解压后内核存放的起始地址
#endif
		@ 打开缓存和 MMU
		bl	cache_on

步骤 4

检查解压后的内核镜像是否与当前镜像发生覆盖

restart:	adr	r0, LC0
		ldmia	r0, {r1, r2, r3, r6, r10, r11, r12} @ 将 LC0 数据放入寄存器中
		ldr	sp, [r0, #28] @ 更新栈指针位置
		
		sub	r0, r0, r1		@ 计算当前程序位置和链接地址间的偏移量
		add	r6, r6, r0		@ 得到 zImage 运行地址的结束位置
		add	r10, r10, r0	@ 存放未压缩内核大小的运行地址
		
		/*
		 * 内核编译系统会将未压缩的内核大小追加到压缩后的内核数据之后
		 * 并以小端格式存储
		 */
		@ 取出未压缩内核大小放入 r9
		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
		/* 在栈指针之上分配内存空间放到 r10 中 (64k max) */
		add	sp, sp, r0
		add	r10, sp, #0x10000
#else
		mov	r10, r6
#endif
		mov	r5, #0			@ init dtb size to 0
		/*
		 * 检查解压后的内核是否会覆盖当前代码
		 *   r4  = 最终解压后的内核起始地址
		 *   r9  = 解压后的内核镜像大小
		 *   r10 = 当前镜像结束地址,其中包括所分配的内存区域
		 * We basically want:
		 *   r4 - 16k 页表 >= r10 -> OK
		 *   r4 + image length <= address of wont_overwrite -> OK
		 */
		add	r10, r10, #16384 @ 内核镜像前的 16KB 页表
		cmp	r4, r10
		bhs	wont_overwrite @ 解压后的内核起始地址大于等于 r10
		add	r10, r4, r9
		adr	r9, wont_overwrite
		cmp	r10, r9        @ 或者解压后的内核结束地址在 wont_overwrite 地址之前
		bls	wont_overwrite
		/*
		 *   r6  = _edata
		 *   r10 = end of the decompressed kernel
		 */
		@ 当前代码段向后移动到的目的地址,不足 256 字节的补足 256 字节,向上取整
		add	r10, r10, #((reloc_code_end - restart + 256) & ~255)
		bic	r10, r10, #255

		@ 当前代码段起始地址,以 32 字节为单位,向下取整
		adr	r5, restart
		bic	r5, r5, #31

		sub	r9, r6, r5		@ 需要移动镜像的大小
		add	r9, r9, #31		@ 以 32 字节为单位向上取整
		bic	r9, r9, #31
		add	r6, r9, r5		@ 循环结束地址
		add	r9, r9, r10		@ 目的地址结束位置
		@ 循环移动当前镜像
1:		ldmdb	r6!, {r0 - r3, r10 - r12, lr}
		cmp	r6, r5
		stmdb	r9!, {r0 - r3, r10 - r12, lr}
		bhi	1b

		sub	r6, r9, r6 @ 代码重定位的偏移地址

#ifndef CONFIG_ZBOOT_ROM
		add	sp, sp, r6 @ 对栈指针也进行重定位
#endif

		bl	cache_clean_flush
		@ 跳转到重定位后的镜像 restart 处重新执行,检查是否存在覆盖
		adr	r0, BSYM(restart)
		add	r0, r0, r6
		mov	pc, r0

步骤 5

清理 bss 段、解压内核、关闭缓存,最后启动内核

/* 至此,当前镜像和解压后的内核不会发生覆盖问题 */
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
 *   r5  = appended dtb size (0 if not present)
 *   r7  = architecture ID
 *   r8  = atags pointer
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 */
		orrs	r1, r0, r5
		beq	not_relocated

		add	r11, r11, r0
		add	r12, r12, r0

not_relocated:	mov	r0, #0
1:		str	r0, [r2], #4		@ 清理 bss 段
		str	r0, [r2], #4
		str	r0, [r2], #4
		str	r0, [r2], #4
		cmp	r2, r3
		blo	1b

/*
 *   至此,C 语言运行环境已经初始化完成
 *   r4  = 解压后的内核起始地址
 *   r7  = 机器 ID
 *   r8  = atags 指针
 */
		mov	r0, r4
		mov	r1, sp				@ 在栈指针之上分配内存空间
		add	r2, sp, #0x10000	@ 64k max
		mov	r3, r7
		bl	decompress_kernel	@ 解压内核
		bl	cache_clean_flush
		bl	cache_off			@ 关闭缓存
		mov	r0, #0			@ must be zero
		mov	r1, r7			@ restore architecture number
		mov	r2, r8			@ restore atags pointer
 ARM(		mov	pc, r4	)	  @ 启动内核
 THUMB(		bx	r4	)		@ entry point is always ARM
		
		.align	2
		.type	LC0, #object
LC0:		.word	LC0			@ r1:LC0 的链接地址
		.word	__bss_start		@ r2:bss 段的起始地址
		.word	_end			@ r3:bss 段的结束地址
		.word	_edata			@ r6:zImage 的结束地址
		.word	input_data_end - 4	@ r10:存放未压缩的内核大小的链接地址
		.word	_got_start		@ r11:全局偏移表起始位置
		.word	_got_end		@ ip:全局偏移表结束位置
		.word	.L_user_stack_end	@ sp:栈指针
		.size	LC0, . - LC0	@ 该 LC0 数据的大小

你可能感兴趣的:(linux)