概述
Linux启动入口主要代码在 arch/mips/kernel/head.S文件中 kernel_entry函数以汇编形式出现
主要干了以下几件事:
1. BSS段清0
2. 从boot传过来的参数赋值到全局变量
3. clear context register
4. 根据init_thread_union建立$gp寄存器 并设置$sp寄存器,堆栈指针 (PTR_LA $28, init_thread_union)
5. 做好上述准备后就跳转到/arch/mips/kernel/main.c中的start_kernel()初始化硬件平台相关的代码
主要涉及的数据结构 在arch/mips/kernel/init_task.c
union thread_unioninit_thread_union__init_task_data
__attribute__((__aligned__(THREAD_SIZE))) =
{ INIT_THREAD_INFO(init_task) };
THREAD_SIZE在这里为8K,__attribute__((__aligned__(THREAD_SIZE)))表示这个数据结构以8K对齐
struct task_struct init_task = INIT_TASK(init_task);
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
套用linux设计与实现的图表示Thread_info、stack、task_struct的关系
在mips的head.S的工作就是
这就是传说中的0号进程
1. 进程0是所有其他进程的祖先, 也称作idle进程或swapper进程。
2. 进程0是在系统初始化时由kernel自身从无到有创建。(过程集中在start_kernel函数内)
3. 进程0的数据成员大部分是静态定义的,即由预先定义好的(如上)INIT_TASK, INIT_MM等宏初始化。
进程0的描述符init_task定义在arch/arm/kernel/init_task.c,由INIT_TASK宏初始化。 init_mm等结构体定义在include/linux/init_task.h内,为init_task成员的初始值,分别由对应的初始化宏如INIT_MM等初始化
下面是转载网上大虾的详细分析
系统加电起动后,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 = .; /* 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 处。
*********************************************
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) # kernelentry point
声明函数 kernel_entry,函数的堆栈为 16byte,返回地址保存在 $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 # cpuspecific 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 temptemp2
#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 stackptemp 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)
最后跳转到 /arch/mips/kernel/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) };
******************************************
问题:
1)这个 init_thread_union 结构体指针是怎么初始化的?
******************************************
最后
以上就是野性花卷为你收集整理的linux mips 启动分析,Linux/MIPS启动分析的全部内容,希望文章能够帮你解决linux mips 启动分析,Linux/MIPS启动分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复