linux 内核启动过程分析

文件linux/arch/arm/boot/compressed/head.S是linux内核启动过程执行的第一个文件。
  .align
start:
  .type start,#function  #function //type指定start这个符号是函数类型
  .rept 8   //重复8次 mov r0, r0,
  mov r0, r0 //空操作,让前面所取指令得以执行。
  .endr

  b 1f  //跳转

/*

魔数0x016f2818是在bootloader中用于判断zImage的存在,

而zImage的判别的magic number为0x016f2818,这个也是内核和bootloader约定好的。

*/
  .word 0x016f2818  
  .word start   
  .word _edata   

////r1和r0中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。
1:  mov r7, r1  
  mov r8, r2   

/*

这也标志着u-boot将系统完全的交给了OS,bootloader生命终止。之后会读取

cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;

而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入

*/

#ifndef __ARM_ARCH_2__
  mrs r2, cpsr  @ get current mode
  tst r2, #3   @ not user?
  bne not_angel
  mov r0, #0x17  //0x17是angel_SWIreason_EnterSVC半主机操作
  swi 0x123456  //0x123456是arm指令集的半主机操作编号
not_angel:
  mrs r2, cpsr  
  orr r2, r2, #0xc0  
  msr cpsr_c, r2 ////这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ
#else
  teqp pc, #0x0c000003  @ turn off interrupts
#endif

/*

LC0表是链接文件arch/arm/boot/compressed/vmlinux.lds

(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)的各段入口。

ENTRY(_start)
SECTIONS
{
  /DISCARD/ : {
    *(.ARM.exidx*)
    *(.ARM.extab*)
  }

  . = TEXT_START;
  _text = .;

  .text : {

………………

展开如下表:

假定zImage在内存中的初始地址为0x30008000(这个地址由bootloader决定,位置不固定)1、初始状态

linux 内核启动过程分析_第1张图片
链接文件arch/arm/boot/compressed/vmlinux.lds中的连接地址都是位置无关的,即都是以0地址为偏移的。
而此时内核已被bootloader搬移到了SDRAM中。链接地址应该加上这个偏移。

 */

  .text

//指令adr是基于PC的值来获取标号LC0 的地址的,由于内核已被搬移,标号LC0的地址不是以地址0为偏移的

//这中间就存在一个固定的地址偏移,在s3c2410中是0x30008000.
  adr r0, LC0  
  ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
  subs r0, r0, r1  //这里获得当前运行地址与链接地址的偏移量,存入r0中为0x30008000.

 //如果不需要重定位,即内核没有进行过搬移,就跳转。如果内核代码是存于NANDflash中的

//是需要搬移的。
  beq not_relocated

  add r5, r5, r0 //修改内核映像基地址此时r5=0x30008000
  add r6, r6, r0 //修改got表的起始和结束位置
  add ip, ip, r0

#ifndef CONFIG_ZBOOT_ROM
//S3C2410平台是需要一下调整的,将bss段以及堆栈的地址都进行调整
  add r2, r2, r0
  add r3, r3, r0
  add sp, sp, r0

//修改GOT(全局偏移表)表。根据当前的运行地址,修正该表  
1:  ldr r1, [r6, #0]  @ relocate entries in the GOT
  add r1, r1, r0  @ table.  This fixes up the
  str r1, [r6], #4  @ C references.
  cmp r6, ip
  blo 1b
#else

//S3C2410平台不会进入该分支,只对got表中在bss段以外的符号进行重定位
1:  ldr r1, [r6, #0]  @ relocate entries in the GOT
  cmp r1, r2   @ entry < bss_start ||
  cmphs r3, r1   @ _end < entry
  addlo r1, r1, r0  @ table.  This fixes up the
  str r1, [r6], #4  @ C references.
  cmp r6, ip
  blo 1b
#endif

//下面的代码,如果运行当前运行地址和链接地址相等,则不需进行重定位。直接清除bss段

not_relocated: mov r0, #0
1:  str r0, [r2], #4    //清BSS段,所有的arm程序都需要做这些的
  str r0, [r2], #4
  str r0, [r2], #4
  str r0, [r2], #4
  cmp r2, r3
  blo 1b


  bl cache_on  //打开cache

  mov r1, sp   @ malloc space above stack
  add r2, sp, #0x10000 //分配一段解压函数需要的内存缓冲

/*

head.S调用misc.c中的decompress_kernel刚解压完内核后,还没有进行重定位。

*/
  cmp r4, r2

//r4为内核执行地址,此时为0X30008000,r2此时为用户栈定,即解压函数所需内存缓冲的开始处,

//显然r4 < r2所以不会跳转。
  bhs wont_overwrite 
  sub r3, sp, r5  //r5是内核映像的开始地址0X30008000,sp为用户栈的栈顶,他们之差就是映像大小。

//将这个大小值乘以4,因为映像解压后不会超过解压前的4倍大小。

//r4为解压后内核开始地址,此时为0X30008000,r5为解压前映存放的开始位置,此时也为0X30008000。

//下面的判断是,看解压后的内核是不是会覆盖未解压的映像。显然是覆盖的,所以是不会跳转的。

  add r0, r4, r3, lsl #2 
  cmp r0, r5
  bls wont_overwrite

  mov r5, r2   //此时r2为解压函数缓冲区的尾部地址。
  mov r0, r5  //r0为映像解压后存放的起始地址,解压后的内核就紧接着存放在解压函数缓冲区的尾部。
  mov r3, r7 //r7中存放的是architecture ID,对于SMDK2410这个值为193; 

/*

解压函数是用C语言实现的在文件arch/arm/boot/compressed/misc.c中。

解压函数的是个参数是有r0~r3传入的。r0~r3的值在上面已初始化,再对照上面表格就很清楚了。

decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,int arch_id)

  output_start:指解压后内核输出的起始位置,此时它的值参考上面的图表,紧接在解压缓冲区后;

  free_mem_ptr_p:解压函数需要的内存缓冲开始地址;

 ulg free_mem_ptr_end_p:解压函数需要的内存缓冲结束地址,共64K;

arch_id :architecture ID,对于SMDK2410这个值为193; 

*/
  bl decompress_kernel

 

//解压完毕后,内核长度返回值存放在r0寄存器里。在内核末尾空出128字节的栈空间用,

//并且使其长度128字节对齐。

  add r0, r0, #127 + 128 
  bic r0, r0, #127  
/*
 * r0     = 解压后内核的长度
 * r1-r3  = 没使用

 * r4     = 内核执行地址
 * r5     = 解压后内核的起始地址,如上面初始化 mov r5, r2

 * r6     = 处理器ID
 * r7     = 体系结构 ID
 * r8     = 标记列表地址

 * r9-r14 = corrupted
 */

/*

上面只是将内核临时解压到了一个位置,下面还要将它重定位到0X30008000处。

标号reloc_start下面有一段重定位内核的程序。为了在内核重定位的过程中不至于将这段用于

重定位的代码给覆盖了,就先将这段用于内核重定位的代码搬到另一个地方,如下表。

linux 内核启动过程分析_第2张图片

 */
  add r1, r5, r0   //r1就是解压后内核代码的结束位置,下面就是将这段重定位代码搬移到r1地址处。
  adr r2, reloc_start //重定位代码起始地址
  ldr r3, LC1  //用于内核重定位的代码的长度
  add r3, r2, r3 //重定位代码的结束地址
1:  ldmia r2!, {r9 - r14}  //将这段重定位代码搬移到r1地址处,如上表。
  stmia r1!, {r9 - r14}
  ldmia r2!, {r9 - r14}
  stmia r1!, {r9 - r14}
  cmp r2, r3
  blo 1b
  add sp, r1, #128  //改变堆栈指针位置。

//搬移完成后刷新cache,因为代码地址变化了不能让cache再命中被内核覆盖的老地址。

  bl cache_clean_flush
  add pc, r5, r0  //r0 + r5 就是被搬移后的内核重定位代码的开始位置,reloc_start。下面将会讲到。

 

//如果内核映像没有被bootloader搬移过,上面程序就会跳到此处。

wont_overwrite: mov r0, r4
  mov r3, r7
  bl decompress_kernel
  b call_kernel

//这个表与文件arch/arm/kernel/vmlinux.lds.S

//这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)

  .type LC0, #object  //
LC0:  .word LC0   @ r1
  .word __bss_start  @ r2
  .word _end   @ r3
  .word zreladdr  @ r4
  .word _start   @ r5
  .word _got_start  @ r6
  .word _got_end  @ ip
  .word user_stack+4096  @ sp
LC1:  .word reloc_end - reloc_start
  .size LC0, . - LC0

 

下面是将解压后的内核代码重定位。

linux 内核启动过程分析_第3张图片

 //将解压后的内核代码搬到0X30008000处。

  .align 5
reloc_start: add r9, r5, r0  // r0 + r5就是解压后内核代码的结束位置加128字节栈空间。
  sub r9, r9, #128  @ do not copy the stack
  debug_reloc_start
  mov r1, r4 //r4为内核执行地址,即为0X30008000。
1:
  .rept 4  //将解压后的内核搬到r1处,即0X30008000处。

  ldmia r5!, {r0, r2, r3, r10 - r14} @ relocate kernel
  stmia r1!, {r0, r2, r3, r10 - r14}
  .endr

  cmp r5, r9
  blo 1b
  add sp, r1, #128  @ relocate the stack
  debug_reloc_end

//清除并关闭cache,清零r0,将machine ID存入r1,atags指针存入r2,再跳入0x30008000执行真正的内核Image

call_kernel: bl cache_clean_flush
  bl cache_off
  mov r0, #0   @ must be zero
  mov r1, r7   @ restore architecture number
  mov r2, r8   @ restore atags pointer
  mov pc, r4   @ call kernel

 

//内核映像zImage的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码

//(head.o misc.o),组成的。

//此时内核解压已经完成。内核启动要执行的第二个文件就是arch/arm/kernel/head.S文件。

 

linux/arch/arm/kernel/head.S linux内核映像解压后执行的第一个文件。

 

// PAGE_OFFSET  =  0xc0000000;  TEXT_OFFSET  = 0x00008000;

// PHYS_OFFSET  = 0x30000000;

#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)

#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)

/*

链接脚本文件 arch/arm/kernel/vmlinux.lds 指定了编译时程序段存放的位置。

SECTIONS

{

#ifdef CONFIG_XIP_KERNEL

. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

. = PAGE_OFFSET + TEXT_OFFSET;

#endif

.text.head : {

_stext = .;

_sinittext = .;

*(.text.head)

}

 

.init : {

……………………

}

*/

.section ".text.head", "ax"

ENTRY(stext)

msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE   // 禁止 FIQ IRQ ,设定 SVC 模式

 

mrc p15, 0, r9, c0, c0 获取  processor id

/* 判断 CPU 类型,查找运行的 CPU ID 值与 Linux 编译支持的 ID 值是否支持   */

bl __lookup_processor_type @ r5=procinfo r9=cpuid

movs r10, r5 @ invalid processor (r5=0)?

// /* 判断如果 r10 的值为 0 ,跳转到出错处理, */

beq __error_p @ yes, error 'p'

// 查询machine ID 并检查合法性

bl __lookup_machine_type @ r5=machinfo

movs r8, r5 @ invalid machine (r5=0)?

beq __error_a @ yes, error 'a'

bl __vet_atags   // 检查 bootloader传入的参数列表 atags 合法性

bl __create_page_tables   // 创建初始页表

 

ldr r13, __switch_data   //将列表 __switch_data 存到 r13中后面会跳到该列表出

adr lr, __enable_mmu //将程序段 __enable_mmu 的地址存到 lr中。

//此命令将导致程序段 __arm920_setup 的执行,后面会将到。

// r10中存放的基 地址是从 __lookup_processor_type 中得到的 ,如上面 movs r10, r5

    add pc, r10, #PROCINFO_INITFUNC  

ENDPROC(stext)

 

 

接下来将对上面遇到的几个程序段展开分析。

__lookup_processor_type

/**********************************************************************/

 

在讲解该程序段之前先来看一些相关知识。

 

内核做支持的每一种CPU 类型都由结构体 proc_info_list  来描述。

该结构体在文件arch/arm/include/asm/procinfo.h 中定义:

struct proc_info_list {

unsigned int cpu_val;

unsigned int cpu_mask;

unsigned long __cpu_mm_mmu_flags; /* used by head.S */

unsigned long __cpu_io_mmu_flags; /* used by head.S */

unsigned long __cpu_flush; /* used by head.S */

const char *arch_name;

const char *elf_name;

unsigned int elf_hwcap;

const char *cpu_name;

struct processor *proc;

struct cpu_tlb_fns *tlb;

struct cpu_user_fns *user;

struct cpu_cache_fns *cache;

};

对于 arm920 来说,其对应结构体在文件 linux/arch/arm/mm/proc-arm920.S

初始化。

.section ".proc.info.init", #alloc, #execinstr

 

.type __arm920_proc_info,#object

__arm920_proc_info:

.long 0x41009200

.long 0xff00fff0

.long   PMD_TYPE_SECT | /

PMD_SECT_BUFFERABLE | /

PMD_SECT_CACHEABLE | /

PMD_BIT4 | /

PMD_SECT_AP_WRITE | /

PMD_SECT_AP_READ

.long   PMD_TYPE_SECT | /

PMD_BIT4 | /

PMD_SECT_AP_WRITE | /

PMD_SECT_AP_READ

b __arm920_setup

………………………………

.section ".proc.info.init"表明了该结构在编译后存放的位置。

在链接文件 arch/arm/kernel/vmlinux.lds 中:

SECTIONS

{

#ifdef CONFIG_XIP_KERNEL

. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

. = PAGE_OFFSET + TEXT_OFFSET;

#endif

.text.head : {

_stext = .;

_sinittext = .;

*(.text.head)

}

 

.init : { /* Init code and data */

INIT_TEXT

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init)

__arch_info_end = .;

__tagtable_begin = .;

*(.taglist.init)

__tagtable_end = .;

………………………………

所有CPU类型对应的被初始化的 proc_info_list结构体都放在 __proc_info_begin

和__proc_info_end之间。

__lookup_processor_type:

// r3存储的是标号 3 的物理地址(由于没有启用 mmu ,所以当前肯定是物理地址)

adr r3, 3f

// R5=__proc_info_begin,r6=__proc_info_end,r 7=标号3处的虚拟地址。

ldmda r3, {r5 - r7} 

sub r3, r3, r7 // 得到虚拟地址和物理地址之间的offset

add r5, r5, r3 // 利用offset ,将 r5 r6 中保存的虚拟地址转变为物理地址

add r6, r6, r3 @ physical address space

1: ldmia r5, {r3, r4} // r3= cpu_val , r4= cpu_mask ,

and r4, r4, r9 //r9 中存放的是先前读出的 processor ID ,此处屏蔽不需要的位。

// // 查看代码和CPU 硬件是否匹配( 比如 想在arm920t 上运行为 cortex-a8 编译的内核?不让!)

teq r3, r4 

beq 2f  //如果匹配成功就返回

// PROC_INFO_SZ (proc_info_list 结构的长度,在这等于 48) 跳到下一个 proc_info_list

add r5, r5, #PROC_INFO_SZ 

//判断是否已经到了结构体 proc_info_list 存放区域的末尾 __proc_info_end

cmp r5, r6 

blo 1b

//如果没有匹配成功就将r5清零,如果匹配成功r5中放的是该CPU类型对应的结构体// proc_info_list 的基地址。

mov r5, #0

2: mov pc, lr //子程序返回。

ENDPROC(__lookup_processor_type)

 

   .long __proc_info_begin

   .long __proc_info_end

3: .long .

   .long __arch_info_begin

   .long __arch_info_end

 

/**********************************************************************/

 

 

__lookup_machine_type

 

/**********************************************************************/

 

每一个CPU 平台都可能有其不一样的结构体,描述这个平台的结构体是 machine_desc

这个结构体在文件arch/arm/include/asm/mach/arch.h 中定义:

struct machine_desc {

unsigned int nr; /* architecture number */

unsigned int phys_io; /* start of physical io */

………………………………

};

对于平台smdk2410 来说其对应 machine_desc 结构在文件

linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化:

MACHINE_START(SMDK2410, "SMDK2410") 

.phys_io = S3C2410_PA_UART,

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params = S3C2410_SDRAM_PA + 0x100,

.map_io = smdk2410_map_io,

.init_irq = s3c24xx_init_irq,

.init_machine = smdk2410_init,

.timer = &s3c24xx_timer,

MACHINE_END

对于宏MACHINE_START 在文件 arch/arm/include/asm/mach/arch.h 中定义:

#define MACHINE_START(_type,_name) /

static const struct machine_desc __mach_desc_##_type /

 __used /

 __attribute__((__section__(".arch.info.init"))) = { /

.nr = MACH_TYPE_##_type, /

.name = _name,

 

#define MACHINE_END /

};

 

__attribute__((__section__(".arch.info.init")))表明该结构体在并以后存放的位置。

在链接文件 链接脚本文件 arch/arm/kernel/vmlinux.lds

SECTIONS

{

#ifdef CONFIG_XIP_KERNEL

. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

. = PAGE_OFFSET + TEXT_OFFSET;

#endif

.text.head : {

_stext = .;

_sinittext = .;

*(.text.head)

}

 

.init : { /* Init code and data */

INIT_TEXT

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init)

__arch_info_end = .;

………………………………

在__arch_info_begin和 __arch_info_end之间存放了linux内核所支持的所有平台对应的 machine_desc 结构体。

 

 

   .long __proc_info_begin

   .long __proc_info_end

3: .long .

   .long __arch_info_begin

   .long __arch_info_end

 

 

__lookup_machine_type:

adr r3, 3b // // r3存储的是标号 3 的物理地址

// // R 4 = 标号3处的虚拟地址 ,r 5 = __arch_info_begin ,r 6= __arch_info_end

ldmia r3, {r4, r5, r6}

sub r3, r3, r4 @ get offset between virt&phys

add r5, r5, r3 @ convert virt addresses to

add r6, r6, r3 @ physical address space

//读取 machine_desc结构的 nr 参数,对于 smdk2410 来说该值是 MACH_TYPE_SMDK2410

// 这个值在文件 linux/arch/arm/tools/mach-types 中:
// smdk2410 ARCH_SMDK2410 SMDK2410 193

1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type

teq r3, r1 //r1就是 bootloader 传递过来的机器码,就是上面的平台编号。

beq 2f //如果匹配成功就返回

add r5, r5, #SIZEOF_MACHINE_DESC//没匹配成功就跳到下一个结构体 machine_desc

cmp r5, r6

blo 1b

mov r5, #0 //如果匹配失败就将 r5 清零。

2: mov pc, lr    //子程序返回。

ENDPROC(__lookup_machine_type)

/**********************************************************************/

 

__vet_atags //检查bootloader传入的参数列表atags的合法性

 

/**********************************************************************/

关于参数链表:

内核参数链表的格式和说明可以从内核源代码目录树中的 include/asm-arm/setup.h

找到,参数链表必须以ATAG_CORE  开始,以 ATAG_NONE 结束。这里的  ATAG_CORE

ATAG_NONE是各个参数的标记,本身是一个 32 位值,例如: ATAG_CORE=0x54410001

其它的参数标记还包括: ATAG_MEM32  ,  ATAG_INITRD  ,  ATAG_RAMDISK 

ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参

数链表。参数结构体的定义如下:  

 struct tag {
      struct  tag_header  hdr;
      union {
             struct tag_core  core;
       struct tag_mem32   mem;
          struct tag_videotext videotext;
struct tag_ramdisk  ramdisk;
struct tag_initrd     initrd;
          struct tag_serialnr     serialnr;
struct tag_revision  revision;
          struct tag_videolfb  videolfb;
          struct tag_cmdline  cmdline;
 struct tag_acorn       acorn;
struct tag_memclk    memclk;
        } u;
};

 

参数结构体包括两个部分,一个是 tag_header 结构体 , 一个是 u 联合体。

tag_header结构体的定义如下: 

   struct tag_header { 

                 u32 size;   

                 u32 tag; 

}; 

其中 size :表示整个  tag  结构体的大小 ( 用字的个数来表示,而不是字节的个数 ) ,等于

tag_header的大小加上  u 联合体的大小,例如,参数结构体  ATAG_CORE  的 

size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通过函数  tag_size(struct * tag_xxx)

来获得每个参数结构体的 size 。其中  tag :表示整个  tag  结构体的标记,如: ATAG_CORE

等。 

 

__vet_atags:

tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐

bne 1f

 

ldr r5, [r2, #0] //获取第一个 tag 结构的 size

//#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 判断该 tag 的长度是否合法

subs r5, r5, #ATAG_CORE_SIZE

bne 1f

ldr r5, [r2, #4]  //获取第一个 tag 结构体的标记,

ldr r6, =ATAG_CORE 

cmp r5, r6 //判断第一个 tag 结构体的标记是不是 ATAG_CORE

bne 1f  

 

mov pc, lr //正常退出

 

1: mov r2, #0

mov pc, lr  //参数连表不正确

ENDPROC(__vet_atags)

 

/**********************************************************************/

 

__create_page_tables

 

/**********************************************************************/

 

 

.macro pgtbl, rd

ldr /rd, =(KERNEL_RAM_PADDR - 0x4000)

.endm

 

 

__create_page_tables:

//r4 =  0x30004000 这是转换表的物理基地址,最终将写入CP15 的寄存器 2 C2

//这个值必须是 16K 对齐的。

pgtbl r4 

//为内核代码存储区域 创建页表,首先将内核起始地址-0x4000 到内核起始地址之间的 16K  

 // 存储器清0 ,将创建的页表存于此处。

mov r0, r4

mov r3, #0

add r6, r0, #0x4000

1: str r3, [r0], #4

str r3, [r0], #4

str r3, [r0], #4

str r3, [r0], #4

teq r0, r6

bne 1b

// proc_info_list结构中获取字段 __cpu_mm_mmu_flags ,该字段包含了存储空间访问权限 

//等。此处指令执行之后 r7=0x 00000c 1e

ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

 

/*

此处建立一个物理地址到物理地址的平板映射,这个映射将在函数paging_init().   被清除。

 r6 = 0x300  r3 = 0x30000c1e    [0x30004c00]=0x30000c1e

 */

mov r6, pc, lsr #20 @ start of kernel section

orr r3, r7, r6, lsl #20 @ flags + kernel base

str r3, [r4, r6, lsl #2] //字对齐

 

/*

MMU是通过 C2 中基地址(高 18 位)与虚拟地址的高 12 位组合成物理地址,在转换表中查找地址条目。 R4 中存放的就是这个基地址 0x30004000 。下面通过两次获取虚拟地址

KERNEL_START的高 12 位。 KERNEL_START 是内核存放的起始地址,为 0X30008000

 

 */

add r0, r4,  #(KERNEL_START & 0xff000000) >> 18  // r0 = 0x30007000

str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! // r0 存放的是转换表的起始位置

ldr r6, =(KERNEL_END - 1) //获取内核的尾部虚拟地址存于 r6

add r0, r0, #4 //第一个地址条目存放在 0x30007004 处,以后一次递增

add r6, r4, r6, lsr #18 //计算最后一个地址条目存放的位置

1: cmp r0, r6 //填充这之间的地址条目

add r3, r3, #1 << 20 //每一个地址条目代表了 1MB 空间的地址映射。物理地址将从  

 // 0x30100000  开始映射。0X30000000 开始的 1MB 空间将在下面映射。

strls r3, [r0], #4

bls 1b

 

#ifdef CONFIG_XIP_KERNEL

//如果是 XIP 就进行以下映射,这只是将内核代码存储的空间重新映射,

orr r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)

.if (KERNEL_RAM_PADDR & 0x00f00000)

orr r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)

.endif

add r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18

str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!

ldr r6, =(_end - 1)

add r0, r0, #4

add r6, r4, r6, lsr #18

1: cmp r0, r6

add r3, r3, #1 << 20

strls r3, [r0], #4

bls 1b

#endif

/*

映射0X30000000 开始的 1MB 空间。

PAGE_OFFSET = 0XC0000000,PHYS_OFFSET = 0X30000000,

*/

//r0 =  0x30007000, 上面是从 0x3000700 4开始存放地址条目的。

add r0, r4, #PAGE_OFFSET >> 18 

orr r6, r7, #(PHYS_OFFSET & 0xff000000)  //r6= 0x30000c1e

.if (PHYS_OFFSET & 0x00f00000)

orr r6, r6, #(PHYS_OFFSET & 0x00f00000) //

.endif

str r6, [r0] // 0x30000c1e 存于 0x30007000 处。

 

#ifdef CONFIG_DEBUG_LL //下面是为了调试而做的相关映射,跳过。

ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags

/*

 * Map in IO space for serial debugging.

 * This allows debug messages to be output

 * via a serial console before paging_init.

 */

ldr r3, [r8, #MACHINFO_PGOFFIO]

add r0, r4, r3

rsb r3, r3, #0x4000 @ PTRS_PER_PGD*sizeof(long)

cmp r3, #0x0800 @ limit to 512MB

movhi r3, #0x0800

add r6, r0, r3

ldr r3, [r8, #MACHINFO_PHYSIO]

orr r3, r3, r7

1: str r3, [r0], #4

add r3, r3, #1 << 20

teq r0, r6

bne 1b

#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)

/*

 * If we're using the NetWinder or CATS, we also need to map

 * in the 16550-type serial port for the debug messages

 */

add r0, r4, #0xff000000 >> 18

orr r3, r7, #0x7c000000

str r3, [r0]

#endif

#ifdef CONFIG_ARCH_RPC

/*

 * Map in screen at 0x02000000 & SCREEN2_BASE

 * Similar reasons here - for debug.  This is

 * only for Acorn RiscPC architectures.

 */

add r0, r4, #0x02000000 >> 18

orr r3, r7, #0x02000000

str r3, [r0]

add r0, r4, #0xd8000000 >> 18

str r3, [r0]

#endif

#endif

mov pc, lr  //子程序返回。

ENDPROC(__create_page_tables)

/**********************************************************************/

 

 

__arm920_setup

 

/**********************************************************************/

 

在上面程序段.section ".text.head", "ax" 的最后有这样几行:

ldr r13, __switch_data @ address to jump to after

@ mmu has been enabled

adr lr, __enable_mmu @ return (PIC) address

add pc, r10, #PROCINFO_INITFUNC

R10中存放的是在函数 __lookup_processor_type 中成功匹配的结构体 proc_info_list

对于arm920 来说在文件 linux/arch/arm/mm/proc-arm920.S 中有:

.section ".proc.info.init", #alloc, #execinstr

 

.type __arm920_proc_info,#object

__arm920_proc_info:

.long 0x41009200

.long 0xff00fff0

.long   PMD_TYPE_SECT | /

PMD_SECT_BUFFERABLE | /

PMD_SECT_CACHEABLE | /

PMD_BIT4 | /

PMD_SECT_AP_WRITE | /

PMD_SECT_AP_READ

.long   PMD_TYPE_SECT | /

PMD_BIT4 | /

PMD_SECT_AP_WRITE | /

PMD_SECT_AP_READ

b __arm920_setup

………………………………

add pc, r10, #PROCINFO_INITFUNC的意思跳到函数 __arm920_setup去执行。

 

.type __arm920_setup, #function  //表明这是一个函数

__arm920_setup:

mov r0, #0   //设置 r0 0

mcr p15, 0, r0, c7, c7 //使数据 cahche,  指令 cache 无效。

mcr p15, 0, r0, c7, c10, 4 //使 write buffer 无效。

#ifdef CONFIG_MMU

mcr p15, 0, r0, c8, c7 //使数据 TLB, 指令 TLB 无效。

#endif

adr r5, arm920_crval //获取 arm920_crval 的地址,并存入 r5

ldmia r5, {r5, r6} //获取 arm920_crval 地址处的连续 8 字节分别存入 r5,r6

mrc p15, 0, r0, c1, c0 //获取 CP15 下控制寄存器的值,并存入 r0

//通过查看 arm920_crval 的值可知该行是清除 r0 中相关位,为以后对这些位的赋值做准备

bic r0, r0, r5 

orr r0, r0, r6 //设置 r0 中的相关位,即为 mmu 做相应设置。

mov pc, lr  //上面有操作 adr lr, __enable_mmu ,此处将跳到程序段 __enable_mmu 处。

 

.size __arm920_setup, . - __arm920_setup

 

.type arm920_crval, #object

arm920_crval:

crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130

 

 

 

文件linux/arch/arm/kernel/head.S

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

orr r0, r0, #CR_A //使能地址对齐错误检测

#else

bic r0, r0, #CR_A

#endif

#ifdef CONFIG_CPU_DCACHE_DISABLE

bic r0, r0, #CR_C //禁止数据 cache

#endif

#ifdef CONFIG_CPU_BPREDICT_DISABLE

bic r0, r0, #CR_Z

#endif

#ifdef CONFIG_CPU_ICACHE_DISABLE

bic r0, r0, #CR_I //禁止指令 cache

#endif //配置相应的访问权限并存入 r5

mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | /

      domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | /

      domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | /

      domain_val(DOMAIN_IO, DOMAIN_CLIENT))

mcr p15, 0, r5, c3, c0, 0 //将访问权限写入协处理器

mcr p15, 0, r4, c2, c0, 0 //将页表基地址写入基址寄存器 C2 0X30004000

b __turn_mmu_on //跳转到程序段去打开 MMU

ENDPROC(__enable_mmu)

 

 

文件linux/arch/arm/kernel/head.S

__turn_mmu_on:

mov r0, r0

mcr p15, 0, r0, c1, c0, 0 //打开 MMU 同时打开 cache 等。

mrc p15, 0, r3, c0, c0, 0 @ read id reg //读取 id 寄存器

mov r3, r3

mov r3, r3 //两个空操作,等待前面所取的指令得以执行。

mov pc, r13  //程序跳转,如下面解释。

ENDPROC(__turn_mmu_on)

 

 

在前面有过这样的指令操作 ldr r13, __switch_data

mov pc, r13 就是将跳转到 __switch_data处。

 

在文件linux/arch/arm/kernel/head-common.S 中:

 

.type __switch_data, %object  //定义一个对象

__switch_data:

.long __mmap_switched  //由此可知上面程序将跳转到该程序段处。

.long __data_loc @ r4

.long _data @ r5

.long __bss_start @ r6

.long _end @ r7

.long processor_id @ r4

.long __machine_arch_type @ r5

.long __atags_pointer @ r6

.long cr_alignment @ r7

.long init_thread_union + THREAD_START_SP @ sp

 

/*

__data_loc 是数据存放的位置

_data 是数据开始的位置

        

__bss_start  bss 开始的位置

_end  bss 结束的位置 也是内核结束的位置

        

  .data  , 后面的 AT(__data_loc)  的意思是这部分的内容是在 __data_loc 中存储的 ( 要注意 , 储存的位置和链接的位置是可以不相同的 ).

 

这几个字段在文件 arch/arm/kernel/vmlinux.lds 中指定位置如下:

 SECTIONS

 {

 ……………………

 #ifdef CONFIG_XIP_KERNEL

 __data_loc = ALIGN(4); /* location in binary */

 . = PAGE_OFFSET + TEXT_OFFSET;

 #else

 . = ALIGN(THREAD_SIZE);

 __data_loc = .;

 #endif

 

 .data : AT(__data_loc) {  //此处数据存储在上面__data_loc处。

 _data = .; /* address in memory */

 

 *(.data.init_task)

…………………………

 

.bss : {

__bss_start = .; /* BSS */

*(.bss)

*(COMMON)

_end = .;

}

………………………………

init_thread_union 是 init进程的基地址. 在 arch/arm/kernel/init_task.c 中:

 

00033: union thread_union init_thread_union

00034:         __attribute__((__section__(".init.task"))) =

00035:                 { INIT_THREAD_INFO(init_task) };        

 

    对照 vmlnux.lds.S 中,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的

*/

 

__mmap_switched:

adr r3, __switch_data + 4

 

ldmia r3!, {r4, r5, r6, r7}  

cmp r4, r5 @ Copy data segment if needed

1: cmpne r5, r6  // __data_loc处数据搬移到 _data

ldrne fp, [r4], #4

strne fp, [r5], #4

bne 1b

 

mov fp, #0 //清除 BSS 段内容

1: cmp r6, r7    

strcc fp, [r6],#4

bcc 1b

 

ldmia r3, {r4, r5, r6, r7, sp}

str r9, [r4] @ Save processor ID

str r1, [r5] @ Save machine type

str r2, [r6] @ Save atags pointer

bic r4, r0, #CR_A @ Clear 'A' bit

stmia r7, {r0, r4} @ Save control register values

b start_kernel  //程序跳转到函数 start_kernel 进入 C 语言部分。

ENDPROC(__mmap_switched)

 

/**********************************************************************/

你可能感兴趣的:(linux 内核启动过程分析)