天天看點

Linux2.6.35.7核心啟動流程分析

S3C2410 Linux 2.6.35.7啟動分析

1. 依據arch/arm/kernel/vmlinux.lds 生成linux核心源碼根目錄下的vmlinux,這個vmlinux屬于未壓縮,帶調試資訊、符号表的最初的核心,大小約23MB; 

指令:arm-linux-gnu-ld -o vmlinux -T arch/arm/kernel/vmlinux.lds  

arch/arm/kernel/head.o  

init/built-in.o  

--start-group   

arch/arm/mach-s3c2410/built-in.o   

kernel/built-in.o          

mm/built-in.o   

fs/built-in.o   

ipc/built-in.o   

drivers/built-in.o   

net/built-in.o  

--end-group .tmp_kallsyms2.o 

2. 将上面的vmlinux去除調試資訊、注釋、符号表等内容,生成arch/arm/boot/Image,這是不帶多餘資訊的linux核心,Image的大小約3.2MB; 

  指令:arm-linux-gnu-objcopy -O binary -S  vmlinux arch/arm/boot/Image 

3.将 arch/arm/boot/Image 用gzip -9 壓縮生成arch/arm/boot/compressed/piggy.gz大小約1.5MB;          指令:gzip -f -9 < arch/arm/boot/compressed/../Image > arch/arm/boot/compressed/piggy.gz 

4. 編譯arch/arm/boot/compressed/piggy.S 生成arch/arm/boot/compressed/piggy.o大小約1.5MB,這裡實際上是将piggy.gz通過piggy.S編譯進piggy.o檔案中。而piggy.S檔案僅有6行,隻是包含了檔案piggy.gz; 

 指令:arm-linux-gnu-gcc -o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/piggy.S 

5. 依據arch/arm/boot/compressed/vmlinux.lds 将arch/arm/boot/compressed/目錄下的檔案head.o 、piggy.o 、misc.o連結生成 arch/arm/boot/compressed/vmlinux,這個vmlinux是經過壓縮且含有自解壓代碼的核心,大小約1.5MB; 

指令:arm-linux-gnu-ld zreladdr=0x30008000 params_phys=0x30000100 -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux 

6. 将arch/arm/boot/compressed/vmlinux去除調試資訊、注釋、符号表等内容,生成arch/arm/boot/zImage大小約1.5MB;這已經是一個可以使用的linux核心映像檔案了; 

指令:arm-linux-gnu-objcopy -O binary -S  arch/arm/boot/compressed/vmlinux  arch/arm/boot/zImage 

7. 将arch/arm/boot/zImage添加64Bytes的相關資訊打包為arch/arm/boot/uImage大小約1.5MB; 

指令: ./mkimage -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.35.7' -d arch/arm/boot/zImage arch/arm/boot/uImage

Linux2.6.35.7核心啟動流程分析

核心啟動分析:

本文着重分析S3C2410 linux-2.6.35.7 核心啟動的詳細過程,主要包括: zImage 解壓縮階段、 vmlinux 啟動彙編階段、 startkernel 到建立第一個程序階段三個部分,一般将其稱為 linux 核心啟動一、二、三階段,本文也将采用這種表達方式。對于 zImage 之前的啟動過程,本文不做表述,可參考前面正亮講得 “ u-boot的啟動過程分析”。

本文中涉及到的術語約定如下:

基本核心映像:即核心編譯過程中最終在核心源代碼根目錄下生成的 vmlinux 映像檔案,并不包含任何核心解壓縮和重定位代碼;

zImage 核心映像:包含了核心piggy.o及解壓縮和重定位代碼,通常是目标闆 bootloader 加載的對象;

zImage 下載下傳位址:即 bootloader 将 zImage 下載下傳到目标闆記憶體的某個位址或者 nand read 将 zImage 讀到記憶體的某個位址;

zImage 加載位址:由 Linux 的 bootloader 完成的将 zImage 搬移到目标闆記憶體的某個位置所對應的位址值,預設值 0x30008000 。

1、 Linux 核心啟動第一階段:核心解壓縮和重定位

該階段是從 u-boot 引導進入核心執行的第一階段,我們知道 u-boot 引導核心啟動的最後一步是:通過一個函數指針 thekernel()帶三個參數跳轉到核心( zImage )入口點開始執行,此時, u-boot 的任務已經完成,控制權完全交給核心( zImage )。 

稍作解釋,在 u-boot 的檔案arch\arm\lib\bootm.c(uboot-2010.9)中定義了 thekernel, 并在 do_bootm_linux 的最後執行 thekernel.

定義如下:void (*theKernel)(int zero, int arch, uint params); 

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); 

//hdr->ih_ep----Entry Point Address uImage 中指定的核心入口點,這裡是 0x30008000 。 

theKernel (0, bd->bi_arch_number, bd->bi_boot_params); 

其中第二個參數為機器 ID, 第三參數為 u-boot 傳遞給核心參數存放在記憶體中的首位址,此處是 0x30000100 。 

由上述 zImage 的生成過程我們可以知道,第一階段運作的核心映像實際就是arch/arm/boot/compressed/vmlinux,而這一階段所涉及的檔案也隻有三個:   

(1)arch/arm/boot/compressed/vmlinux.lds

(2)arch/arm/boot/compressed/head.S      

(3)arch/arm/boot/compressed/misc.c 

下面的圖是使用64MRAM時,通常的記憶體分布圖:

Linux2.6.35.7核心啟動流程分析

下面我們的分析集中在 arch/arm/boot/compressed/head.S, 适當參考 vmlinux.lds 。

從linux/arch/arm/boot/compressed/vmlinux.lds檔案可以看出head.S的入口位址為ENTRY(_start),也就是head.S彙編檔案的_start标号開始的第一條指令。

下面從head.S中得_start 标号開始分析。(有些指令不影響初始化,暫時略去不分析)

代碼位置在/arch/arm/boot/compressed/head.S中: 

start:

.type start,#function   

.rept 8            

 mov r0, r0            

.endr

b 1f                   @ 跳轉到後面的标号1處

.word 0x016f2818 @ 輔助引導程式的幻數,用來判斷鏡像是否是zImage

.word start @ 加載運作zImage的絕對位址,start表示賦的初值

.word _edata @ zImage結尾位址,_edata是在vmlinux.lds.S中定義的,表示init,text,data三個段的結束位置

1: mov r7, r1 @ save architecture ID 儲存體系結構ID 用r1儲存

mov r8, r2 @ save atags pointer 儲存r2寄存器 參數清單,r0始終為0

mrs r2, cpsr @ get current mode  得到目前模式

tst r2, #3 @ not user?,tst實際上是相與,判斷是否處于使用者模式

bne not_angel            @ 如果不是處于使用者模式,就跳轉到not_angel标号處

mov r0, #0x17 @ angel_SWIreason_EnterSVC,向SWI中傳遞參數

swi 0x123456 @ angel_SWI_ARM這個是讓使用者空間進入SVC空間

not_angel:                                

mrs r2, cpsr @ turn off interrupts to 讀出cpsr寄存器的值放到r2中

orr r2, r2, #0xc0 @ prevent angel from running關閉中斷

msr cpsr_c, r2           @ 把r2的值從新寫回到cpsr中

.text

adr r0, LC0                              

ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp} 

@r0是運作時位址,而r1則是連結時位址,而它們兩都是表示LC0表的起始位置,這樣他們兩的差則是運作和連結的偏移量,糾正了這個偏移量才可以運作與”位址相關的代碼“

subs r0, r0, r1 @ calculate the delta offset 計算偏移量,并放入r0中

beq not_relocated @ if delta is zero, we are running at the address we  were linked at.

@ 如果為0,則不用重定位了,直接跳轉到标号not_relocated處執行

add r5, r5, r0 

add r6, r6, r0 

add ip, ip, r0 

add r2, r2, r0 

add r3, r3, r0 

add sp, sp, r0 

1: ldr r1, [r6, #0] @ relocate entries in the GOT

add r1, r1, r0 @ table.  This fixes up the

str r1, [r6], #4 @ C references.

cmp r6, ip

blo 1b

not_relocated: mov r0, #0 

1: str r0, [r2], #4 @ clear bss 清除bss段

str r0, [r2], #4

str r0, [r2], #4

str r0, [r2], #4

cmp r2, r3

blo 1b

bl cache_on        

@ 這裡的 r1,r2 之間的空間為解壓縮核心程式所使用,也是傳遞給 decompress_kernel 的第二和第三的參數

mov r1, sp @ malloc space above stack

add r2, sp, #0x10000 @ 64k max解壓縮的緩沖區

@下面程式的意義就是保證解壓位址和目前程式的位址不重疊。上面配置設定了64KB的空間來做解壓時的資料緩存。

(2)zImage 的起始位址大于 vmlinux 的目标起始位址加上 vmlinux 大小( 4M )的位址,是以将 zImage 直接解壓到 vmlinux 的目标位址

add r0, r4, #4096*1024 @ 4MB largest kernel size

cmp r0, r5

bls wont_overwrite 

@ 前兩種方案通常都不成立,不會跳轉到wont_overwrite标号處,會繼續走如下分支,其解壓後的記憶體配置設定示意圖如下:

Linux2.6.35.7核心啟動流程分析

mov r5, r2 @ decompress after malloc space

mov r0, r5          

mov r3, r7

bl decompress_kernel

@ decompress_kernel共有4個參數,解壓的核心位址、緩存區首位址、緩存區尾位址、和晶片ID,傳回解壓縮代碼的長度。

decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,

  int arch_id)

{

output_data = (uch *)output_start;

free_mem_ptr = free_mem_ptr_p;     

free_mem_ptr_end = free_mem_ptr_end_p;

__machine_arch_type = arch_id;           

arch_decomp_setup();  

makecrc();                             

putstr("Uncompressing Linux...");

gunzip();                            

putstr(" done, booting the kernel.\n");

return output_ptr;                     

}

add r0, r0, #127 + 128

bic r0, r0, #127 @ align the kernel length對齊核心長度

@ 完成了解壓縮之後,由于核心沒有解壓到正确的位址,最後必須通過代碼搬移來搬到指定的位址0x30008000。搬運過程中有

@ 可能會覆寫掉現在運作的重定位代碼,是以必須将這段代碼搬運到安全的地方,

@ 這裡搬運到的位址是解壓縮了的代碼的後面r5+r0的位置。

add r1, r5, r0 @ end of decompressed kernel 解壓核心的結束位址

adr r2, reloc_start

ldr r3, LC1             @ LC1: .word reloc_end - reloc_start 表示reloc_start段代碼的大小

add r3, r2, r3

1: ldmia r2!, {r9 - r14}     @ copy relocation code

stmia r1!, {r9 - r14}

ldmia r2!, {r9 - r14}

stmia r1!, {r9 - r14}

cmp r2, r3

blo 1b

bl cache_clean_flush  @清 cache

ARM(add pc, r5, r0)                     @ call relocation code 跳轉到重定位代碼開始執行

@ 在此處會調用重定位代碼reloc_start來将Image 的代碼從緩沖區r5幫運到最終的目的地r4:0x30008000處

reloc_start: add r9, r5, r0         @r9中存放的是臨時解壓核心的末尾位址

sub r9, r9, #128      @ 不拷貝堆棧

mov r1, r4      @r1中存放的是目的位址0x30008000

1:

.rept 4

ldmia r5!, {r0, r2, r3, r10 - r14} @ relocate kernel

stmia r1!, {r0, r2, r3, r10 - r14} 

.endr

cmp r5, r9

blo 1b

mov sp, r1                            

add sp, sp, #128              @ relocate the stack

call_kernel: bl cache_clean_flush    @清除cache             

bl cache_off            @關閉cache

mov r0, #0 @ must be zero

mov r1, r7 @ restore architecture number

mov r2, r8 @ restore atags pointer

@ 這裡就是最終我們從zImage跳轉到Image的偉大一跳了,跳之前準備好r0,r1,r2

mov pc, r4 @ call kernel

到此kernel的第一階段zImage 解壓縮階段已經執行完。

第二階段的代碼是從\arch\arm\kernel\head.S開始的。

核心啟動第二階段主要完成的工作有,cpu ID檢查,machine ID(也就是開發闆ID)檢查,建立初始化頁表,設定C代碼運作環境,跳轉到核心第一個真正的C函數startkernel開始執行。

這一階段涉及到兩個重要的結構體:

(1) 一個是struct proc_info_list 主要描述CPU相關的資訊,定義在檔案arch\arm\include\asm\procinfo.h中,與其相關的函數及變量在檔案arch/arm/mm/proc_arm920.S中被定義和指派。

(2) 另一個結構體是描述開發闆或者說機器資訊的結構體struct machine_desc,定義在\arch\arm\include\asm\mach\arch.h檔案中,其函數的定義和變量的指派在闆極相關檔案arch/arm/mach-s3c2410/mach-smdk2410.c中實作,這也是核心移植非常重要的一個檔案。

該階段一般由前面的解壓縮代碼調用,進入該階段要求:

 MMU = off, D-cache = off, I-cache = dont care,r0 = 0, r1 = machine id.

所有的機器ID清單儲存在arch/arm/tools/mach-types 檔案中,在編譯時會将這些機器ID按照統一的格式連結到基本核心映像檔案vmlinux的__arch_info_begin和__arch_info_end之間的段中。存儲格式定義在include/asm-arm/mach/arch.h檔案中的結構體struct machine_desc {}。這兩個結構體的内容最終會被連接配接到基本核心映像vmlinux中的兩個段内,分别是*(.proc.info.init)和*(.arch.info.init),可以參考下面的連接配接腳本。

連結腳本:arch/arm/kernel/vmlinux.lds

*****************************連結腳本**************************************

SECTIONS

{

. = TEXTADDR;

.init : { 

_stext = .;

_sinittext = .;

*(.init.text)

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init)

__arch_info_end = .;

__tagtable_begin = .;

*(.taglist.init)

__tagtable_end = .;

. = ALIGN(16);

__setup_start = .;

*(.init.setup)

__setup_end = .;

__early_begin = .;

*(.early_param.init)

__early_end = .;

__initcall_start = .;

*(.initcall1.init)

*(.initcall2.init)

*(.initcall3.init)

*(.initcall4.init)

*(.initcall5.init)

*(.initcall6.init)

*(.initcall7.init)

__initcall_end = .;

__con_initcall_start = .;

*(.con_initcall.init)

__con_initcall_end = .;

__security_initcall_start = .;

*(.security_initcall.init)

__security_initcall_end = .;

. = ALIGN(32);

__initramfs_start = .;

usr/built-in.o(.init.ramfs)

__initramfs_end = .;

. = ALIGN(64);

__per_cpu_start = .;

*(.data.percpu)

__per_cpu_end = .;

#ifndef CONFIG_XIP_KERNEL

__init_begin = _stext;

*(.init.data)

. = ALIGN(4096);

__init_end = .;

#endif

}

*****************************連結腳本**************************************

下面開始代碼\arch\arm\kernel\head.S的注釋:

開始分析前先看下一點基礎知識:

1. kernel運作的史前時期和記憶體布局

在arm平台下,zImage.bin壓縮鏡像是由bootloader加載到實體記憶體,然後跳到zImage.bin裡一段程式,它專門于将被壓縮的kernel解壓縮到KERNEL_RAM_PADDR開始的一段記憶體中,接着跳進真正的kernel去執行。該kernel的執行起點是stext函數,定義于arch/arm/kernel/head.S。此時記憶體的布局如下圖所示

Linux2.6.35.7核心啟動流程分析

在開發闆3c2410中,SDRAM連接配接到記憶體控制器的Bank6中,它的開始記憶體位址是0x30000000,大小為64M,即0x20000000。 ARM Linux kernel将SDRAM的開始位址定義為PHYS_OFFSET。經bootloader加載kernel并由自解壓部分代碼運作後,最終kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)位址上的一段記憶體,經此放置後,kernel代碼以後均不會被移動。

在進入kernel代碼前,即bootloader和自解壓縮階段,ARM未開啟MMU功能。是以kernel啟動代碼一個重要功能是設定好相應的頁表,并開啟MMU功能。為了支援MMU功能,kernel鏡像中的所有符号,包括代碼段和資料段的符号,在連結時都生成了它在開啟MMU時,所在實體記憶體位址映射到的虛拟記憶體位址。

以arm kernel第一個符号(函數)stext為例,在編譯連結,它生成的虛拟位址是0xc0008000,而放置它的實體位址為0x30008000(還記得這是PHYS_OFFSET+TEXT_OFFSET嗎?)。實際上這個變換可以利用簡單的公式進行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最終的kernel空間的頁表,就是按照這個關系來建立。

之是以較早提及arm linux 的記憶體映射,原因是在進入kernel代碼,裡面所有符号位址值為清一色的0xCXXXXXXX位址,而此時ARM未開啟MMU功能,故在執行stext函數第一條執行時,它的PC值就是stext所在的記憶體位址(即實體位址,0x30008000)。是以,下面有些代碼,需要使用位址無關技術。

__HEAD  /*該宏定義了下面的代碼位于".head.text"段内*/

.type stext, %function                           

ENTRY(stext)                                      

setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9  @ ensure svc mode and irqs disabled 進入超級權限模式,關中斷

mrc p15, 0, r9, c0, c0                  @ get processor id 取出cpu id

bl __lookup_processor_type           @ r5=procinfo r9=cpuid

__lookup_processor_type函數的具體解析開始(\arch\arm\kernel\ head-common.S)

在講解該程式段之前先來看一些相關知識,核心所支援的每一種CPU 類型都由結構體proc_info_list來描述。 

該結構體在檔案arch/arm/include/asm/procinfo.h 中定義: 

struct proc_info_list { 

unsigned int cpu_val; 

unsigned int cpu_mask; 

unsigned long __cpu_mm_mmu_flags;  

unsigned long __cpu_io_mmu_flags;  

unsigned long __cpu_flush;         

const char *arch_name; 

const char *elf_name; 

unsigned int elf_hwcap; 

const char *cpu_name; 

struct processor *proc; 

struct cpu_tlb_fns *tlb; 

struct cpu_user_fns *user; 

struct cpu_cache_fns *cache; 

}; 

對于 arm920 來說,其對應結構體在檔案 linux/arch/arm/mm/proc-arm920.S 中初始化。 

.section ".proc.info.init", #alloc, #execinstr 

.type __arm920_proc_info,#object              

__arm920_proc_info:                            

.long 0x41009200

.long 0xff00fff0

.long  PMD_TYPE_SECT | \

PMD_SECT_BUFFERABLE | \

PMD_SECT_CACHEABLE | \

PMD_BIT4 | \

PMD_SECT_AP_WRITE | \

PMD_SECT_AP_READ

.long  PMD_TYPE_SECT | \

PMD_BIT4 | \

PMD_SECT_AP_WRITE | \

PMD_SECT_AP_READ

b __arm920_setup

…………………………………

.section ".proc.info.init"表明了該結構在編譯後存放的位置。在連結檔案 arch/arm/kernel/vmlinux.lds 中: 

SECTIONS 

#ifdef CONFIG_XIP_KERNEL 

. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR); 

#else 

. = PAGE_OFFSET + TEXT_OFFSET; 

#endif 

.text.head : { 

_stext = .; 

_sinittext = .; 

*(.text.head) 

}

.init : {  

INIT_TEXT 

_einittext = .; 

__proc_info_begin = .; 

*(.proc.info.init) 

__proc_info_end = .; 

__arch_info_begin = .; 

*(.arch.info.init) 

__arch_info_end = .; 

__tagtable_begin = .; 

*(.taglist.init) 

__tagtable_end = .; 

……………………………… 

} 

所有CPU類型對應的被初始化的 proc_info_list結構體都放在 __proc_info_begin和__proc_info_end之間。 

/ *

* r9 = cpuid

*  Returns:

* r5 = proc_info pointer in physical address space

* r9 = cpuid (preserved)

*/

__lookup_processor_type:

adr r3, 3f                     @r3存儲的是标号 3 的實體位址(由于沒有啟用 mmu ,是以目前肯定是實體位址) 

ldmia r3, {r5 - r7}              @ R5=__proc_info_begin,r6=__proc_info_end,r7=标号4處的虛拟位址,即4: .long . 處的位址

add r3, r3, #8                 @ 得到4處的實體位址,剛好是跳過兩條指令

sub r3, r3, r7       @ get offset between virt&phys得到虛拟位址和實體位址之間的offset

add r5, r5, r3 @ convert virt addresses to

add r6, r6, r3 @ physical address space

1: ldmia r5, {r3, r4} @ value, mask  r3= cpu_val , r4= cpu_mask

and r4, r4, r9 @ mask wanted bits;r9 中存放的是先前讀出的 processor ID ,此處屏蔽不需要的位

teq r3, r4                      @ 檢視代碼和CPU 硬體是否比對( 比如想在arm920t上運作為cortex-a8編譯的核心?不讓)

beq 2f                          @ 如果相等則跳轉到标号2處,執行傳回指令

add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list結構的長度,在這等于48)如果沒找到, 跳到下一個proc_info_list 處

cmp r5, r6                             @ 判斷是不是到了該段的結尾

blo 1b                                 @ 如果沒有,繼續跳到标号1處,查找下一個

mov r5, #0        @ unknown processor ,如果到了結尾,沒找到比對的,就把0指派給r5,然後傳回

2: mov pc, lr                             @ 找到後傳回,r5指向找到的結構體

ENDPROC(__lookup_processor_type)

.align 2

3: .long __proc_info_begin

.long __proc_info_end

4: .long .                                  @“.”表示目前這行代碼編譯連接配接後的虛拟位址

.long __arch_info_begin

.long __arch_info_end

__lookup_processor_type函數的具體解析結束(\arch\arm\kernel\ head-common.S)

movs r10, r5                     @ invalid processor (r5=0)?

beq __error_p @ yes, error 'p'

__lookup_machine_type函數的具體解析開始(\arch\arm\kernel\ head-common.S)

每一個CPU 平台都可能有其不一樣的結構體,描述這個平台的結構體是 machine_desc 。 

這個結構體在檔案arch/arm/include/asm/mach/arch.h 中定義: 

struct machine_desc { 

unsigned int nr;           

unsigned int phys_io;  

……………………………… 

}; 

對于平台smdk2410 來說其對應 machine_desc 結構在檔案linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化: 

MACHINE_START(SMDK2410, "SMDK2410")  

.phys_io = S3C2410_PA_UART, 

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, 

.boot_params = S3C2410_SDRAM_PA + 0x100, 

.map_io = smdk2410_map_io, 

.init_irq = s3c24xx_init_irq, 

.init_machine = smdk2410_init, 

.timer = &s3c24xx_timer, 

MACHINE_END 

對于宏MACHINE_START 在檔案 arch/arm/include/asm/mach/arch.h 中定義: 

#define MACHINE_START(_type,_name) / 

static const struct machine_desc __mach_desc_##_type / 

 __used / 

 __attribute__((__section__(".arch.info.init"))) = { / 

.nr = MACH_TYPE_##_type, / 

.name = _name, 

#define MACHINE_END / 

}; 

__attribute__((__section__(".arch.info.init")))表明該結構體在并以後存放的位置。 

在連結檔案 連結腳本檔案 arch/arm/kernel/vmlinux.lds 中 

SECTIONS 

#ifdef CONFIG_XIP_KERNEL 

. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR); 

#else 

. = PAGE_OFFSET + TEXT_OFFSET; 

#endif 

.text.head : { 

_stext = .; 

_sinittext = .; 

*(.text.head) 

}

.init : {  

INIT_TEXT 

_einittext = .; 

__proc_info_begin = .; 

*(.proc.info.init) 

__proc_info_end = .; 

__arch_info_begin = .; 

*(.arch.info.init) 

__arch_info_end = .; 

……………………………… 

} 

在__arch_info_begin和 __arch_info_end之間存放了linux核心所支援的所有平台對應的 machine_desc 結構體。 

__lookup_machine_type:

adr r3, 4b                      @ 把标号4處的位址放到r3寄存器裡面

ldmia r3, {r4, r5, r6}            @ R 4 = 标号4處的虛拟位址 ,r 5 = __arch_info_begin ,r 6= __arch_info_end

sub r3, r3, r4 @ get offset between virt&phys 計算出虛拟位址與實體位址的偏移

add r5, r5, r3 @ convert virt addresses to

add r6, r6, r3 @ physical address space

1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type

teq r3, r1 @ matches loader number?把取到的machine id和從uboot中傳過來的machine id(存放r1中)相比較

beq 2f @ found 如果相等,則跳到标号2處,傳回

add r5, r5, #SIZEOF_MACHINE_DESC@ next machine_desc 沒有找到,則繼續找下一個,加上該結構體的長度

cmp r5, r6                      @ 判斷是否已經到該段的末尾

blo 1b                          @ 如果沒有,則跳轉到标号1處,繼續查找

mov r5, #0 @ unknown machine 如果已經到末尾,并且沒找到,則傳回值r5寄存器指派為0

2: mov pc, lr                      @ 傳回原函數,且r5作為傳回值

ENDPROC(__lookup_machine_type)

.align 2

3: .long __proc_info_begin

.long __proc_info_end

4: .long .                                  @“.”表示目前這行代碼編譯連接配接後的虛拟位址

.long __arch_info_begin

.long __arch_info_end

__lookup_machine_type函數的具體解析結束(\arch\arm\kernel\ head-common.S)

movs r8, r5 @ invalid machine (r5=0)?

beq __error_a @ yes, error 'a'

bl __vet_atags

__vet_atags函數的具體解析開始(\arch\arm\kernel\ head-common.S)

關于參數連結清單: 

核心參數連結清單的格式和說明可以從核心源代碼目錄樹中的\arch\arm\include\asm\setup.h中找到,參數連結清單必須以ATAG_CORE開始,以ATAG_NONE結束。這裡的 ATAG_CORE,ATAG_NONE是各個參數的标記,本身是一個32 位值,例如: ATAG_CORE=0x54410001 。 其它的參數标記還包括: ATAG_MEM32  ,  ATAG_INITRD  ,  ATAG_RAMDISK  , ATAG_COMDLINE 等。每個參數标記就代表一個參數結構體,由各個參數結構體構成了參數連結清單。參數結構體的定義如下:   

struct tag { 

      struct  tag_header  hdr; 

      union { 

             struct tag_core  core; 

             struct tag_mem32  mem; 

          struct tag_videotext videotext; 

          struct tag_ramdisk   ramdisk; 

          struct tag_initrd    initrd; 

          struct tag_serialnr  serialnr; 

          struct tag_revision  revision; 

          struct tag_videolfb  videolfb; 

          struct tag_cmdline   cmdline; 

          struct tag_acorn     acorn; 

          struct tag_memclk    memclk; 

        } u; 

}; 

參數結構體包括兩個部分,一個是 tag_header 結構體 , 一個是 u 聯合體。 

tag_header結構體的定義如下:  

struct tag_header {  

                 u32 size;    

                 u32 tag;  

};  

其中 size :表示整個  tag  結構體的大小 ( 用字的個數來表示,而不是位元組的個數 ) ,等于tag_header的大小加上  u 聯合體的大小,例如,參數結構體  ATAG_CORE  的size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通過函數  tag_size(struct * tag_xxx) 來獲得每個參數結構體的 size 。其中  tag :表示整個  tag  結構體的标記,如: ATAG_CORE 等。

__vet_atags:

tst r2, #0x3 @ aligned? r2指向該參數連結清單的起始位置,此處判斷它是否字對齊

bne 1f                          @ 如果沒有對齊,跳到标号1處直接傳回,并且把r2的值指派為0,作為傳回值

ldr r5, [r2, #0] @ is first tag ATAG_CORE? 擷取第一個 tag 結構的 size

cmp r5, #ATAG_CORE_SIZE         @ 判斷該 tag 的長度是否合法

cmpne r5, #ATAG_CORE_SIZE_EMPTY   

bne 1f                          @ 如果不合法,異常傳回

ldr r5, [r2, #4]                @ 擷取第一個 tag 結構體的标記

ldr r6, =ATAG_CORE              @ 取出标記ATAG_CORE的内容

cmp r5, r6                      @ 判斷該标記是否等于ATAG_CORE

bne 1f                          @ 如果不等,異常傳回

mov pc, lr @ atag pointer is ok,如果都相等,則正常傳回

1: mov r2, #0                      @ 異常傳回值

mov pc, lr @ 異常傳回

ENDPROC(__vet_atags)

__vet_atags函數的具體解析結束(\arch\arm\kernel\ head-common.S)

/*建立核心初始化頁表*/

bl __create_page_tables

__create_page_tables函數的具體解析開始(\arch\arm\kernel\ head.S)

#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)

.macro pgtbl, rd

ldr\rd, =(KERNEL_RAM_PADDR - 0x4000)

.endm

其中:PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定義,為UL(0x30000000),而TEXT_OFFSET在arch/arm/Makefile中定義,為核心鏡像在記憶體中到記憶體開始位置的偏移(位元組),為$(textofs-y) textofs-y也在檔案arch/arm/Makefile中定義,為textofs-y   := 0x00008000,r4 = 30004000為臨時頁表的起始位址,首先即是初始化16K的頁表,高12位虛拟位址為頁表索引,每個頁表索引占4個位元組,是以為4K*4 = 16K,大頁表,每一個頁表項,映射1MB虛拟位址.

__create_page_tables:

pgtbl r4 @ r4中存放的為頁表的基位址,最終該位址會寫入cp15的寄存器c2,這個值必須是 16K 對齊的

mov r0, r4                      @ 把頁表的基位址存放到r0中

mov r3, #0                      @ 把r3清0

add r6, r0, #0x4000             @ r6指向16K的末尾

1: str r3, [r0], #4                @ 把16K的頁表空間清0

str r3, [r0], #4

str r3, [r0], #4

str r3, [r0], #4

teq r0, r6

bne 1b

ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

mov r6, pc

mov r6, r6, lsr #20 @ start of kernel section

orr r3, r7, r6, lsl #20 @ flags + kernel base

str r3, [r4, r6, lsl #2] @ identity mapping

add r0, r4,  #(KERNEL_START & 0xff000000) >> 18   @ r0 = 0x30007000 r0存放的是轉換表的起始位置

str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! @ r3存放的是核心鏡像代碼段的起始位址

ldr r6, =(KERNEL_END - 1)                         @ 擷取核心的尾部虛拟位址存于r6中

add r0, r0, #4                                    @ 第一個位址條目存放在 0x30007004 處,以後依次遞增

add r6, r4, r6, lsr #18                           @ 計算最後一個位址條目存放的位置

1: cmp r0, r6                                        @ 填充這之間的位址條目

add r3, r3, #1 << 20                              

strls r3, [r0], #4

bls 1b

…………………………………

…………………………………………

add r0, r4, #PAGE_OFFSET >> 18

orr r6, r7, #(PHYS_OFFSET & 0xff000000)  @ r6= 0x30000c1e

.if (PHYS_OFFSET & 0x00f00000)

orr r6, r6, #(PHYS_OFFSET & 0x00f00000)

.endif

str r6, [r0]                            @ 将0x30000c1e 存于0x30007000處。

………………………

………………………………

mov pc, lr                              @子程式傳回

ENDPROC(__create_page_tables)

__create_page_tables函數的具體解析結束(\arch\arm\kernel\ head.S)

ldr r13, __switch_data @ address to jump to after mmu has been enabled

adr lr, BSYM(__enable_mmu) @ return (PIC) address

__enable_mmu函數的具體解析開始(\arch\arm\kernel\ head.S)

__enable_mmu: 

#ifdef CONFIG_ALIGNMENT_TRAP 

orr r0, r0, #CR_A   //使能位址對齊錯誤檢測 

#else 

bic r0, r0, #CR_A 

#endif 

#ifdef CONFIG_CPU_DCACHE_DISABLE 

bic r0, r0, #CR_C   //禁止資料 cache 

#endif 

#ifdef CONFIG_CPU_BPREDICT_DISABLE 

bic r0, r0, #CR_Z 

#endif 

#ifdef CONFIG_CPU_ICACHE_DISABLE 

bic r0, r0, #CR_I  //禁止指令 cache 

#endif             //配置相應的通路權限并存入 r5 中 

mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | / 

      domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | / 

      domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | / 

      domain_val(DOMAIN_IO, DOMAIN_CLIENT)) 

mcr p15, 0, r5, c3, c0, 0 //将通路權限寫入協處理器 

mcr p15, 0, r4, c2, c0, 0 //将頁表基位址寫入基址寄存器 C2 , 0X30004000 

b __turn_mmu_on          //跳轉到程式段去打開 MMU 

ENDPROC(__enable_mmu) 

檔案linux/arch/arm/kernel/head.S 中 

__turn_mmu_on: 

mov r0, r0 

mcr p15, 0, r0, c1, c0, 0 //打開 MMU 同時打開 cache 等。 

mrc p15, 0, r3, c0, c0, 0 @ read id reg 讀取 id 寄存器 

mov r3, r3 

mov r3, r3    //兩個空操作,等待前面所取的指令得以執行。 

mov pc, r13  //程式跳轉 

ENDPROC(__turn_mmu_on) 

__enable_mmu函數的具體解析結束(\arch\arm\kernel\ head.S)

  ARM( add pc, r10, #PROCINFO_INITFUNC )

__arm920_setup函數的具體解析開始(\arch\arm\mm\ proc-arm920.S)

在上面程式段.section ".text.head", "ax" 的最後有這樣幾行: 

add pc, r10, #PROCINFO_INITFUNC 

R10中存放的是在函數 __lookup_processor_type 中成功比對的結構體 proc_info_list。對于arm920 來說在檔案 linux/arch/arm/mm/proc-arm920.S 中有: 

.section ".proc.info.init", #alloc, #execinstr 

.type  __arm920_proc_info,#object 

__arm920_proc_info: 

.long 0x41009200 

.long 0xff00fff0 

.long   PMD_TYPE_SECT | / 

PMD_SECT_BUFFERABLE | / 

PMD_SECT_CACHEABLE | / 

PMD_BIT4 | / 

PMD_SECT_AP_WRITE | / 

PMD_SECT_AP_READ 

.long   PMD_TYPE_SECT | / 

PMD_BIT4 | / 

PMD_SECT_AP_WRITE | / 

PMD_SECT_AP_READ 

b __arm920_setup 

……………………………… 

add pc, r10, #PROCINFO_INITFUNC的意思跳到函數 __arm920_setup去執行。 

.type __arm920_setup, #function  //表明這是一個函數 

__arm920_setup: 

mov r0, #0                      //設定 r0 為 0 。 

mcr p15, 0, r0, c7, c7          //使資料 cahche,  指令 cache 無效。 

mcr p15, 0, r0, c7, c10, 4      //使 write buffer 無效。 

#ifdef CONFIG_MMU 

mcr p15, 0, r0, c8, c7          //使資料 TLB, 指令 TLB 無效。 

#endif 

adr r5, arm920_crval            //擷取 arm920_crval 的位址,并存入 r5 。 

ldmia r5, {r5, r6}              //擷取 arm920_crval 位址處的連續 8 位元組分别存入 r5,r6 。 

mrc p15, 0, r0, c1, c0          //擷取 CP15 下控制寄存器的值,并存入 r0 。 

bic r0, r0, r5                  //通過檢視 arm920_crval 的值可知該行是清除 r0 中相關位,為以後對這些位的指派做準備 

orr r0, r0, r6                  //設定 r0 中的相關位,即為 mmu 做相應設定。 

mov pc, lr                      //上面有操作 adr lr, __enable_mmu ,此處将跳到程式段 __enable_mmu 處。 

.size __arm920_setup, . - __arm920_setup 

.type arm920_crval, #object 

arm920_crval: 

crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130 

__arm920_setup函數的具體解析結束(\arch\arm\mm\ proc-arm920.S)

ENDPROC(stext)

接着往下分析linux/arch/arm/kernel/head-common.S中:

.type __switch_data, %object      @定義__switch_data為一個對象

__switch_data:

.long __mmap_switched

.long __data_loc @ r4

.long _data @ r5

.long __bss_start @ r6

.long _end @ r7

.long processor_id @ r4

.long __machine_arch_type @ r5

.long __atags_pointer @ r6

.long cr_alignment @ r7

.long init_thread_union + THREAD_START_SP @ sp

********************************** vmlinux.lds開始*******************************************

 SECTIONS 

 { 

 …………………… 

 #ifdef CONFIG_XIP_KERNEL 

 __data_loc = ALIGN(4);  

 . = PAGE_OFFSET + TEXT_OFFSET; 

 #else 

 . = ALIGN(THREAD_SIZE); 

  __data_loc = .; 

 #endif 

 .data : AT(__data_loc) {  //此處資料存儲在上面__data_loc處。 

  _data = .;   

  *(.data.init_task) 

………………………… 

.bss : { 

__bss_start = .;  

*(.bss) 

*(COMMON) 

_end = .; 

……………………………… 

} 

init_thread_union 是 init程序的基位址.在 arch/arm/kernel/init_task.c 中: 

union thread_union init_thread_union __attribute__((__section__(".init.task"))) = { INIT_THREAD_INFO(init_task) };         

對照 vmlnux.lds.S 中,我們可以知道init task是存放在 .data 段的開始8k, 并且是THREAD_SIZE(8k)對齊的 */ 

********************************** vmlinux.lds結束*******************************************

__mmap_switched:

adr r3, __switch_data + 4

ldmia r3!, {r4, r5, r6, r7}

……………………

………………………………

mov fp, #0 @ 清除bss段

1: cmp r6, r7

strcc fp, [r6],#4

bcc 1b

 ARM( ldmia r3, {r4, r5, r6, r7, sp})  

str r9, [r4] @ Save processor ID 儲存處理器id到processor_id所在的位址中

str r1, [r5] @ Save machine type 儲存machine  id到__machine_arch_type中

str r2, [r6] @ Save atags pointer 儲存參數清單首位址到__atags_pointer中

bic r4, r0, #CR_A @ Clear 'A' bit

stmia r7, {r0, r4} @ Save control register values

b start_kernel                @程式跳轉到函數 start_kernel 進入 C 語言部分。

ENDPROC(__mmap_switched)

到處我們的啟動的第二階段分析完畢。

繼續閱讀