linux kernel 自解压部分的一点解释

关于自解压部分的逻辑和代码详解参看

http://blog.csdn.net/luckyapple1028/article/details/44726131

写得非常好,但是貌似还有一点点小错误。本文部分文字引用自这篇文章。


还有几点可能需要后续解释的先记录一下

		ldr	r4, =zreladdr
		/*
		 * Set up a page table only if it won't overwrite ourself.
		 * That means r4 < pc || r4 - 16k page directory > &_end.
		 * Given that r4 > &_end is most unfrequent, we add a rough
		 * additional 1MB of room for a possible appended DTB.
		 */
		mov	r0, pc
		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

		.align	2
		.type	LC0, #object
LC0:		.word	LC0			@ r1
		.word	__bss_start		@ r2
		.word	_end			@ r3
		.word	_edata			@ r6
		.word	input_data_end - 4	@ r10 (inflated size location)
		.word	_got_start		@ r11
		.word	_got_end		@ ip
		.word	.L_user_stack_end	@ sp
		.word	_end - restart + 16384 + 1024*1024
		.size	LC0, . - LC0


这里r4等于zreladdr是内核自解压的地址。

注释的描述是 :

只有在不自覆盖的情况下才会创建一个页表,也就是意味着r4<pc或者 r4-16k页目录的大小>&_end.(&_end是当前内核程序的末尾地址).通常r4>&_end是不常出现的。我们粗略添加了1MB的空间给可能附加在内核后面的DTB设备树块。

后续代码翻译成

r0=cur_pc_in_mem=pc

if(cur_pc_in_mem<zreladdr)

{

//LC0+32里面的值为=_end-restart+16K页目录+1MB的DTB空间

       total_cur_image_size=_end-restart+16*1024+1024*1024;

cur_image_end_pos_in_mem=total_cur_image_size+cur_pc_in_mem;

if(zreladdr<cur_image_end_pos_in_mem)

{

//做一个标记,后续根据这个标记决定是否需要调用cache_on。总之就是cache_on是都需要

//调用的。只是先后顺序不同。这里记住了我们没有打开cache_on。其他的情况都打开了cache_on

zreladdr=zreladdr | 1;

}

else

{

//需要16KB页表 打开data_cache是需要mmu的

cache_on

}

}

else

{

//需要16KB页表 打开data_cache是需要mmu的

cache_on

}

也就是前面连接文章中的

(1)      PC >= r4:直接进行缓存初始化

(2)      PC < r4 && _end + 16384+ 1024*1024 > r4:不进行缓存初始化

(3)      PC < r4 && _end + 16384+ 1024*1024 <= r4:执行缓存初始化


restart:	
		adr	r0, LC0
		ldmia	r0, {r1, r2, r3, r6, r10, r11, r12}
		ldr	sp, [r0, #28]

接下来是从LC0加载对应的值到相应的寄存器中

(1)      r0:LC0标签处的运行地址

(2)      r1:LC0标签处的链接地址

(3)      r2:__bss_start处的链接地址

(4)      r3:_ednd处的链接地址(即程序结束位置)

(5)      r6:_edata处的链接地址(即数据段结束位置)

(6)      r10:压缩后内核数据大小位置 (解压后的内核数据大小位置)

(7)      r11:GOT表的启示链接地址

(8)      r12:GOT表的结束链接地址

(9)      sp:栈空间结束地址

这里直接引用 但是其中r10描述应该错误了,应该是解压后的内核数据大小的位置。

随后通过r0和r1的比较能够知道当前的运行位置和链接地址是不是一致的。

		/*
		 * We might be running at a different address.  We need
		 * to fix up various pointers.
		 */
		sub	r0, r0, r1		@ calculate the delta offset
		add	r6, r6, r0		@ _edata
		add	r10, r10, r0		@ inflated kernel size location

用当前的内存中LC0地址-链接时LC0的地址得到差值。需要修复一些数据

数据段结束位置r6和压缩后内核数据大小位置需要加上这个差值。

这时候访问r6和r10才能得到准确的值,这时候的r6 r10是运行时在内存中对应的值,而不是链接时的值。


接下来一段是根据R10将解压后的内核大小放到r9当中。这个就要看lds脚本了

先看r10原来的值是.word input_data_end - 4@ r10 (inflated size location)

而input_data_end 的定义在 compressed/piggy.gzip.S中

.section .piggydata,#alloc
.globl input_data
input_data:
.incbin "arch/arm/boot/compressed/piggy.gzip"
.globl input_data_end
input_data_end:

可以发现input_data和input_data_end里面就是压缩的内核的数据,由于压缩内核时将解压后的大小4字节放在了piggy.gzip的末尾。也就是arm/boot/Image的大小

所以(r10=input_data_end - 4)。

为何要用一字节一字节方式访问r10地址数据拼凑得到r9呢?

因为压缩后的的内核末尾并不一定是内存对齐的。所以r10不一定是内存对齐的可访问地址,比如访问4字节,那么地址一定要是4的倍数,否则会访问出错。很多arm是不支持非对齐的数据访问,所以要拼凑出来r9。

		add	sp, sp, r0
		add	r10, sp, #0x10000
		mov	r5, #0			@ init dtb size to 0

接下来是修复堆栈指针。

r10=sp+64kb的malloc空间。也就是malloc空间在stack的上面,顺便看一下sp在哪里。

.word .L_user_stack_end@ sp

在head.s的最末尾

.L_user_stack: .space4096
.L_user_stack_end:

定义了4096字节的stack大小


接下来就是检查解压后内存是否会被覆盖的问题了

/*
 * Check to see if we will overwrite ourselves.
 *   r4  = final kernel address (possibly with LSB set)
 *   r9  = size of decompressed image
 *   r10 = end of this image, including  bss/stack/malloc space if non XIP
 * We basically want:
 *   r4 - 16k page directory >= r10 -> OK
 *   r4 + image length <= address of wont_overwrite -> OK
 * Note: the possible LSB in r4 is harmless here.
 */
		add	r10, r10, #16384
		cmp	r4, r10
		bhs	wont_overwrite
		add	r10, r4, r9
		adr	r9, wont_overwrite
		cmp	r10, r9
		bls	wont_overwrite
r4是内核解压缩后最终的地址,也就是最后的entry_point

r9是内核解压后的大小

r10是当前压缩内核的末尾的地址。包括了bss stack malloc空间的

也就是要比较解压后的内核是不是和当前的image内存重复了。

期望有两种。

第一种是r4的地址大于r10+16K页目录。也就是最终内核起始地址比当前image以及末尾需要的附属空间要大,解压后的整个内核在当前image的后面。

第二种是r4的地址+r9也就是解压后内核的大小,小于wont_overwrite。也就是解压后的整个内核在当前image的前面(至少是wont_overwrite的前面,之前的代码被覆盖了没事)。

其他的情况都是需要搬移代码的。如果不需要搬移,那么就直接运行到wont_overwrite。否则执行下面的搬移代码

/*
 * Relocate ourselves past the end of the decompressed kernel.
 *   r6  = _edata
 *   r10 = end of the decompressed kernel
 * Because we always copy ahead, we need to do it from the end and go
 * backward in case the source and destination overlap.
 */
		/*
		 * 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
		bic	r5, r5, #31


原本r10是当解压后内核的末尾的地址。因为在前面比较是不是overwrite的时候。add r10,r4,r9 也就是 r10=r4(解压后内核的起始地址)+r9(解压后内核的大小)

r6是_edata也就是数据段的末尾。

又对r10地址进行了扩展。大小是text段的大小。并且以256对齐。这个扩展是避免搬移的新代码和自己和自己冲突。


此时r10就是搬移的目的地的起始地址了。

再得到restart的地址放到r5。对r5进行对齐。这个就是拷贝的起始地址了。


		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
		add	r9, r9, r10

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. */
		sub	r6, r9, r6

		/* cache_clean_flush may use the stack, so relocate it */
		add	sp, sp, r6


首先计算需要拷贝的大小。r9=r6(数据段的末尾)-r5(restart的地址,也即是拷贝的起始地址);

r6(结束拷贝的地址)=r9(需要拷贝的大小)+r5(restart的地址,也即是拷贝的起始地址)

r9(拷贝的目的地的结束地址)=r9(需要拷贝的大小)+r10(拷贝的目的地的起始地址);

从结束地址向起始地址进行拷贝。比较r6(结束拷贝的地址)和r5(restart的地址,也即是拷贝的起始地址) 直到最终拷贝完毕。

拷贝的时候用的寄存器都是特定的r0-r3 r10r12,lr。目的就是保护其他寄存器不被破坏,r4 r7 r8等等。

执行sub r6,r9,r6的时候。r6已经等于r5了。r9已经等于r10了,所以等同于

r6(相对偏移)=r10(拷贝的目的地的起始地址)-r5(restart的地址,也即是拷贝的起始地址)

add sp,sp,r6

将sp加上相对位移。移动到指到新的sp位置上。因为 cache_clean_flush可能会用到stack。




0x410fb767


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值