linux-mips启动分析,linux mips启动分析 - MIPS技术及应用社区

系统加电起动后,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查找 进程描述符地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值