系统加电启动后,MIPS处理器默认的程序入口是0xBFC00000(虚拟地址),此地址在KSEG1(无缓存)区域内,对应的物理地址是0x1FC00000(高3位清零),所以CPU从物理地址0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH(BIOS)的位置,BIOS将Linux内核镜像文件拷贝到RAM中某个空闲地址(LOAD地址)处,然后一般有个内存移动的操作(Entry point(EP)的地址),最后BIOS跳转到EP指定的地址运行,此时开始运行Linux kernel。
关于LOAD地址的一些说明:
在我们编译完内核时,一般情况下会有俩个版本的内核vmlinux与vmlinuz。其中vmlinux为非压缩版内核,vmlinuz为压缩版内核(包含内核自解压程序)。
使用readelf -l vmlinux 命令可以读到LOAD地址,这个地址是由arch/mips/kernel/vmlinux.lds决定的:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies = jiffies_64;
SECTIONS
{
. = 0xFFFFFFFF80200000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
*(.text)
…
关于Entry point(EP)的一些说明:
EP(ELF可以读到)地址是BIOS移动完内核后,直接跳转的地址(控制权由BIOS转移到KERNEL)。这个地址由ld写入ELF的头中,会依次用下面的方法尝试设置入口地址,当遇见成功时则停止:
a.命令行选项 -e entry;
b.脚本(vmlinux.lds)中的ENTRY(xxx);
c.如果有定义start符号,则使用start符号(xxx);
d.如果存在.text节,则使用第一个字节的地址;
e.地址0。
由于上述ld 脚本(vmlinux.lds)中,用ENTRY宏设置了内核的EP是kernel_entry (KE)函数的地址,所以内核取得控制权(BIOS跳转之后)后执行的第一条指令就是 KE函数。
注意:这种情况只是vmlinux(非压缩版的内核),对于vmlinuz(压缩版的内核),EP会被设置成内核自解压缩的程序代码的地址,这样固件就会跳转到内核自解压代码(此时的EP为解压程序的代码地址),最后还是会到KE函数去执行。
由以上分析可知无论是压缩版还是非压缩版的Linux内核,内核第一个执行的函数是KE。接下来就是对KE函数的分析,看看它到底都做了些什么事?
kernel_entry(KE)分析:
内核版本:3.10.X
源代码文件:arch/mips/kernel/head.S
KE函数是体系相关的汇编语言实现的,源代码中汇编指令的含义(64位指令)为:
PTR_LA dla
LONG_S sd
PTR_ADDIU daddiu
MTC0 dmtc0
PTR_LI dli
PTR_ADDU daddu
PTR_SUBU dsubu
源代码:
NESTED(kernel_entry, 16, sp) # KE函数定义,函数栈的大小为16字节
kernel_entry_setup # 对CPU的配置,详情见kernel_entry_setup函数分析NOTE1
setup_c0_status_pri #设置mips协处理器(cp0)中的寄存器,详情见NOTE2
PTR_LA t0, 0f
jr t0
0:
#ifdef CONFIG_MIPS_MT_SMTC #硬件多线程
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 # 清除BSS段,详情见NOTE3
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 # BIOS传参数,详情见NOTE4
LONG_S a1, fw_arg1
LONG_S a2, fw_arg2
LONG_S a3, fw_arg3
MTC0 zero, CP0_CONTEXT # NOTE5
PTR_LA $28, init_thread_union #为0号进程准备内核栈,详情见NOTE6
PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE
PTR_ADDU sp, $28
back_to_back_c0_hazard #NOTE7
set_saved_sp sp, t0, t1 #NOTE6
PTR_SUBU sp, 4 * SZREG #NOTE8
j start_kernel #NOTE9
END(kernel_entry)
__CPUINIT
NOTE1(kernel_entry_setup函数分析):
Linux内核犹如一座巨大的迷宫,只有找到了正确的入口,才有可能找到出口。
之前的分析得出的结论是Linux内核第一个调用的函数是KE,而KE第一个调用函数则是kernel_entry_setup,这才是真正执行的第一个函数,那么我们就从它开始吧。
函数名称:kernel_entry_setup
源代码文件:arch/mips/include/asm/mach-loongson/kernel-entry-init.h
源代码:
#ifndef __ASM_MACH_LOONGSON_KERNEL_E