内核的实际起始函数为 start_kernel() 函数,然后再调用其他函数来执行启动。再调用此函数之前,需要先将通过编译内核获得的 zImage 进行解压,请按成页目录构建等基本任务。
调用 start_kernel 的过程分为以下三个阶段:
1.保存boot下传递给内核的参数,并且关闭中断进入svc 模式
2.缓存和mmu的初始化;
3.内核的搬移;
4.内核的解压缩;
1.1 准备阶段
解压缩准备阶段将执行中断禁用、分配动态内存、初始化BBS区域、初始化页目录、打开缓存等任务。
在该阶段,zImage 解压位置的下级 16KB 构建用于保存页目录的空间,在CP15的c2寄存器中保存页目录的位置。
ARM中,页目录将 4GB 的内存以 1MB 节区为单位进行管理。因此,为了管理 4GB 的内存,需要有 4096 个以 1MB为单位的项。由于以32位的字符为单位管理各项,所以共需要 16KB (4字节 X 4096各项 = 16KB)。之后,向相当于页目录位置的项设置 cacheable 和 bufferable,使页目录得到缓冲并能快速访问。
通过加载项完成对软硬件的默认初始化后,最先执行的是 head.S (arch\arm\boot\compressed) 下的 start 标签中的代码。 完成的主要功能如下:
- 从启动加载项接收结构ID和atags信息
- 禁用中断
- 初始化寄存器,跳转到 not_relocated 标签
- 从 start 标签开始执行,共执行了 8 (rept 7 + 1) 次 "mov r0, r0" 指令(等同于 nop 指令),空出了 32 字节的用来存放 ARM 的中断向量表的位置,然后向前跳转到 "1" 标签处。使用.type标号来指明start的符号类型是函数类型,然后重复执行.rept到.endr之间的指令7次,这里一共执行了7次mov r0, r0指令,共占用了4*7 = 28个字节,这是用来存放ARM的异常向量表的。向前跳转到标号为1处执行
start:
.type start,#function
.rept 7
__nop
.endrmov r0, r0
W(b) 1f -
保存 cpsr 的值到 r9 中,保存架构 ID 和 atags 指针分别到 r7 和 r8 中
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 指针
这里将CPU的工作模式保存到r9寄存器中,将uboot通过r1传入的机器码保存到r7寄存器中,将启动参数tags的地址保存到r8寄存器中。
CONFIG_ARM_VIRT_EXT 表明启用了 ARM 虚拟化扩展。
从 bootloader 中接收了 3 个参数,分别为
R0=0
R1 = 架构 ID
R2 = atags 指针
继续在标签“1”中运行,判断当前 CPU 的工作模式,若不是在用户模式下,则跳转到 "not_angel" 标签处,否则通过 swi 指令产生软中断异常的方式来进入 SVC 模式
1 /*
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符号处执行。
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 借助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ中断,切换到 SVC 模式;将 r9 中保存的原来的 CPSR 的值保存到 SPSR 中。
arch/arm/include/asm/assembler.h
这里的注释已经说明了,这里是强制将CPU的工作模式切换到SVC模式,并且关闭IRQ和FIQ中断。然后将r9中保存的原始CPU配置保存到SPSR中。
/* 此处出现的 MODE_MASK、PSR_I_BIT 等常量被宏定义在 arch/arm/include/uapi/asm/ptrace.h */
.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
mrs \reg , cpsr
eor \reg, \reg, #HYP_MODE
tst \reg, #MODE_MASK
bic \reg , \reg , #MODE_MASK @ 将模式位M[4:0]清0
/* 通过设置低 8 位为 110 10011,达到了关闭 IRQ、FIQ、设置 CPU 工作模式为 SVC 模式的目标 */
orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
THUMB( orr \reg , \reg , #PSR_T_BIT )
bne 1f
orr \reg, \reg, #PSR_A_BIT
badr lr, 2f
msr spsr_cxsf, \reg
__MSR_ELR_HYP(14)
__ERET
1: msr cpsr_c, \reg
2:
#else
/*
* workaround for possibly broken pre-v6 hardware
* (akita, Sharp Zaurus C-1000, PXA270-based)
*/
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg
#endif
.endm
5.将内核解压地址(ZRELADDR)保存到 R4 中,依然在 "not_angel" 标签中运行
/* 字符段开始区域 */
.text
#ifdef CONFIG_AUTO_ZRELADDR
mov r4, pc
and r4, r4, #0xf8000000
/* Determine final kernel image address. */
add r4, r4, #TEXT_OFFSET
#else
ldr r4, =zreladdr
#endif
内核配置项AUTO_ZRELDDR表示自动计算内核解压地址(Auto calculation of the decompressed kernelimage address),这里没有选择这个配置项,所以保存到r4中的内核解压地址就是zreladdr
(1)定义了 CONFIG_AUTO_ZRELADDR, 将在运行时计算确定 ZRELADDR
ZRELADDR 的值为:
- 先是 pc 值和 0xf8000000 做与操作;
注:此处与 0xf8000000 做 and 操作的原因样是我们默认 zImage 被放置的位置一定在距离 PHYS_OFFSET 的 128MB 之内。
- 再加上 TEXT_OFFSET(内核最终存放的物理地址与内存起始处之间的偏移)
TEXT_OFFSET 定义如下所示:
File: /arch/arm/Makefile
即 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