arm-linux內核start_kernel以前啓動分析(1)-接過bootloader的衣鉢

前段時間移植uboot細緻研究過uboot啓動過程,近期耐不住寂寞。想對kernel下手。


Uboot啓動過程分析博文鏈接例如如下:linux


http://blog.csdn.net/skyflying2012/article/details/25804209


移植內核時kernel啓動過程需要咱們改動的地方比較少。研究這個對於編寫driver也沒有多大幫助,但對了解整個linux架構,各類機制仍是很是實用。數組


僅僅有知道kernel怎樣啓動,咱們才幹真正的去理解kernel架構

做爲一個嵌入式工做者,我想不能僅僅侷限於某個module driver。而應深刻到kernel的汪洋大海中去傲遊!函數


學習啓動過程,我本着打破沙鍋問究竟的原則,但願能研究的明明確白,但也鑑於水平有限。仍是有很是多紕漏之處post

共享博文。但願你們多多交流指正,辛苦整理。如需轉載,還請註明出處。學習

對於arm linux,start_kernel以前都是彙編代碼,區區上百行彙編,但是卻蘊含着很是多精髓。ui

這部分代碼分3篇來分析,另外兩篇連接地址例如如下:this

http://blog.csdn.net/skyflying2012/article/details/41447843spa

http://blog.csdn.net/skyflying2012/article/details/48054417
.net


今天先來學習前幾十行!


Kernel版本:3.4.55

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

.arm

    __HEAD
ENTRY(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;
#else
jiffies = jiffies_64 + 4;
#endif

SECTIONS
{
........
#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   := 0x00008000
textofs-$(CONFIG_ARCH_CLPS711X) := 0x00028000
# We don't want the htc bootloader to corrupt kernel during resume
textofs-$(CONFIG_PM_H1940)      := 0x00108000
# SA1111 DMA bug: we don't want the kernel to live in precious DMA-able memory
ifeq ($(CONFIG_ARCH_SA1100),y)
textofs-$(CONFIG_SA1111) := 0x00208000
endif
textofs-$(CONFIG_ARCH_MSM7X30) := 0x00208000
textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000
textofs-$(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,Kernel代碼將所有CPU信息的定義都放到.proc.info.init段中。所以可以以爲.proc.info.init段就是一個數組,每個元素都定義了一個或一種CPU的信息。

眼下__lookup_processor_type使用該元素的前兩個字段cpuid和mask來匹配當前CPUID。假設知足CPUID & mask == cpuid,則找到當前cpu的定義並返回。

 

代碼例如如下:

   __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 processor
2:  mov pc, lr
ENDPROC(__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中填充,定義了相應arm指令集的處理器特性和初始化函數。在第三篇文章中咱們還會具體來理解proc info的做用,這裏先按下不表。

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, lr
ENDPROC(__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就是樣例!
相關文章
相關標籤/搜索