http://hi.baidu.com/comcat/blog/item/8c3078cdeefd9c550eb34527.html
1. bootloader 将ELF 格式的Kernel 加载到某个空闲地址处,然后一般有个内存移动操作,目的地址在 arch/mips/Makefile 内指定: load-$(CONFIG_MIPS_PB1550) += 0xFFFFFFFF80100000,则最终bootloader定会将内核移到物理地址 0x00100000处
2. 上面Makefile 里指定的的 load 地址,最后会被编译系统写入到 arch/mips/kernel/vmlinux.lds 中:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies = jiffies_64;
SECTIONS
{
. = 0xFFFFFFFF80100000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
*(.text)
...
这个文件最终会以参数 -Xlinker --script -Xlinker vmlinux.lds 的形式传给 gcc,并最终传给链接器 ld 来控制其行为。ld 会将 .text 节的地址链接到 0xFFFFFFFF80100000 处。
关于内核 ELF 文件的入口地址(Entry point),即 bootloader 移动完内核后,直接跳转到的地址,由ld 写入 ELF的头中,其会依次用下面的方法尝试设置入口点,当遇到成功时则停止:
a. 命令行选项 -e entry
b. 脚本中的 ENTRY(symbol)
c. 如果有定义 start 符号,则使用start符号(symbol)
d. 如果存在 .text 节,则使用第一个字节的地址。
e. 地址0
注意到上面的 ld script 中,用 ENTRY 宏设置了内核的 entry point 是 kernel_entry,因此内核取得控制权后执行的第一条指令是在 kernel_entry 处。
3. 这个 kernel_entry 定义于 arch/mips/kernel/head.S 中:
NESTED(
kernel_entry, 16, sp) # kernel entry point
kernel_entry_setup # cpu specific setup,某些MIPS CPU需要额外的设置一些控制寄
存器,和具体的平台相关,一般为空宏;某些多核MIPS,启动时所
有的core的入口一起指向kernel_entry,然后在该宏里分叉,boot
core 继续往下,其它的则不停的判断循环,直到boot core 唤醒之
setup_c0_status_pri # 设置cp0_status 寄存器
ARC64_TWIDDLE_PC # 除非 CONFIG_ARC64,否则为空操作
#ifdef CONFIG_MIPS_MT_SMTC
/*
* In SMTC kernel, "CLI" is thread-specific, in TCStatus.
* We still need to enable interrupts globally in Status,
* and clear EXL/ERL.
*
* TCContext is used to track interrupt levels under
* service in SMTC kernel. Clear for boot TC before
* allowing any interrupts.
*/
mtc0 zero, CP0_TCCONTEXT
mfc0 t0, CP0_STATUS
ori t0, t0, 0xff1f
xori t0, t0, 0x001e
mtc0 t0, CP0_STATUS
#endif /* CONFIG_MIPS_MT_SMTC */
PTR_LA t0, __bss_start # clear .bss
LONG_S zero, (t0)
PTR_LA t1, __bss_stop - LONGSIZE
1:
PTR_ADDIU t0, LONGSIZE
LONG_S zero, (t0)
bne t0, t1, 1b
LONG_S a0, fw_arg0 # firmware arguments
LONG_S a1, fw_arg1 # bootloader 会将要传给内核的参数
LONG_S a2, fw_arg2 写在 a0 ~ a4 里。此处为将参数保存
LONG_S a3, fw_arg3 在 fw_arg0 ~ fw_arg3 四个变量里
MTC0 zero, CP0_CONTEXT # clear context register
PTR_LA $28, init_thread_union # 初始化 gp,指向一个union,THREAD_SIZE
大小,最低处是一个thread_info 结构
PTR_LI sp, _THREAD_SIZE - 32 # _THREAD_SIZE = THREAD_SIZE
PTR_ADDU sp, $28 # sp 指向这个union结构的最高低32B处
set_saved_sp sp, t0, t1
PTR_SUBU sp, 4 * SZREG # init stack pointer
j
start_kernel # gp, sp设好,可以进入 C 语言环境了 :)
END(kernel_entry)
[include/asm-mips/thread_info.h]
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
_THREAD_SIZE 是为预处理生成,与THREAD_SIZE 一致。
来看看 setup_c0_status_pri:
.macro setup_c0_status_pri
#ifdef CONFIG_64BIT
setup_c0_status ST0_KX 0
#else
setup_c0_status 0 0 # CP0_Status[4:0] 置0,CU0 置1,其他位不变
#endif
.endm
特别注意一直到 trap_init() 中的 per_cpu_trap_init(),CP0_Status[BEV] 一直为1。因此这个阶段异常的入口一直在0xFFFFFFFFBFC000200
.macro setup_c0_status set clr
.set push
#ifdef CONFIG_MIPS_MT_SMTC
/*
* For SMTC, we need to set privilege and disable interrupts only for
* the current TC, using the TCStatus register.
*/
mfc0 t0, CP0_TCSTATUS
/* Fortunately CU 0 is in the same place in both registers */
/* Set TCU0, TMX, TKSU (for later inversion) and IXMT */
li t1, ST0_CU0 | 0x08001c00
or t0, t1
/* Clear TKSU, leave IXMT */
xori t0, 0x00001800
mtc0 t0, CP0_TCSTATUS
_ehb
/* We need to leave the global IE bit set, but clear EXL...*/
mfc0 t0, CP0_STATUS
or t0, ST0_CU0 | ST0_EXL | ST0_ERL | \set | \clr
xor t0, ST0_EXL | ST0_ERL | \clr
mtc0 t0, CP0_STATUS
#else
mfc0 t0, CP0_STATUS
or t0, ST0_CU0|\set|0x1f|\clr
xor t0, 0x1f|\clr
mtc0 t0, CP0_STATUS
.set noreorder
sll zero,3 # ehb
#endif
.set pop
.endm
在完成 status, gp, fp 的设置后,直接跳转到 init/main.c 中的 start_kernel() ,开始了一个新的时代 ;)