更多内容可關注微信公衆号![]()
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之後段代的代碼大體可認為做了三件事:
- 檢查核心是否需要移動,如果需要移動則将其移動并重定向.got的内容。
- 調用decompress_kernel解壓核心。
- 調用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,如下:
檢視其内容:
檢視piggy.gz:
可知整個zImage,實際上就是vmlinux(2.51MB那個)掐頭去尾一點,vmlinux是個标準ELF檔案,而zImage就是這個vmlinux去了ELF頭尾組成的,其二進制差異不過1%20.
也就是說,實際上是vmlinux包含了piggy.gz,而将其裁剪一點,就形成了最終的zImage。
- vmlinux(小的那個),是一個标準的elf檔案,将其掐頭去尾後就形成了zImage,這個就是最終的核心鏡像。
- zImage是最終的核心鏡像,其内部包含了一個原封不動的piggy.gz,這是一個壓縮後的核心。
- piggy.gz是image通過gzip指令壓縮來的。
- Image檔案是一個純二進制檔案,沒有elf頭,zImage中的最後一句 MOV PC, R4 ; call_kernel,這個R4就是之前kernel解壓的位址,就是直接跳到了Image的相對偏移0位置的指令。
- 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(大)的入口函數,也是體系結構相關的!