arm-linux kernel啟動過程分析(1)-start_kernel之前第一步

來源:互聯網
上載者:User

標籤:des   style   blog   http   io   ar   color   使用   sp   

前段時間移植uboot仔細研究過uboot啟動過程,最近耐不住寂寞,又想對kernel下手。
Uboot啟動過程分析博文串連如下:
http://blog.csdn.net/skyflying2012/article/details/25804209

kernel啟動過程一般不需要我們修改,研究這個對於編寫driver也沒有多大協助,但對瞭解整個linux架構,各種機制還是非常有用。
如一句心靈雞湯所說,只有瞭解一個人如何成長,才能看清他是什麼樣的人(語文不好,大體意思如此。。)

只有知道kernel如何啟動,我們才能真正的去理解kernel


作為一個嵌入式工作者,我想不能僅僅局限於某個module driver,而應深入到kernel的汪洋大海中去傲遊!


學習啟動過程,我本著打破沙鍋問到底的原則,希望能研究的明明白白,但也鑒於水平有限,還是有很多紕漏之處

共用博文,希望大家多多交流指正,辛苦整理,如需轉載,還請註明出處。


這段時間主要學習start_kernel之前的啟動代碼,區區上百行彙編,但是卻蘊含著很多精髓。
Start_kernel前代碼分3部分來分析,今天先來學習前幾十行!

Kernel版本號碼:3.4.55

在arch/arm/kernel/head.S中,如下:

.arm    __HEADENTRY(stext) THUMB( adr r9, BSYM(1f)    )   @ Kernel is always entered in ARM. THUMB( bx  r9      )   @ If this is a Thumb-2 kernel, THUMB( .thumb          )   @ switch to Thumb now. THUMB(1:           )    //處理器進入svc模式,關閉中斷    setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode                        @ and irqs disabled    //擷取處理器ID    mrc p15, 0, r9, c0, c0      @ get processor id    bl  __lookup_processor_type     @ r5=procinfo r9=cpuid    //將proc_type_list pointer存在r10中,如果為NULL,則error_p    movs    r10, r5             @ invalid processor (r5=0)? THUMB( it  eq )        @ force fixup-able long branch encoding    beq __error_p           @ yes, error 'p'    //CONFIG_ARM_LPAE不太明白含義,我使用處理器設定檔沒有選擇該項,感興趣朋友可以研究下#ifdef CONFIG_ARM_LPAE    mrc p15, 0, r3, c0, c1, 4       @ read ID_MMFR0    and r3, r3, #0xf            @ extract VMSA support    cmp r3, #5              @ long-descriptor translation table format? THUMB( it  lo )                @ force fixup-able long branch encoding    blo __error_p           @ only classic page table format#endif#ifndef CONFIG_XIP_KERNEL    //擷取物理地址與虛擬位址的offset,存在r8中    adr r3, 2f    ldmia   r3, {r4, r8}    sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)    add r8, r8, r4          @ PHYS_OFFSET#else    //定義CONFIG_XIP_KERNEL,offset為PHYS_OFFSET    ldr r8, =PHYS_OFFSET        @ always constant in this case#endif    /*     * r1 = machine no, r2 = atags or dtb,     * r8 = phys_offset, r9 = cpuid, r10 = procinfo     */    //對bootloader傳來的tags參數進行檢查    bl  __vet_atags
Kernel的入口函數是哪個,入口地址在哪,需要根據串連指令碼來確定。
在arch/arm/kernel/vmlinux.lds.S,如下:
OUTPUT_ARCH(arm)ENTRY(stext)#ifndef __ARMEB__jiffies = jiffies_64;#elsejiffies = jiffies_64 + 4;#endifSECTIONS{........#ifdef CONFIG_XIP_KERNEL    . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);#else    . = PAGE_OFFSET + TEXT_OFFSET;#endif}
入口函數是head.S中的stext,不採用XIP技術,入口地址是PAGE_OFFSET+TEXT_OFFSET。
./arch/arm/include/asm/memory.h中:

#define PAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)Menuconfig中CONFIG_PAGE_OFFSET = 0xc0000000./arch/arm/Makefile中:textofs-y   := 0x00008000textofs-$(CONFIG_ARCH_CLPS711X) := 0x00028000# We don't want the htc bootloader to corrupt kernel during resumetextofs-$(CONFIG_PM_H1940)      := 0x00108000# SA1111 DMA bug: we don't want the kernel to live in precious DMA-able memoryifeq ($(CONFIG_ARCH_SA1100),y)textofs-$(CONFIG_SA1111) := 0x00208000endiftextofs-$(CONFIG_ARCH_MSM7X30) := 0x00208000textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000textofs-$(CONFIG_ARCH_MSM8960) := 0x00208000......# The byte offset of the kernel image in RAM from the start of RAM.TEXT_OFFSET := $(textofs-y)
入口地址是0xc0008000.
但是實際操作中,kernel是載入到0x80008000地址啟動並執行。
(我使用處理器sdram物理起始地址是0x80000000)
為什麼連結地址和運行地址不一致?
學習完start_kernel之前的彙編,就會明白原因了。
在stext中,開始會調用到__lookup_processor_type,代碼如下:
   __CPUINIT__lookup_processor_type:    //3行彙編,計算出物理地址與虛擬位址之間的offset,存在r3中    adr r3, __lookup_processor_type_data    ldmia   r3, {r4 - r6}    sub r3, r3, r4          @ get offset between virt&phys    //擷取__proc_info_begin的物理地址    add r5, r5, r3          @ convert virt addresses to    //擷取__proc_info_end的物理地址    add r6, r6, r3          @ physical address space    //mask cp15讀出的cpuid,與proc_type_list中value對比1:  ldmia   r5, {r3, r4}            @ value, mask    and r4, r4, r9          @ mask wanted bits    teq r3, r4    //一致則返回,不一致則跳到下一個proc_type_list,繼續對比    beq 2f    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)    cmp r5, r6    blo 1b    //匹配成功,r5存該proc_type_list指標,匹配失敗,r5置0    mov r5, #0              @ unknown processor2:  mov pc, lrENDPROC(__lookup_processor_type)/* * Look in <asm/procinfo.h> for information about the __proc_info structure. */    .align  2    .type   __lookup_processor_type_data, %object__lookup_processor_type_data:    .long   .    .long   __proc_info_begin    .long   __proc_info_end    .size   __lookup_processor_type_data, . - __lookup_processor_type_data</span>
因為kernel要開啟MMU,所以kernel編譯連結地址是虛擬位址(物理地址經過MMU轉換後CPU看到的地址),並不是物理地址,
 連結確定了變數的絕對位址(虛擬位址),但在現階段,沒開啟MMU,CPU看到的sdram地址就是其物理地址(0x80000000起始)。
 如果直接運行,對於變數的定址則會出現問題(函數定址沒問題,因為arm函數定址使用相對跳轉指令b bl)
 比如,kernel image中全域變數i連結地址在0xc0009000,但現階段i物理地址是在0x80009000,對於CPU來說,只能在0x80009000上才能找到i。
 去0xc0009000定址,程式運行就出錯了。
 這就是為什麼我們所理解的,連結地址 載入地址 運行地址必須一致的原因。


 kernel現階段給出的解決方案,就是lookup_processor_type前3行彙編:

adr r3, __lookup_processor_type_data 載入__lookup_processor_type_data地址(實際運行地址,這裡就是物理地址)到r3

ldmia r3, {r4 - r6} 擷取以r3 r3+4 r3+8為地址的變數到r4,r5,r6.
地址變數值是在連結時確定的,所以r4中存的是__lookup_processor_type_data的連結地址(虛擬位址)。

sub r3 ,r3 ,r4     r3中儲存的是物理地址與虛擬位址的位移。


這是多麼genius的操作啊!

_proc_info_begin _proc_info_end在連結指令碼中定義,是.proc.info.init段的首尾。
該段中是proc_info_list struct,表示處理器相關資訊,定義如下:

struct proc_info_list {    unsigned int        cpu_val;    unsigned int        cpu_mask;    unsigned long       __cpu_mm_mmu_flags; /* used by head.S */    unsigned long       __cpu_io_mmu_flags; /* used by head.S */    unsigned long       __cpu_flush;        /* used by head.S */    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;};
該段是在arch/arm/mm/proc-xxx.S中填充,不需要軟體人員修改,感興趣朋友可以研究下。
lookup_processor_type_data返回stext中。
接下來同樣用上面的方法擷取phy&virt offset,存在r8.
根據我之前分析uboot傳參kernel的博文(連結如下:http://blog.csdn.net/skyflying2012/article/details/35787971)
r1儲存machine id,r2儲存atags。
stext中__vet_atags會對atags做一個基本的檢查,代碼如下:
__vet_atags:    tst r2, #0x3            @ aligned?    bne 1f    ldr r5, [r2, #0]    //判斷是否是dtb類型#ifdef CONFIG_OF_FLATTREE    ldr r6, =OF_DT_MAGIC        @ is it a DTB?    cmp r5, r6    beq 2f#endif    cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?    cmpne   r5, #ATAG_CORE_SIZE_EMPTY    bne 1f    ldr r5, [r2, #4]    ldr r6, =ATAG_CORE    cmp r5, r6    bne 1f    //正確tags,返回2:  mov pc, lr              @ atag/dtb pointer is ok    //錯誤tags,清空r2,返回1:  mov r2, #0    mov pc, lrENDPROC(__vet_atags)
檢查tag頭4 byte(tag_core的size)和第二個4 byte(tag_core的type)是否正確。

對於stext中前幾十行彙編,已經分析完成,總結下做了哪些工作:
(1)設定CPU模式
(2)檢查CPUID是否匹配
(3)擷取phy&virt offset
(4)檢查atags參數


這段代碼就分析到這,不過引起了我對於連結地址 運行地址的思考。
一直認為,連結地址 運行地址(載入地址)必須是一致,但是卻沒有真正去思考這個問題。
就像老師告訴我們地球是圓的,我們就認為地球是圓的,而沒有去探究過。

程式的連結地址與運行地址為什麼要一致?

我的理解,連結確定程式運行絕對位址,也確定了其中變數及函數的絕對位址,載入運行地址不是其連結地址,變數實際儲存的地址就變了。
這時如果對變數進行定址,就會有不可知的結果,這是我能想到的原因。
平時我們編譯連結都是一些C語言編寫程式,難免會定義一些全域變數,如果連結和運行地址不一致,就不能正常定址。

如果想運行和連結地址不一致,我能想到的辦法,只能是彙編中盡量不去涉及一些絕對位址,使用PIC位置無關代碼。

聯想之前分析的uboot relocation原理(博文連結:http://blog.csdn.net/skyflying2012/article/details/37660265),

uboot在relocation之後,kernel在開啟MMU之前,都實現了連結地址和運行地址不一致,看看它們用的什麼方法?


(1)uboot在relocation時修改rel.dyn段(儲存所有變數地址),實現將所有變數地址重定位到新運行地址


(2)kernel在開啟MMU之前,計算運行地址(物理地址)與連結地址(虛擬位址)的位移,對變數定址時都進行地址轉換,從而正常找到變數。開啟MMU之後,利用硬體機制,來實現連結和運行地址的統一


所以說,連結地址一定要等於運行地址嗎?不一定,嵌入式最著名的uboot  kernel就是例子!


今天先分析到這,start_kernel之前剩餘部分彙編會再寫2篇文章來分析學習。


arm-linux kernel啟動過程分析(1)-start_kernel之前第一步

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.