本文着重分析 FS_S5PC100 平台 linux-2.6.35 内核启动的详细过程,主要包括: zImage 解压缩阶段、 vmlinux 启动汇编阶段、 startkernel 到创建第一个进程阶段三个部分,一般将其称为 linux 内核启动一、二、三阶段,本文也将采用这种表达方式。本文参考了许多技术大牛的博文,感谢他们的无私奉献。
-------------------------------------------------------------------------------------------------------
1. Linux内核启动第一阶段:内核解压缩和重定位
该阶段是从u-boot引导进入内核执行的第一阶段,我们知道u-boot引导内核启动的最后一步是:通过一个函数指针thekernel()带三个参数跳转到内核(zImage)入口点开始执行,此时,u-boot的任务已经完成,控制权完全交给内核(zImage)。
稍作解释,在u-boot的文件lib_arm/bootm.c(u-boot-2010.03)中定义了thekernel,并在do_bootm_linux的最后执行thekernel,如下:
void (*theKernel)(int zero, int arch, uint params);
theKernel = void (*)(int, int, uint))images->ep;
// images->ep指定的内核入口点,这里是0x20008000。
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);其中第二个参数为机器ID,第三参数为u-boot传递给内核参数存放在内存中的首地址,此处是0x20000100。
由上述zImage的生成过程我们可以知道,第一阶段运行的内核映像实际就是arch/arm/boot/compressed/vmlinux,而这一阶段所涉及的文件也只有三个:
arch/arm/boot/compressed/vmlinux.lds
arch/arm/boot/compressed/head.S
arch/arm/boot/compressed/misc.c
我们的分析集中在 arch/arm/boot/compressed/head.S,适当参考
vmlinux.lds。
从 arch/arm/boot/compressed/vmlinux的反汇编代码可一看出,内核执行的第一个代码段位
start,
*****start
vmlinux: file format elf32-littlearm
Disassembly of section .text:
00000000 <start>:
0: e1a00000 nop
(mov r0,r0)
…. …. …..
1c: e1a00000 nop (mov r0,r0)
20: ea000002 b 30 <.text+0x30>
…. …. …..
*****保存参数
30: e1a07001 mov r7, r1
u-boot向内传递参数分析
//由thekernel传递的三个参数分别保存在r0,r1,r2中。
//将机器ID保存在r7
中
34: e3a08000 mov r8, #0
; 0x0
//保存 r0,这里似乎没有太大的意义,
这里没有保存 r2,也就是
u-boot传递给内核参数的首地址 0x20000100,看来
linux-2.6.35启动时是不需要传递该参数的而是通过 struct machine_desc(arch/arm/include/asm/mach/arch.h)来确定,但是这个文件只是该结构的定义,真正的参数赋值在哪呢?实际上,这就是在内核移植是需要做的工作了,内核移植最主要的一个文件就是
arch/arm/mach-s5pc100/mach-smdkc100.c,通过下面的宏来实现对 machine_desc结构体的赋值,并且在该文件中对所涉及到的函数进行了具体的代码实现,这是内核移植方面的内容,与我们的主题无关,这里不再多说。
下面是宏定义:
下面我们采用汇编代码来进行分析: arch/arm/boot/compressed/head.S
.align
start: //u-boot first jump to this execute
.type start,#function
.rept 8
mov r0, r0
.endr
b 1f
.word 0x016f2818
@ Magic numbers to help the loader
.word start
@ absolute load/run zImage address
.word _edata
@ zImage end address
1: mov r7, r1 @ save architecture ID
mov r8, #0 @ save r0
*****判定是否是超级用户模式
mrs r2, cpsr
@ get current mode
tst r2, #3
@ not user? // 判断当前是否为普通用户模式
bne not_angel
// 如果不是普通用户模式, jump to not_angel
mov r0, #0x17 @ angel_SWIreason_EnterSVC 如果是普通用户模式,则通过软中断进入超级用户权限模式
swi 0x123456
@ angel_SWI_ARM
*****关中断
not_angel:
mrs r2, cpsr
@ turn off interrupts to // 关中断
orr r2, r2, #0xc0
@ prevent angel from running
msr cpsr_c, r2
*****将编译时指定的一些变量加载到相应的寄存器中
/* some architecture specific code can be inserted by the linker here, but it should preserve r7 and r8. zImage的连接首地址为
0x0 zImage的运行时首地址一般为 0x30008000,当然可以不同 */
.text
adr r0, LC0 //
读取 LC0的当前运行时地址,应当为
zImage的运行时起始地址 +(LC0到
zImage链接地址的首地址 (0x0)的偏移
)
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}//将
LC0中的变量值加载到 r1, r2, r3, r4, r5, r6, ip, sp
subs r0, r0, r1 @ calculate the delta offset //计算当前运行地址与链接地址的偏移
beq not_relocated @ if delta is zero, we are running at the address we were linked at.
//如果运行地址等于链接地址,则跳过重定位部分代码,否则继续执行
relocate
/* We're running at a different address. We need to fix up various pointers:
* r5 - zImage base address
* r11 - GOT start
* ip - GOT end
这段代码的作用的是将LC0标号后面的地址,传到各个寄存器中,经过这段代码后,r1保存的是链接zImage时LC0标号处的地址,r2中保存的是bss段的起始地址,r4中保存的是内核加载的物理地址,r5保存的是镜像的起始地址,r6保存的是解压内核镜像后的大小,
GOT(global
offset table)
GOT是一个数组,存在ELF
image的数据段中,他们是一些指向objects 的指针(通常
是数据objects).动态连接器将重新修改那些编译时还没有确定下来地址的符号的
GOT入口。所以说GOT在i386动态连接中扮演着重要的角色。*/
*****将上面的变量进行重定位,转换为当前的运行时地址
add r5, r5, r0
//zImage 的链接时首地址重定位为运行时的首地址
add r11, r11, r0
//GOT 的链接时首地址重定位为运行时的首地址
add ip, ip, r0
#ifndef CONFIG_ZBOOT_ROM
/* If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
* we need to fix up pointers into the BSS region.
* r2 - BSS start
* r3 - BSS end
* sp - stack pointer
*/
add r2, r2, r0
//__bss_start的链接时首地址重定位为运行时的首地址
add r3, r3, r0
//_end 的链接时地址重定位为运行时的地址
add sp, sp, r0 //user_stack+4096
的链接时地址重定位为运行时的地址
/* Relocate all entries in the GOT table.
重定位
GOT中的所有链接地址为当前运行时的地址 */
1: ldr r1, [r6, #0]
@ relocate entries in the GOT
add r1, r1, r0
@ table. This fixes up the
str r1, [r6], #4
@ C references.
cmp r6, ip
blo 1b
#else
/* Relocate entries in the GOT table. We only relocate
* the entries that are outside the (relocated) BSS region.
重定位
GOT中的所有链接地址为当前运行时地址但是不重定位
BSS_START到
BSS_END部分 */
1: ldr r1, [r6, #0]
@ relocate entries in the GOT
cmp r1, r2 @ entry < bss_start ||
cmphs r3, r1
@ _end < entry
addlo r1, r1, r0
@ table. This fixes up the
str r1, [r6], #4
@ C references.
cmp r6, ip
blo 1b
#endif
*****重定位已经完成,清零BSS段
not_relocated: mov r0, #0
1: str r0, [r2], #4
@ clear bss
str r0, [r2], #4
str r0, [r2], #4
str r0, [r2], #4
cmp r2, r3
blo 1b
*****准备进入C程序的相关设置,开启cache,设置一些指针
/* The C runtime environment should now be setup sufficiently. Turn the cache on, set up some pointers, and start decompressing. */
cache on是一个相当复杂的过程,这里简单描述其流程,如有兴趣可参考“
Arm linux启动第一阶段 cache on分析”
bl cache_on -------〉
call_cache_fn---- ---〉
通过查表 proc_types调用
__armv4_cache_on