linuxmips启动分析.docx
《linuxmips启动分析.docx》由会员分享,可在线阅读,更多相关《linuxmips启动分析.docx(219页珍藏版)》请在冰豆网上搜索。
linuxmips启动分析
linux-mips启动分析
(1)
系统加电起动后,MIPS处理器默认的程序入口是0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是0x1FC00000,即CPU从0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH的位置,Bootloader将Linux内核映像拷贝到 RAM 中某个空闲地址处,然后一般有个内存移动操作,目的地址在arch/mips/Makefile内指定:
load-$(CONFIG_MIPS_PB1550)+=0xFFFFFFFF80100000,
则最终bootloader定会将内核移到物理地址 0x00100000 处。
上面Makefile里指定的的load地址,最后会被编译系统写入到arch/mips/kernel/vmlinux.lds中:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies=jiffies_64;
SECTIONS
{
.=0xFFFFFFFF80100000;
/*read-only*/
_text=.;/*Textandread-onlydata*/
.text:
{
*(.text)
...
这个文件最终会以参数-Xlinker--script-Xlinkervmlinux.lds的形式传给gcc,并最终传给链接器ld来控制其行为。
ld会将.text节的地址链接到0xFFFFFFFF80100000处。
关于内核ELF文件的入口地址(Entrypoint),即bootloader移动完内核后,直接跳转到的地址,由ld写入ELF的头中,其会依次用下面的方法尝试设置入口点,当遇到成功时则停止:
a.命令行选项-eentry
b.脚本中的ENTRY(symbol)
c.如果有定义start符号,则使用start符号(symbol)
d.如果存在.text节,则使用第一个字节的地址。
e.地址0
注意到上面的ldscript中,用ENTRY宏设置了内核的entrypoint是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) #kernelentrypoint
声明函数 kernel_entry,函数的堆栈为16byte,返回地址保存在 $sp寄存器中。
-----------------------------
声明函数入口
#defineNESTED(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 #cpuspecificsetup
----------------------------
这个宏一般为空的,在include/asm-mips/mach-generic/kernel-entry-init.h文件中定义。
某些MIPSCPU需要额外的设置一些控制寄
存器,和具体的平台相关,一般为空宏;某些多核MIPS,启动时所
有的core的入口一起指向 kernel_entry,然后在该宏里分叉,
bootcore继续往下,其它的则不停的判断循环,直到bootcore唤醒之
----------------------------
setup_c0_status_pri
设置 cp0_status寄存器
----------------------------
.macro setup_c0_status_pri
#ifdefCONFIG_64BIT
setup_c0_statusST0_KX0
#else
setup_c0_status00
#endif
.endm
----------------------------
ARC64_TWIDDLE_PC
除非CONFIG_ARC64,否则为空操作
-----------------------------
#ifdefCONFIG_MIPS_MT_SMTC
mtc0 zero,CP0_TCCONTEXT__bss_start
mfc0 t0,CP0_STATUS
orit0,t0,0xff1f
xori t0,t0,0x001e
mtc0 t0,CP0_STATUS
#endif/*CONFIG_MIPS_MT_SMTC*/
宏定义 CONFIG_MIPS_MT_SMTC是使用多核的 SMTCLinux时定义的。
一般情况下不考虑。
MIPS已经开发出 SMPLinux的改进版,叫做SMTC(线程上下文对称多处理)Linux。
SMTCLinux能理解轻量级 TC的概念,并能因此减少某些与SMPLinux相关的开销。
----------------------------
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 #firmwarearguments
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 #clearcontextregister
清除 CP0的contextregister,这个寄存器用来保存页表的起始地址。
----------------------------------
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_spstackptemptemp2
#ifdefCONFIG_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_spstackptemptemp2
LONG_S \stackp,kernelsp
.endm
变量 kernelsp的定义,在arch/mips/kernel/setup.c文件中。
unsignedlongkernelsp[NR_CPUS];
把 这个CPU核的堆栈地址 $sp保存到 kernelsp[NR_CPUS]数组。
---------------------------------
PTR_SUBU sp,4*SZREG #initstackpointer
---------------------------------
j start_kernel
END(kernel_entry)
最后跳转到 /arch/mips/kernel/main.c中的start_kernel()初始化硬件平台相关的代码。
----------------------------------
******************************************
这个 init_thread_union变量在 arch/mips/kernel/init_task.c文件中定义。
unionthread_unioninit_thread_union
__attribute__((__section__(".data.init_task"),
__aligned__(THREAD_SIZE)))=
{INIT_THREAD_INFO(init_task)};
******************************************
问题:
1)这个 init_thread_union结构体指针是怎么初始化的?
******************************************
linux-mips启动分析
(2)
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()函数。
********************************************
asmlinkagevoid__initstart_kernel(void)
{
---------------------------------
char*command_line;
externstructkernel_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来帮助我们调试代码,
通过这个标记可以帮助我们知道是否在“earlybootupcode”,
也可以通过这个标志警告是否有无效的中断打开。
和 early_boot_irqs_on()函数配置使用,参考下面。
---------------------------------
early_init_irq_lock_class();
每一个中断都有一个 IRQ描述符(structirq_desc)来进行描述。
这个函数的主要作用是设置所有的 IRQ描述符(structirq_desc)的锁是统一的锁,
还是每一个 IRQ描述符(structirq_desc)都有一个小锁。
参考《linux-mips启动分析(2-1)》。
---------------------------------
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 宏时,非空函数。
其他情况为空函数。
参考《linux-mips启动分析(2-1)》。
---------------------------------
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[]中。
参考《linux-mips启动分析(2-1)》。
---------------------------------
unwind_setup();
空函数。
---------------------------------
setup_per_cpu_areas();
如果没有定义 CONFIG_SMP宏,则这个函数为空函数。
如果定义了 CONFIG_SMP宏,
则这个 setup_per_cpu_areas()函数给每个CPU分配内存,并拷贝 .data.percpu段的数据。
参考《linux-mips启动分析(2-1)》。
---------------------------------
如果没有定义 CONFIG_SMP宏,则这个函数为空函数。
如果定义了 CONFIG_SMP宏,这个函数
smp_prepare_boot_cpu();
---------------------------------
sched_init();
核心进程调度器初始化,调度器的初始化优先于任何中断的建立(包括 timer中断)。
并且初始化进程0,即 idle进程,但是并没有设置idle进程的 NEED_RESCHED标志,
以完成内核剩余的启动部分。
---------------------------------
preempt_disable();
进制内核的抢占。
使当前进程的 structthread_info结构 preempt_count成员的值增加1。
参考《linux-mips启动分析(2-1)》。
---------------------------------
建立各个节点的管理区的 zonelist,便于分配内存的 fallback使用。
这个链表的作用:
这个链表是为了在一个分配不能够满足时可以考察下一个管理区来设置了。
在考察结束时,分配将从 ZONE_HIGHMEM回退到 ZONE_NORMAL,
在分配时从 ZONE_NORMAL退回到 ZONE_DMA就不会回退了。
build_all_zonelists();
---------------------------------
参考《linux-mips启动分析(2-1)》。
page_alloc_init();
---------------------------------
在 MIPS体系结构下,这个函数已经在 arch_mem_init()函数中调用了一次。
这个函数的具体分析详细分析,请看《linux-mips启动分析(4)》。
所以这个函数直接返回。
parse_early_param();
---------------------------------
打印 linux启动命令行参数。
printk(KERN_NOTICE"Kernelcommandline:
%s\n",boot_command_line);
---------------------------------
这个函数在 《linux-mips启动分析(4)》文件有分析,可以参考。
这个函数的意思对 linux启动命令行参数进行再分析和处理。
这两个变量 __start___param和 __stop___param在
链接脚本 arch/mips/kernel/vmlinux.lds中定义。
最后一个参数为,当不能够识别 linux启动命令行参数时,调用