系统加电起动后,MIPS处理器默认的程序入口是0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是0x1FC00000,即CPU从0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH的位置,Bootloader将Linux内核映像拷贝到 RAM 中某个空闲地址处,然后一般有个内存移动操作,目的地址在arch/mips/Makefile内指定:
core-$(CONFIG_MIPS_ADM5120)+= arch/mips/adm5120/
load-$(CONFIG_MIPS_ADM5120)+= 0xffffffff80002000
则最终bootloader定会将内核移到物理地址 0x002000 处
上面Makefile里指定的的load地址,最后会被编译系统写入到arch/mips/kernel/vmlinux.lds中:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies = jiffies_64;
SECTIONS
{
. = 0xFFFFFFFF80002000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
*(.text)
...
这个文件最终会以参数-Xlinker --script -Xlinker vmlinux.lds的形式传给gcc,并最终传给链接器ld来控制其行为。ld会将.text节的地址链接到0xFFFFFFFF80002000处。
关于内核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处。
linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s文件开始的。而此处正是内核入口函数kernel_entry(),该函数定义在/arch/mips/kernel/head.s文件里。kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零,最后跳转到 /init/main.c中的start_kernel()初始化硬件平台相关的代码。
*********************************************
NESTED(kernel_entry, 16, sp) # kernel entry point
声明函数 kernel_entry,函数的堆栈为16 byte,返回地址保存在 $sp寄存器中。
-----------------------------
声明函数入口
#define NESTED(symbol, framesize, rpc) \
.globl symbol; \
.align 2; \
.type symbol,@function; \
.ent symbol,0; \
symbol: .frame sp, framesize, rpc
汇编伪指令 frame用来声明堆栈布局。
它有三个参数:
1)第一个参数 framereg:声明用于访问局部堆栈的寄存器,一般为 $sp。
2)第二个参数 framesize:申明该函数已分配堆栈的大小,应该符合
$sp+framesize= 原来的 $sp。
3)第三个参数 returnreg:这个寄存器用来保存返回地址。
----------------------------
kernel_entry_setup # cpu specific setup
----------------------------
这个宏一般为空的,在include/asm-mips/mach-generic/kernel-entry-init.h文件中定义。
某些MIPS CPU需要额外的设置一些控制寄存器,和具体的平台相关,一般为空宏;某些多核MIPS,启动时所有的core的入口一起指向 kernel_entry,然后在该宏里分叉,boot core继续往下,其它的则不停的判断循环,直到boot core唤醒之。
----------------------------
setup_c0_status_pri
设置 cp0_status寄存器
----------------------------
.macro setup_c0_status_pri
#ifdef CONFIG_64BIT
setup_c0_status ST0_KX 0
#else
setup_c0_status 0 0
#endif
.endm
----------------------------
ARC64_TWIDDLE_PC
除非CONFIG_ARC64,否则为空操作
-----------------------------
#ifdef CONFIG_MIPS_MT_SMTC
mtc0 zero, CP0_TCCONTEXT__bss_start
mfc0 t0, CP0_STATUS
ori t0, t0, 0xff1f
xori t0, t0, 0x001e
mtc0 t0, CP0_STATUS
#endif /* CONFIG_MIPS_MT_SMTC */
宏定义 CONFIG_MIPS_MT_SMTC是使用多核的 SMTC Linux时定义的。一般情况下不考虑。
MIPS已经开发出 SMP Linux的改进版,叫做SMTC(线程上下文对称多处理) Linux。
SMTC Linux能理解轻量级 TC的概念,并能因此减少某些与SMP Linux相关的开销。
----------------------------
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
清除 BSS段,清0。
变量 __bss_start 和 __bss_stop在连接文件arch/mips/kernel/vmlinux.lds中定义。
--------------------------------
LONG_S a0, fw_arg0 # firmware arguments
LONG_S a1, fw_arg1
LONG_S a2, fw_arg2
LONG_S a3, fw_arg3
把 bootloader传递给内核的启动参数保存在fw_arg0,fw_arg1,fw_arg2,fw_arg3变量中。
变量 fw_arg0为内核参数的个数,其余分别为字符串指针,为 *** =XXXX 的格式。
----------------------------------
MTC0 zero, CP0_CONTEXT # clear context register
清除 CP0的context register,这个寄存器用来保存页表的起始地址。
----------------------------------
PTR_LA $28, init_thread_union
初始化 $gp寄存器,这个寄存器的地址指向一个 union,
THREAD_SIZE 大小,最低处是一个thread_info结构
---------------------------------
PTR_LI sp, _THREAD_SIZE - 32
PTR_ADDU sp, $28
设置 $sp寄存器,堆栈指针。 $sp = (init_thread_union的地址)+ _THREAD_SIZE - 32
的得出 $sp指向这个 union 结构的结尾地址 -32字节地址。
-----------------------------------
set_saved_sp sp, t0, t1
把 这个CPU核的堆栈地址 $sp保存到 kernelsp[NR_CPUS]数组。
---------------------------------
如果定义了 CONFIG_SMP宏,即多 CPU核。
.macro set_saved_sp stackp temp temp2
#ifdef CONFIG_MIPS_MT_SMTC
mfc0 \temp, CP0_TCBIND
#else
MFC0 \temp, CP0_CONTEXT
#endif
LONG_SRL \temp, PTEBASE_SHIFT
LONG_S \stackp, kernelsp(\temp)
.endm
如果没有定义 CONFIG_SMP宏,单 CPU核。
.macro set_saved_sp stackp temp temp2
LONG_S \stackp, kernelsp
.endm
变量 kernelsp的定义,在arch/mips/kernel/setup.c文件中。
unsigned long kernelsp[NR_CPUS];
把 这个CPU核的堆栈地址 $sp保存到 kernelsp[NR_CPUS]数组。
---------------------------------
PTR_SUBU sp, 4 * SZREG # init stack pointer
---------------------------------
j start_kernel
END(kernel_entry)
最后跳转到 /init/main.c中的start_kernel()初始化硬件平台相关的代码。
----------------------------------
**********************************************
这个 init_thread_union变量在 arch/mips/kernel/init_task.c文件中定义。
union thread_union init_thread_union
__attribute__((__section__(".data.init_task"),
__aligned__(THREAD_SIZE))) =
{ INIT_THREAD_INFO(init_task) };
linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s文件开始的。
而此处正是内核入口函数kernel_entry(),该函数定义在/arch/mips/kernel/head.s文件里。
kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零,
最后跳转到 /arch/mips/kernel/main.c中的start_kernel()初始化硬件平台相关的代码。
下面讲述 start_kernel()函数。
*******************************************
asmlinkage void __init start_kernel(void)
{
---------------------------------
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
定义了核的参数数据结构
---------------------------------
smp_setup_processor_id();
设置 SMP多核的 CPU核的ID号,单核不进行任何操作,我们不关心。
---------------------------------
unwind_init();
在 MIPS体系结构中,这个函数是个空函数(可能调用setup_arch,配置核的相关函数)
---------------------------------
lockdep_init();
初始化核依赖关系哈希表。
---------------------------------
local_irq_disable();
关闭当前 CPU核的中断
---------------------------------
early_boot_irqs_off();
通过一个静态全局变量 early_boot_irqs_enabled来帮助我们调试代码,
通过这个标记可以帮助我们知道是否在“early bootup code”,
也可以通过这个标志警告是否有无效的中断打开。
和 early_boot_irqs_on()函数配置使用,参考下面。
---------------------------------
early_init_irq_lock_class();
每一个中断都有一个 IRQ描述符 (struct irq_desc)来进行描述。
这个函数的主要作用是设置所有的 IRQ描述符 (struct irq_desc)的锁是统一的锁,
还是每一个 IRQ描述符 (struct irq_desc)都有一个小锁。
---------------------------------
lock_kernel();
获取大内核锁,这种大内核锁锁定整个内核。
---------------------------------
tick_init();
如果没有定义 CONFIG_GENERIC_CLOCKEVENTS宏定义,则这个函数为空函数,
如果定义了这个宏,这执行初始化 tick控制功能,注册clockevents的框架。
---------------------------------
boot_cpu_init();
对于 CPU核的系统来说,设置第一个 CPU核为活跃 CPU核。
对于单 CPU核系统来说,设置CPU核为活跃 CPU核。
参考《linux-mips启动分析(2-1)》。
---------------------------------
page_address_init();
当定义了CONFIG_HIGHMEM 宏,并且没有定义 WANT_PAGE_VIRTUAL 宏时,非空函数。
其他情况为空函数。
---------------------------------
printk(KERN_NOTICE);
printk(linux_banner);
输出打印版本信息。
---------------------------------
setup_arch(&command_line);
每种体系结构都有自己的 setup_arch()函数,这些是体系结构相关的。
如何确定编译那个体系结构的 setup_arch()函数呢?
主要由 linux源码树顶层 Makefile中 ARCH变量来决定的。
例如: MIPS体系结构的。
SUBARCH := mips
ARCH ?= $(SUBARCH)
---------------------------------
setup_command_line(command_line);
保存未改变的 comand_line 到字符数组 static_command_line[] 中。
保存 boot_command_line到字符数组 saved_command_line[]中。
---------------------------------
unwind_setup();
空函数。
---------------------------------
setup_per_cpu_areas();
如果没有定义 CONFIG_SMP宏,则这个函数为空函数。
如果定义了 CONFIG_SMP宏,
则这个 setup_per_cpu_areas()函数给每个CPU分配内存,并拷贝 .data.percpu段的数据。
---------------------------------
如果没有定义 CONFIG_SMP宏,则这个函数为空函数。
如果定义了 CONFIG_SMP宏,这个函数
smp_prepare_boot_cpu();
---------------------------------
sched_init();
核心进程调度器初始化,调度器的初始化优先于任何中断的建立(包括 timer中断)。
并且初始化进程0,即 idle进程,但是并没有设置idle进程的 NEED_RESCHED标志,
以完成内核剩余的启动部分。
---------------------------------
preempt_disable();
进制内核的抢占。使当前进程的 struct thread_info结构 preempt_count成员的值增加1。
---------------------------------
建立各个节点的管理区的 zonelist,便于分配内存的 fallback使用。
这个链表的作用: 这个链表是为了在一个分配不能够满足时可以考察下一个管理区来设置了。
在考察结束时,分配将从 ZONE_HIGHMEM回退到 ZONE_NORMAL,
在分配时从 ZONE_NORMAL退回到 ZONE_DMA就不会回退了。
build_all_zonelists();
---------------------------------
page_alloc_init();
---------------------------------
在 MIPS体系结构下,这个函数已经在 arch_mem_init() 函数中调用了一次。
这个函数的具体分析详细分析,请看《linux-mips启动分析(4)》。
所以这个函数直接返回。
parse_early_param();
---------------------------------
打印 linux启动命令行参数。
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
---------------------------------
这个函数的意思对 linux启动命令行参数进行再分析和处理。
这两个变量 __start___param和 __stop___param在
链接脚本 arch/mips/kernel/vmlinux.lds中定义。
最后一个参数为,当不能够识别 linux启动命令行参数时,调用的函数。
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param, &unknown_bootoption);
---------------------------------
检查中断是否已经打开了,如果已将打开了,关闭中断。
if (!irqs_disabled()) {
local_irq_disable();
}
---------------------------------
sort_main_extable();
这个函数对内核建立的异常处理调用函数表(exception table)
根据异常的向量号进行堆排序。
---------------------------------
设置 CPU的异常处理函数,TLB重填,cache出错,还有通用异常处理表的初始化。
trap_init();
---------------------------------
初始化 RCU机制,这个步骤必须比本地 timer的初始化早。
rcu_init();
---------------------------------
用来初始化中断处理硬件相关的寄存器和中断描述符数组 irq_desc[] 数组,
每个中断号都有一个对应的中断描述符。
参考《linux-mips启动分析(11)》。
init_IRQ();
--------------------------------
系统在初始化阶段动态的分配了 4 个 hashtable,并把它们的地址存入 pid_hash[] 数组。
便于从PID查找 进程描述符地址。
转自嵌入式学习网