Uboot启动过程分析博文链接例如如下:linux
移植内核时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
今天先来学习前几十行!
在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?Kernel的入口函数是哪一个,入口地址在哪,需要依据链接脚原本肯定。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
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.
为何连接地址和执行地址不一致?
学习完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起始)。 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)是否正确。
假设想执行和连接地址不一致。我能想到的办法,仅仅能是汇编中尽可能不去涉及一些绝对地址,使用PIC位置无关代码。
联想以前分析的uboot relocation原理(博文连接:http://blog.csdn.net/skyflying2012/article/details/37660265),
uboot在relocation以后,kernel在开启MMU以前,都实现了连接地址和执行地址不一致,看看它们用的什么方法?
(1)uboot在relocation时改动rel.dyn段(存储所有变量地址)。实现将所有变量地址重定位到新执行地址