Android arm linux kernel啟動(dòng)流程二
前面說(shuō)過(guò)解壓以后,代碼會(huì)跳到解壓完成以后的vmlinux開(kāi)始執(zhí)行,具體從什么地方開(kāi)始執(zhí)行我們可以看看生成的vmlinux.lds(arch/arm/kernel/)這個(gè)文件:
本文引用地址:http://2s4d.com/article/201611/317679.htm view plaincopy to clipboardprint?
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0x80000000 + 0x00008000;
.text.head : {
_stext = .;
_sinittext = .;
*(.text.h
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0x80000000 + 0x00008000;
.text.head : {
_stext = .;
_sinittext = .;
*(.text.h
很明顯我們的vmlinx最開(kāi)頭的section是.text.head,這里我們不能看ENTRY的內(nèi)容,以為這時(shí)候我們沒(méi)有操作系統(tǒng),根本不知道如何來(lái)解析這里的入口地址,我們只能來(lái)分析他的section(不過(guò)一般來(lái)說(shuō)這里的ENTRY和我們從seciton分析的結(jié)果是一樣的),這里的.text.head section我們很容易就能在arch/arm/kernel/head.S里面找到,而且它里面的第一個(gè)符號(hào)就是我們的stext:
view plaincopy to clipboardprint?
.section ".text.head", "ax"
Y(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
.section ".text.head", "ax"
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
這里的ENTRY這個(gè)宏實(shí)際我們可以在include/linux/linkage.h里面找到,可以看到他實(shí)際上就是聲明一個(gè)GLOBAL Symbol,后面的ENDPROC和END唯一的區(qū)別是前面的聲明了一個(gè)函數(shù),可以在c里面被調(diào)用。
view plaincopy to clipboardprint?
#ifndef ENTRY
#define ENTRY(name) /
.globl name; /
ALIGN; /
name:
#endif
#ifndef WEAK
#define WEAK(name) /
.weak name; /
name:
#endif
#ifndef END
#define END(name) /
.size name, .-name
#endif
/* If symbol name is treated as a subroutine (gets called, and returns)
* then please use ENDPROC to mark name as STT_FUNC for the benefit of
* static analysis tools such as stack depth analyzer.
*/
#ifndef ENDPROC
#define ENDPROC(name) /
.type name, @function; /
END(name)
#endif
#ifndef ENTRY
#define ENTRY(name) /
.globl name; /
ALIGN; /
name:
#endif
#ifndef WEAK
#define WEAK(name) /
.weak name; /
name:
#endif
#ifndef END
#define END(name) /
.size name, .-name
#endif
/* If symbol name is treated as a subroutine (gets called, and returns)
* then please use ENDPROC to mark name as STT_FUNC for the benefit of
* static analysis tools such as stack depth analyzer.
*/
#ifndef ENDPROC
#define ENDPROC(name) /
.type name, @function; /
END(name)
#endif
找到了vmlinux的起始代碼我們就來(lái)進(jìn)行分析了,先總體概括一下這部分代碼所完成的功能,head.S會(huì)首先檢查proc和arch以及atag的有效性,然后會(huì)建立初始化頁(yè)表,并進(jìn)行CPU必要的處理以后打開(kāi)MMU,并跳轉(zhuǎn)到start_kernel這個(gè)symbol開(kāi)始執(zhí)行后面的C代碼。這里有很多變量都是我們進(jìn)行kernel移植時(shí)需要特別注意的,下面會(huì)一一講到。
在這里我們首先看看這段匯編開(kāi)始跑的時(shí)候的寄存器信息,這里的寄存器內(nèi)容實(shí)際上是同bootloader跳轉(zhuǎn)到解壓代碼是一樣的,就是r1=arch r2=atag addr。下面我們就具體來(lái)看看這個(gè)head.S跑的過(guò)程:
view plaincopy to clipboardprint?
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
首先進(jìn)入SVC模式并關(guān)閉所有中斷,并從arm協(xié)處理器里面讀到CPU ID,這里的CPU主要是指arm架構(gòu)相關(guān)的CPU型號(hào),比如ARM9,ARM11等等。
view plaincopy to clipboardprint?
然后跳轉(zhuǎn)到__lookup_processor_type,這個(gè)函數(shù)定義在head-common.S里面,這里的bl指令會(huì)保存當(dāng)前的pc在lr里面,最后__lookup_processor_type會(huì)從這個(gè)函數(shù)返回,我們具體看看這個(gè)函數(shù):
view plaincopy to clipboardprint?
__lookup_processor_type:
adr r3, 3f
ldmda r3, {r5 - r7}
sub r3, r3, r7 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
__lookup_processor_type:
adr r3, 3f
ldmda r3, {r5 - r7}
sub r3, r3, r7 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
他這里的執(zhí)行過(guò)程其實(shí)比較簡(jiǎn)單就是在__proc_info_begin和__proc_info_end這個(gè)段里面里面去讀取我們注冊(cè)在里面的proc_info_list這個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體的定義在arch/arm/include/asm/procinfo.h,具體實(shí)現(xiàn)根據(jù)你使用的cpu的架構(gòu)在arch/arm/mm/里面找到具體的實(shí)現(xiàn),這里我們使用的ARM11是proc-v6.S,我們可以看看這個(gè)結(jié)構(gòu)體:
view plaincopy to clipboardprint?
.section ".proc.info.init", #alloc, #execinstr
/*
* Match any ARMv6 processor core.
*/
.type __v6_proc_info, #object
_proc_info:
.long 0x0007b000
.long 0x0007f000
.long PMD_TYPE_SECT | /
PMD_SECT_BUFFERABLE | /
PMD_SECT_CACHEABLE | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | /
PMD_SECT_XN | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
b __v6_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA
.long cpu_v6_name
.long v6_processor_functions
.long v6wbi_tlb_fns
.long v6_user_fns
.long v6_cache_fns
.size __v6_proc_info, . - __v6_proc_info
.section ".proc.info.init", #alloc, #execinstr
/*
* Match any ARMv6 processor core.
*/
.type __v6_proc_info, #object
__v6_proc_info:
.long 0x0007b000
.long 0x0007f000
.long PMD_TYPE_SECT | /
PMD_SECT_BUFFERABLE | /
PMD_SECT_CACHEABLE | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | /
PMD_SECT_XN | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
b __v6_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA
.long cpu_v6_name
.long v6_processor_functions
.long v6wbi_tlb_fns
.long v6_user_fns
.long v6_cache_fns
.size __v6_proc_info, . - __v6_proc_info
對(duì)著.h我們就知道各個(gè)成員變量的含義了,他這里lookup的過(guò)程實(shí)際上是先求出這個(gè)proc_info_list的實(shí)際物理地址,并將其內(nèi)容讀出,然后將其中的mask也就是我們這里的0x007f000與寄存器與之后與0x007b00進(jìn)行比較,如果一樣的話呢就校驗(yàn)成功了,如果不一樣呢就會(huì)讀下一個(gè)proc_info的信息,因?yàn)閜roc一般都是只有一個(gè)的,所以這里一般不會(huì)循環(huán),如果檢測(cè)正確寄存器就會(huì)將正確的proc_info_list的物理地址賦給寄存器,如果檢測(cè)不到就會(huì)將寄存器值賦0,然后通過(guò)LR返回。
view plaincopy to clipboardprint?
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error a
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error a
檢測(cè)完proc_info_list以后就開(kāi)始檢測(cè)machine_type了,這個(gè)函數(shù)的實(shí)現(xiàn)也在head-common.S里面,我們看看它具體的實(shí)現(xiàn):
view plaincopy to clipboardprint?
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, r6}
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?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
ENDPROC(__lookup_machine_type)
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, r6}
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?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
ENDPROC(__lookup_machine_type)
這里的過(guò)程基本上是同proc的檢查是一樣的,這里主要檢查芯片的類(lèi)型,比如我們現(xiàn)在的芯片是MSM7X27FFA,這也是一個(gè)結(jié)構(gòu)體,它的頭文件在arch/arm/include/asm/arch/arch.h里面(machine_desc),它具體的實(shí)現(xiàn)根據(jù)你對(duì)芯片類(lèi)型的選擇而不同,這里我們使用的是高通的7x27,具體實(shí)現(xiàn)在arch/arm/mach-msm/board-msm7x27.c里面,這些結(jié)構(gòu)體最后都會(huì)注冊(cè)到_arch_info_begin和_arch_info_end段里面,具體的大家可以看看vmlinux.lds或者system.map,這里的lookup會(huì)根據(jù)bootloader傳過(guò)來(lái)的nr來(lái)在__arch_info里面的相匹配的類(lèi)型,沒(méi)有的話就尋找下一個(gè)machin_desk結(jié)構(gòu)體,直到找到相應(yīng)的結(jié)構(gòu)體,并會(huì)將結(jié)構(gòu)體的地址賦值給寄存器,如果沒(méi)有的話就會(huì)賦值為0的。一般來(lái)說(shuō)這里的machine_type會(huì)有好幾個(gè),因?yàn)椴煌男酒?lèi)型可能使用的都是同一個(gè)cpu架構(gòu)。
對(duì)processor和machine的檢查完以后就會(huì)檢查atags parameter的有效性,關(guān)于這個(gè)atag具體的定義我們可以在./include/asm/setup.h里面看到,它實(shí)際是一個(gè)結(jié)構(gòu)體和一個(gè)聯(lián)合體構(gòu)成的結(jié)合體,里面的size都是以字來(lái)計(jì)算的。這里的atags param是bootloader創(chuàng)建的,里面包含了ramdisk以及其他memory分配的一些信息,存儲(chǔ)在boot.img頭部結(jié)構(gòu)體定義的地址中,具體的大家可以看咱以后對(duì)bootloader的分析~
view plaincopy to clipboardprint?
__vet_atags:
tst r2, #0x3 @ aligned?
bne 1f
ldr r5, [r2, #0] @ is first tag ATAG_CORE?
cmp r5, #ATAG_CORE_SIZE
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f
ldr r5, [r2, #4]
ldr r6, =ATAG_CORE
cmp r5, r6
bne 1f
mov pc, lr @ atag pointer is ok
1: mov r2, #0
mov pc, lr
ENDPROC(__vet_atags)
__vet_atags:
tst r2, #0x3 @ aligned?
bne 1f
ldr r5, [r2, #0] @ is first tag ATAG_CORE?
cmp r5, #ATAG_CORE_SIZE
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f
ldr r5, [r2, #4]
ldr r6, =ATAG_CORE
cmp r5, r6
bne 1f
mov pc, lr @ atag pointer is ok
1: mov r2, #0
mov pc, lr
ENDPROC(__vet_atags)
這里對(duì)atag的檢查主要檢查其是不是以ATAG_CORE開(kāi)頭,size對(duì)不對(duì),基本沒(méi)什么好分析的,代碼也比較好看~ 下面我們來(lái)看后面一個(gè)重頭戲,就是創(chuàng)建初始化頁(yè)表,說(shuō)實(shí)話這段內(nèi)容我沒(méi)弄清楚,它需要對(duì)ARM VIRT MMU具有相當(dāng)?shù)睦斫猓@里我沒(méi)有太多的時(shí)間去分析spec,只是粗略了翻了ARM V7的manu,知道這里建立的頁(yè)表是arm的secition頁(yè)表,完成內(nèi)存開(kāi)始1m內(nèi)存的映射,這個(gè)頁(yè)表建立在kernel和atag paramert之間,一般是4000-8000之間~具體的代碼和過(guò)程我這里就不貼了,大家可以看看參考的鏈接,看看其他大蝦的分析,我還沒(méi)怎么看明白,等以后仔細(xì)研究ARM MMU的時(shí)候再回頭來(lái)仔細(xì)研究了,不過(guò)代碼雖然不分析,這里有幾個(gè)重要的地址需要特別分析下~
這幾個(gè)地址都定義在arch/arm/include/asm/memory.h,我們來(lái)稍微分析下這個(gè)頭文件,首先它包含了arch/memory.h,我們來(lái)看看arch/arm/mach-msm/include/mach/memory.h,在這個(gè)里面定義了#define PHYS_OFFSET UL(0x00200000) 這個(gè)實(shí)際上是memory的物理內(nèi)存初始地址,這個(gè)地址和我們以前在boardconfig.h里面定義的是一致的。然后我們?cè)倏碼sm/memory.h,他里面定義了我們的memory虛擬地址的首地址#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)。
另外我們?cè)趆ead.S里面看到kernel的物理或者虛擬地址的定義都有一個(gè)偏移,這個(gè)偏移又是從哪來(lái)的呢,實(shí)際我們可以從arch/arm/Makefile里面找到:textofs-y := 0x00008000 TEXT_OFFSET := $(textofs-y) 這樣我們?cè)倏磌ernel啟動(dòng)時(shí)候的物理地址和鏈接地址,實(shí)際上它和我們前面在boardconfig.h和Makefile.boot里面定義的都是一致的~
建立初始化頁(yè)表以后,會(huì)首先將__switch_data這個(gè)symbol的鏈接地址放在sp里面,然后獲得__enable_mmu的物理地址,然后會(huì)跳到__proc_info_list里面的INITFUNC執(zhí)行,這個(gè)偏移是定義在arch/arm/kernel/asm-offset.c里面,實(shí)際上就是取得__proc_info_list里面的__cpu_flush這個(gè)函數(shù)執(zhí)行。
view plaincopy to clipboardprint?
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
這個(gè)__cpu_flush在這里就是我們proc-v6.S里面的__v6_setup函數(shù)了,具體它的實(shí)現(xiàn)我就不分析了,都是對(duì)arm控制寄存器的操作,這里轉(zhuǎn)一下它對(duì)這部分操作的注釋?zhuān)赐曛缶突局浪瓿傻墓δ芰恕?/p>
/*
* __v6_setup
*
* Initialise TLB, Caches, and MMU state ready to switch the MMU
* on. Return in r0 the new CP15 C1 control register setting.
*
* We automatically detect if we have a Harvard cache, and use the
* Harvard cache control instructions insead of the unified cache
* control instructions.
*
* This should be able to cover all ARMv6 cores.
*
* It is assumed that:
* - cache type register is implemented
*/
完成這部分關(guān)于CPU的操作以后,下面就是打開(kāi)MMU了,這部分內(nèi)容也沒(méi)什么好說(shuō)的,也是對(duì)arm控制寄存器的操作,打開(kāi)MMU以后我們就可以使用虛擬地址了,而不需要我們自己來(lái)進(jìn)行地址的重定位,ARM硬件會(huì)完成這部分的工作。打開(kāi)MMU以后,會(huì)將SP的值賦給PC,這樣代碼就會(huì)跳到__switch_data來(lái)運(yùn)行,這個(gè)__switch_data是一個(gè)定義在head-common.S里面的結(jié)構(gòu)體,我們實(shí)際上是跳到它地一個(gè)函數(shù)指針__mmap_switched處執(zhí)行的。
這個(gè)switch的執(zhí)行過(guò)程我們只是簡(jiǎn)單看一下,前面的copy data_loc段以及清空.bss段就不用說(shuō)了,它后面會(huì)將proc的信息和machine的信息保存在__switch_data這個(gè)結(jié)構(gòu)體里面,而這個(gè)結(jié)構(gòu)體將來(lái)會(huì)在start_kernel的setup_arch里面被使用到。這個(gè)在后面的對(duì)start_kernel的詳細(xì)分析中會(huì)講到。另外這個(gè)switch還涉及到控制寄存器的一些操作,這里我不沒(méi)仔細(xì)研究spec,不懂也就不說(shuō)了~
好啦,switch操作完成以后就會(huì)b start_kernel了~ 這樣就進(jìn)入了c代碼的運(yùn)行了,下一篇文章仔細(xì)研究這個(gè)start_kernel的函數(shù)~~
評(píng)論