天天看點

arm-linux核心啟動學習筆記(二)(廢棄)

更多内容可關注微信公衆号
arm-linux核心啟動學習筆記(二)(廢棄)

#已合并到arm-linux核心啟動學習筆記(一)

###Part2:(cache_on,call_kernel]

####vmlinux(小)的連結腳本:

//./arch/arm/boot/compressed/vmlinux.lds.in
//這個腳本是用來連結vmlinux(小)的,故zImage代碼執行時
//核心各個段的分布也是在這個腳本中定義的(部分代碼已省略)。
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
  . = TEXT_START;
  _text = .;
  .text : {
    _start = .;
    *(.start)
    *(.text)
    *(.text.*)
    *(.fixup)
    *(.gnu.warning)
    *(.glue_7t)
    *(.glue_7)
  }
  .rodata : {
    *(.rodata)
    *(.rodata.*)
  }
  //這裡是piggy.gz的位置
  .piggydata : {
    *(.piggydata)
  }
  . = ALIGN(4);
  _etext = .;
  .got.plt		: { *(.got.plt) }
  _got_start = .;
  .got			: { *(.got) }
  _got_end = .;

  .pad			: { BYTE(0); . = ALIGN(8); }
  _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) }
}

           

####piggy.gzip.S

piggy.gzip.S是用來生成piggy.gzip.o的,其實際上相當于将一個piggy.gzip檔案當

二進制打包到vmlinux(小)中了。

//./arch/arm/boot/compressed/piggy.gzip.S
	.section .piggydata,#alloc
	//global隻是聲明,并不配置設定空間
	.globl	input_data
input_data:
##INCBIN 指令在被彙編的檔案内包含一個檔案。 該檔案按原樣包含,沒有進行彙編。
	.incbin	"arch/arm/boot/compressed/piggy.gzip"
	.globl	input_data_end
input_data_end:
           

####cache_on之後

cache_on之後段代的代碼大體可認為做了三件事:

  1. 檢查核心是否需要移動,如果需要移動則将其移動并重定向.got的内容。
  2. 調用decompress_kernel解壓核心。
  3. 調用call_kernel跳轉到Image。
//cache_on函數的實際作用是開啟高速緩存,這樣可以加快後續代碼的執行速度
		//在arm體系結構中,高速緩存TLB必須依賴于mmu,是以cache_on函數内部通過
		//調用__setup_mmu來初始化頁表(代碼段的頁表屬性允許緩存)
		//調用__common_mmu_cache_on來使能mmu
		bl	cache_on
		//跳轉到這裡,mmu已經開啟了,恒等映射!
/*		
		LC0的代碼本來是在後面的,這裡為了友善解釋,先在前面列出了
		各個代碼段的分布見後(這些值都是編譯位址!)。
LC0:		.word	LC0			@ r1
		//bss段的開始位置
		.word	__bss_start		@ r2
		//bss的結束位置
		.word	_end			@ r3
		//資料段的結束位置
		.word	_edata			@ r6
		//pizzy.gz的結束位置
		.word	input_data_end - 4	@ r10 (inflated size location)
		.word	_got_start		@ r11
		.word	_got_end		@ r12 (ip)
		//棧的結束位置
		.word	.L_user_stack_end	@ sp
		//這個應該是個僞指令,指定LC0大小的
		.size	LC0, . - LC0
*/
		//adr是相對于目前pc的相對尋址,一般用于擷取指令的真是位址,而不是加載位址
restart:	adr	r0, LC0
		//把上面LC0中的一堆變量載入到各個寄存器中
		ldmia	r0, {r1, r2, r3, r6, r10, r11, r12}
		//設定臨時棧, sp = L_user_stack_end,目前sp還指向棧底
		ldr	sp, [r0, #28]

		/*
			zImage的加載位址未必是編譯位址,是以這裡需要
			将各個變量由編譯位址修正為真實位址。
		*/
		//計算LC0的真實位址(既是實體位址,又是虛拟位址)和編譯位址的內插補點
		sub	r0, r0, r1		@ calculate the delta offset
		//将_edata的編譯位址轉為真實位址
		add	r6, r6, r0		@ _edata
		//将input_data_end的編譯位址轉為真實位址
		add	r10, r10, r0		@ inflated kernel size location

		//将sp的編譯位址修正為真實位址,并向上增加64K,作為棧頂
		add	sp, sp, r0
		add	r10, sp, #0x10000

		/*
		 * The kernel build system appends the size of the
		 * decompressed kernel at the end of the compressed data
		 * in little-endian form.
		 */
		/*
			 r9是piggy.gzip這個gzip檔案的最後四個位元組,這四個位元組記錄的
			 是解壓後的Image的大小存的.在我這裡piggy.gzip最後: 30c1 b500 0a
			 Image大小為0xb5c130, 最後那個0a估計最後會去掉的。
			 這裡用ldrb指令是考慮到這個存Image大小的位置可能不是4byte對齊的。
		 */
		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
		
/*
 *   這一段代碼是來檢測是否需要複制自身的
 *   r4是最終核心要解壓到的位址,是通過zreladdr來指定的
 *   r9是Image鏡像的大小,是從piggy.gz檔案末尾擷取的
 *   r10是piggy.gz的結束位置
 * 往下執行需要滿足兩個條件之一:
 *   1) 最終核心要解壓到的位址(r4) - 16k頁表 >= zImage的基本代碼結束位置(r10)
 *   2) 最終核心解壓後的結束位址(r4 + r9(image length)) <= 後續執行的代碼(wont_overwrite的位址)
0x00000000 -----------------------------------------------------------------------------

												------ Image(zreladdr)
												  |
												  |
     ----- zImage起始位址                          | 
	   |                                          |
	   |                                          |
	 ----- 目前代碼(pc)                          ------ Image end 在此之上都可以
	 ----- wont_overwrite的代碼                      
	   |
     ----- zImage基本代碼結束(piggy.gz末尾,r10)
		     									----- Image(zreladdr)
				     								  ;如果解壓到這個位置往下都是可以的
									              |
									              |
									            ----- Image end
									             
0xffffffff -----------------------------------------------------------------------------
 */

 		//這裡r10 += 0x4000是預留給頁表的
		add	r10, r10, #16384
		cmp	r4, r10
		//如果r4 >= r10則滿足條件1,Image要解壓的位址在zImage後面,
		//跳轉到wont_overwrite,不需移動自身代碼。
		bhs	wont_overwrite
		//r10 = r4 + r9, r10為預計解壓後的image的結束位址
		add	r10, r4, r9
		//擷取wont_overwrite的實體位址
		adr	r9, wont_overwrite
		//如果否滿足條件2,即image解壓後沒有覆寫wont_overwrite
		//之後的代碼,則也無需移動自身代碼。
		cmp	r10, r9
		bls	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.
		 */
		//否則就需要移動代碼的位置了,這裡的邏輯是把從restart到zImage結束的代碼
		//移動到Image預計解壓後的結束位置的後面,為什麼從restart開始見下面。
		add	r10, r10, #((reloc_code_end - restart + 256) & ~255)
		//這裡的r10應該就是最終要移動到的位址了
		bic	r10, r10, #255

		/* Get start of code we want to copy and align it down. */
		//要複制的代碼的起始位址
		adr	r5, restart
		//清除末尾位
		bic	r5, r5, #31
		//要複制的資料大小
		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

		//代碼被移動過了,是以清緩存
		bl	cache_clean_flush
		//跳轉到restart重新執行,因為目前位址變了,是以LC0的目前位址和編譯位址
		//的內插補點就變了,前面的好多變量都是根據這個內插補點算出來的,是以這裡跳到
		//restart重新來過,是以前面檢測的時候是檢測wont_overwrite之前
		//的代碼是否被覆寫,而移動得要從restart的代碼開始移動。
		adr	r0, BSYM(restart)
		add	r0, r0, r6
		mov	pc, r0
		//到這裡
		
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
 */
		//r5在一開始被初始化為0
		orrs	r1, r0, r5
		//這裡是如果運作位址與連結位址相等則跳轉到not_relocated
		//不執行got段的重定位
		beq	not_relocated
		
		//否則将r11,r12存的GOT表的編譯位址修改為目前位址
		add	r11, r11, r0
		add	r12, r12, r0
		//修正bbs段的起始,結束位址
		add	r2, r2, r0
		add	r3, r3, r0
		//對GOT表中的所有元素做重定位
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
		str	r1, [r11], #4		@ next entry
		cmp	r11, r12
		blo	1b
		/* bump our bss pointers too */
		add	r2, r2, r5
		add	r3, r3, r5

not_relocated:	mov	r0, #0
		//初始化bss段的所有資料為空
1:		str	r0, [r2], #4		@ clear bss
		str	r0, [r2], #4
		str	r0, [r2], #4
		str	r0, [r2], #4
		cmp	r2, r3
		blo	1b

/*
 * 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
 */
		mov	r0, r4
		mov	r1, sp			@ malloc space above stack
		add	r2, sp, #0x10000	@ 64k max
		mov	r3, r7
		//這個即是将piggy.gz解壓為Image的函數,這裡是c代碼
		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
		//r4是Image的入口位址,也是Image的解壓位址,Image
		//本身是個binary檔案,第一個位元組即為指令,這裡跳轉到Image
		//即./arch/arm/kernel/head.s:stext
 ARM(	mov	pc, r4	)		@ call kernel
           

###decompress_kernel

decompress_kernel是用c函數完成的,其代碼如下

//./kernel/arch/arm/boot/compressed/Misc.c
extern char input_data[];
void decompress_kernel(
	unsigned long output_start,        //zImage的解壓位址(r4)
	unsigned long free_mem_ptr_p,      //臨時空間,解壓用(這裡用的是棧)
	unsigned long free_mem_ptr_end_p,  //臨時空間結尾 
	int arch_id)                       // arch id
{
	int ret;
	output_data		= (unsigned char *)output_start;
	free_mem_ptr		= free_mem_ptr_p;
	free_mem_end_ptr	= free_mem_ptr_end_p;
	__machine_arch_type	= arch_id;
	arch_decomp_setup();
	//putstr是核心啟動早期的列印函數,一般都是直接向IO端口寫的資料
	putstr("Uncompressing Linux...");
	//核心解壓函數,這裡用的是gzip解壓,這個input_data和input_data_end
	//定義在piggy.gzip.S中,是piggy.gzip的起始和結束位置。
	ret = do_decompress(input_data, input_data_end - input_data, output_data, error);
	if (ret)
		error("decompressor returned an error");
	else
		putstr(" done, booting the kernel.\n");
}
           

這裡的這一句”Uncompressing Linux…”在goldfish啟動的時候是可以列印出來的,在真實設别上(如MT6582),通過序列槽也是可以看到這些日志的,其實作如下:

./kernel/arch/arm/boot/compressed/Misc.c
static void putstr(const char *ptr)
{
    char c;
    while ((c = *ptr++) != '\0') {
        if (c == '\n')
            putc('\r');
        putc(c);
    }
    flush();
}
           

###舊版廢棄的内容,但是正确的

在zImage的反彙編代碼中,input_data在_got_start段,值為0x4015,如下:

arm-linux核心啟動學習筆記(二)(廢棄)

檢視其内容:

arm-linux核心啟動學習筆記(二)(廢棄)

檢視piggy.gz:

arm-linux核心啟動學習筆記(二)(廢棄)

可知整個zImage,實際上就是vmlinux(2.51MB那個)掐頭去尾一點,vmlinux是個标準ELF檔案,而zImage就是這個vmlinux去了ELF頭尾組成的,其二進制差異不過1%20.

也就是說,實際上是vmlinux包含了piggy.gz,而将其裁剪一點,就形成了最終的zImage。

  1. vmlinux(小的那個),是一個标準的elf檔案,将其掐頭去尾後就形成了zImage,這個就是最終的核心鏡像。
  2. zImage是最終的核心鏡像,其内部包含了一個原封不動的piggy.gz,這是一個壓縮後的核心。
  3. piggy.gz是image通過gzip指令壓縮來的。
  4. Image檔案是一個純二進制檔案,沒有elf頭,zImage中的最後一句 MOV PC, R4 ; call_kernel,這個R4就是之前kernel解壓的位址,就是直接跳到了Image的相對偏移0位置的指令。
  5. Image是從vmlinux(大)中抽取出來的,是objcopy -o binary vmlinux(大)來的。vmlinux(大)入口位址的第一條指令,就是Image這個二進制檔案的第一條指令。

是以綜上所述,zImage解壓核心,跳轉到Image偏移0處的指令,實際上相當于執行了vmlinux(大)的入口函數。而vmlinux(大)的入口函數為ENTRY(stext),定義在./arch/arm/kernel/head.S(注,zImage的入口函數定義在./arch/arm/boot/compressed/head.S),也就是說vmlinux(大)的入口函數,也是體系結構相關的!

繼續閱讀