内核的实际起始函数为 start_kernel() 函数,而后再调用其余函数来执行启动。再调用此函数以前,须要先将经过编译内核得到的 zImage 进行解压,请按成页目录构建等基本任务。javascript
调用 start_kernel 的过程分为如下三个阶段:java
解压缩准备阶段将执行中断禁用、分配动态内存、初始化BBS区域、初始化页目录、打开缓存等任务。api
在该阶段,zImage 解压位置的下级 16KB 构建用于保存页目录的空间,在CP15的c2寄存器中保存页目录的位置。缓存
ARM中,页目录将 4GB 的内存以 1MB 节区为单位进行管理。所以,为了管理 4GB 的内存,须要有 4096 个以 1MB为单位的项。因为以32位的字符为单位管理各项,因此共须要 16KB (4字节 X 4096各项 = 16KB)。以后,向至关于页目录位置的项设置 cacheable 和 bufferable,使页目录获得缓冲并能快速访问。bash
从start 标签到解压缩准备阶段的流程图架构
经过加载项完成对软硬件的默认初始化后,最早执行的是 head.S (arch\arm\boot\compressed) 下的 start 标签中的代码。 完成的主要功能以下:函数
1 start: 2 .type start,#function 3 .rept 7 4 __nop 5 .endr 6 7 mov r0, r0 8 W(b) 1f
使用.type标号来指明start的符号类型是函数类型,而后重复执行.rept到.endr之间的指令7次,这里一共执行了7次mov r0, r0指令,共占用了4*7 = 28个字节,这是用来存放ARM的异常向量表的。向前跳转到标号为1处执行this
2. 保存 cpsr 的值到 r9 中,保存架构 ID 和 atags 指针分别到 r7 和 r8 中。spa
1 1: 2 ARM_BE8( setend be ) @ go BE8 if compiled for BE8 3 AR_CLASS( mrs r9, cpsr ) 4 /* 将启动加载项传递的结构ID和 atags 信息分别保存到寄存器 r7 r8 中 */ 5 mov r7, r1 @ 保存结构ID 6 mov r8, r2 @ 保存 atags 指针
当中还有未贴出来的代码,不相关的3d
这里将CPU的工做模式保存到r9寄存器中,将uboot经过r1传入的机器码保存到r7寄存器中,将启动参数tags的地址保存到r8寄存器中。
3.继续在标签“1”中运行,判断当前 CPU 的工做模式,若不是在用户模式下,则跳转到 "not_angel" 标签处,不然经过 swi 指令产生软中断异常的方式来进入 SVC 模式。
1 2 /* 3 * Booting from Angel - need to enter SVC mode and disable 4 * FIQs/IRQs (numeric definitions from angel arm.h source). 5 * We only do this if we were in user mode on entry. 6 */ 7 mrs r2, cpsr @ 将CPSR状态寄存器读取,保存到R1中,即获取当前CPU模式 8 tst r2, #3 @ 判断CPU是否为用户模式 9 bne not_angel 10 mov r0, #0x17 @ angel_SWIreason_EnterSVC 11 ARM( swi 0x123456 ) @ angel_SWI_ARM 12 THUMB( svc 0xab ) @ angel_SWI_THUMB
这里将CPU的工做模式保存到r2寄存器中,而后判断是不是SVC模式,若是是USER模式就会经过swi指令产生软中断异常的方式来自动进入SVC模式。因为我这里在uboot中已经将CPU的模式设置为SVC模式了,因此就直接跳到not_angel符号处执行。
4.借助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ中断,切换到 SVC 模式;将 r9 中保存的原来的 CPSR 的值保存到 SPSR 中。
1 /* 设置CPU为SVC模式的具体操做 */ 2 not_angel: 3 /* .macro safe_svcmode_maskall reg:req 在 Assembler.h (arch\arm\include\asm)中定义*/ 4 safe_svcmode_maskall r0 5 msr spsr_cxsf, r9 @ Save the CPU boot mode in 6 @ SPSR
(1)借助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ中断,切换到 SVC 模式;将 r9 中保存的原来的 CPSR 的值保存到 SPSR 中。
arch/arm/include/asm/assembler.h
这里的注释已经说明了,这里是强制将CPU的工做模式切换到SVC模式,而且关闭IRQ和FIQ中断。而后将r9中保存的原始CPU配置保存到SPSR中。
1 /* 此处出现的 MODE_MASK、PSR_I_BIT 等常量被宏定义在 arch/arm/include/uapi/asm/ptrace.h */ 2 .macro safe_svcmode_maskall reg:req 3 #if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M) 4 mrs \reg , cpsr 5 eor \reg, \reg, #HYP_MODE 6 tst \reg, #MODE_MASK 7 bic \reg , \reg , #MODE_MASK @ 将模式位M[4:0]清0 8 /* 经过设置低 8 位为 110 10011,达到了关闭 IRQ、FIQ、设置 CPU 工做模式为 SVC 模式的目标 */ 9 orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE 10 THUMB( orr \reg , \reg , #PSR_T_BIT ) 11 bne 1f 12 orr \reg, \reg, #PSR_A_BIT 13 badr lr, 2f 14 msr spsr_cxsf, \reg 15 __MSR_ELR_HYP(14) 16 __ERET 17 1: msr cpsr_c, \reg 18 2: 19 #else 20 /* 21 * workaround for possibly broken pre-v6 hardware 22 * (akita, Sharp Zaurus C-1000, PXA270-based) 23 */ 24 setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg 25 #endif 26 .endm
5.将内核解压地址(ZRELADDR)保存到 R4 中,依然在 "not_angel" 标签中运行
1 /* 字符段开始区域 */ 2 .text 3 4 #ifdef CONFIG_AUTO_ZRELADDR 5 mov r4, pc 6 and r4, r4, #0xf8000000 7 /* Determine final kernel image address. */ 8 add r4, r4, #TEXT_OFFSET 9 #else 10 ldr r4, =zreladdr 11 #endif
内核配置项AUTO_ZRELDDR表示自动计算内核解压地址(Auto calculation of the decompressed kernelimage address),这里没有选择这个配置项,因此保存到r4中的内核解压地址就是zreladdr
(1)定义了 CONFIG_AUTO_ZRELADDR, 将在运行时计算肯定 ZRELADDR
ZRELADDR 的值为:
注:此处与 0xf8000000 作 and 操做的缘由样是咱们默认 zImage 被放置的位置必定在距离 PHYS_OFFSET 的 128MB 以内。
TEXT_OFFSET 定义以下所示:
File: /arch/arm/Makefile
此处的 textofs-y 定义以下所示:
即 TEXT_OFFSET 的值为 0x00008000 = 32KB
此处之因此加上 TEXT_OFFSET 这个 32KB 的值的缘由以下图所示:
PHY_OFFSET的值不必定为 0x60000000,根据硬件来肯定。
(2)未定义 CONFIG_AUTO_ZRELADDR 时,直接加载 zreladdr 到 R4 中
zreladdr 的定义以下所示:
File: /arch/arm/boot/compressed/Makefile

ZERLADDR定义以下:
File: /arch/arm/boot/Makefile
看一下params_phys和initrd_phys的值,他们最终由arch/arm/mach-$(SOC)/Makefile.boot决定,我这里使用的soc是bcm2807(bcm2835),他的Makefile.boot内容以下:
zreladdr-y := 0x00008000
params_phys-y := 0x00000100
initrd_phys-y :=0x00800000
params_phys-y和initrd_phys-y是内核参数的物理地址和initrd文件系统的物理地址。其实除了zreladdr外这些地址uboot都会传入的。
这里的 zreladdr-y 定义在 /arch/arm/mach-xxx/Makefile.boot 中。
好比所用的 2440
这些地址都是经过uboot 传入进来的
6.缓存和MMU初始化cache_on的执行流程
这里将比较当前PC地址和内核解压地址,只有在不会自覆盖的状况下才会建立一个页表,若是当前运行地址PC < 解压地址 r4,则读取 LC0+32 地址处的内容加载到 r0 中,不然跳转到 cache_on 处执行缓存初始化和MMU初始化。
代码以下,此处代码依然在 "not_angel" 标签中运行
1 mov r0, pc 2 cmp r0, r4 3 ldrcc r0, LC0+32 4 addcc r0, r0, pc 5 cmpcc r4, r0 6 orrcc r4, r4, #1 @ remember we skipped cache_on 7 blcs cache_on
LC0的定义以下:
LC0+32地址处的内容为:_end -restart + 16384 + 1024*1024,所指的就是程序长度+16k的页表长+1M的DTB空间。
继续比较解压地址r4(0x00008000)和当前运行程序的(结束地址+16384 + 1024*1024),若是小于则不进行缓存初始化并置位r4最低位进行标识。
分状况总结一下:
(1) PC >= r4:直接进行缓存初始化
(2) PC < r4 && _end + 16384+ 1024*1024 > r4:不进行缓存初始化
(3) PC < r4 && _end + 16384+ 1024*1024 <= r4:执行缓存初始化
cache on 开始执行:
1 /* 2 * Turn on the cache. We need to setup some page tables so that we 3 * can have both the I and D caches on. 4 * 5 * We place the page tables 16k down from the kernel execution address, 6 * and we hope that nothing else is using it. If we're using it, we 7 * will go pop! 8 * 9 * On entry, 10 * r4 = kernel execution address 11 * r7 = architecture number 12 * r8 = atags pointer 13 * On exit, 14 * r0, r1, r2, r3, r9, r10, r12 corrupted 15 * This routine must preserve: 16 * r4, r7, r8 17 */ 18 .align 5 19 cache_on: mov r3, #8 @ cache_on function 20 b call_cache_fn
注释中说明了,为了开启I Cache和D Cache,须要创建页表(开启MMU),而页表使用的就是内核运行地址如下的16KB空间(对于个人环境来讲地址就等于0x00004000~0x00008000)。同时在运行的过程当中r0~r3以及r九、r10和r12寄存器会被使用。
这里首先在r3中保存打开缓存函数表项在cache操做表中的地址偏移(这里为8,cache操做表见后文),而后跳转到call_cache_fn中。
1 /* 2 * Here follow the relocatable cache support functions for the 3 * various processors. This is a generic hook for locating an 4 * entry and jumping to an instruction at the specified offset 5 * from the start of the block. Please note this is all position 6 * independent code. 7 * 8 * r1 = corrupted 9 * r2 = corrupted 10 * r3 = block offset 11 * r9 = corrupted 12 * r12 = corrupted 13 */ 14 15 call_cache_fn: adr r12, proc_types 16 #ifdef CONFIG_CPU_CP15 17 mrc p15, 0, r9, c0, c0 @ get processor ID 18 #elif defined(CONFIG_CPU_V7M) 19 /* 20 * On v7-M the processor id is located in the V7M_SCB_CPUID 21 * register, but as cache handling is IMPLEMENTATION DEFINED on 22 * v7-M (if existant at all) we just return early here. 23 * If V7M_SCB_CPUID were used the cpu ID functions (i.e. 24 * __armv7_mmu_cache_{on,off,flush}) would be selected which 25 * use cp15 registers that are not implemented on v7-M. 26 */ 27 bx lr 28 #else 29 ldr r9, =CONFIG_PROCESSOR_ID 30 #endif 31 1: ldr r1, [r12, #0] @ get value 32 ldr r2, [r12, #4] @ get mask 33 eor r1, r1, r9 @ (real ^ match) 34 tst r1, r2 @ & mask 35 ARM( addeq pc, r12, r3 ) @ call cache function 36 THUMB( addeq r12, r3 ) 37 THUMB( moveq pc, r12 ) @ call cache function 38 add r12, r12, #PROC_ENTRY_SIZE 39 b 1b
首先保存cache操做表的运行地址到r12寄存器中,proc_types定义在head.s中:
1 /* 2 * Table for cache operations. This is basically: 3 * - CPU ID match 4 * - CPU ID mask 5 * - 'cache on' method instruction 6 * - 'cache off' method instruction 7 * - 'cache flush' method instruction 8 * 9 * We match an entry using: ((real_id ^ match) & mask) == 0 10 * 11 * Writethrough caches generally only need 'on' and 'off' 12 * methods. Writeback caches _must_ have the flush method 13 * defined. 14 */ 15 .align 2 16 .type proc_types,#object
表中的每一类处理器都包含如下5项(若是不存在缓存操做函数则使用“mov pc, lr”占位):
(1) CPU ID
(2) CPU ID 位掩码(用于匹配CPU类型用)
(3) 打开缓存“cache on”函数入口
(4) 关闭缓存“cache off”函数入口
(5) 刷新缓存“cache flush”函数入口
我所用的CPU为ARM920T的 S3C2440,为ARMV4T架构,通常架构以下图:
对应的代码为:
若配置了CPU_CP15条件编译项,因此这里将从CP15中获取CPU型号而不是从内核配置项中获取。
而后逐条对cache操做表中的CPU类型进行匹配,若是匹配上了就跳转到相应的函数入口执行。
遍历 proc_types 列表,查找想对应的处理器类型,找到以后 pc = r12 + r3,r3 中存储的是常数 8,即 pc 指向了相对应的 cache on 子例程。执行以下
1 call_cache_fn: adr r12, proc_types 2 #ifdef CONFIG_CPU_CP15 3 mrc p15, 0, r9, c0, c0 @ get processor ID 4 #elif defined(CONFIG_CPU_V7M) 5 /* 6 * On v7-M the processor id is located in the V7M_SCB_CPUID 7 * register, but as cache handling is IMPLEMENTATION DEFINED on 8 * v7-M (if existant at all) we just return early here. 9 * If V7M_SCB_CPUID were used the cpu ID functions (i.e. 10 * __armv7_mmu_cache_{on,off,flush}) would be selected which 11 * use cp15 registers that are not implemented on v7-M. 12 */ 13 bx lr 14 #else 15 ldr r9, =CONFIG_PROCESSOR_ID 16 #endif 17 1: ldr r1, [r12, #0] @ get value 18 ldr r2, [r12, #4] @ get mask 19 eor r1, r1, r9 @ (real ^ match) 20 tst r1, r2 @ & mask 21 ARM( addeq pc, r12, r3 ) @ call cache function 22 THUMB( addeq r12, r3 ) 23 THUMB( moveq pc, r12 ) @ call cache function 24 add r12, r12, #PROC_ENTRY_SIZE 25 b 1b
代码注释已经很清楚,以后调用 cache 函数,对应 2440 则调用函数:__armv4_mmu_cache_on