Linux kernel启动流程第一阶段


head.S:kernel第一阶段入口

来自:http://blog.csdn.net/ooonebook/article/details/52710290

1safe_svcmode_maskallr9;关闭普通中断、快速中断,使能SVC模式

实现代码:arch/arm/include/asm/assembler.h

注解:来自http://blog.csdn.net/crosskernel/article/details/21091819

从Hyper态返回SVC态
//reg—暂存寄存器
.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6
    //读取cpsr到暂存寄存器reg
mrs \reg , cpsr

/*以下两条指令区分当前cpsr是否处在HYP_MODE,

  *若处在HYP_MODE模式,标志位清零

  */
eor \reg, \reg, #HYP_MODE
tst \reg, #MODE_MASK
bic \reg , \reg , #MODE_MASK
    //在暂存寄存器里存放SVC控制位
orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
THUMB( orr  \reg , \reg , #PSR_T_BIT )
    /*若当前寄存器处于hyper模式,返回SVC模式走一个杜撰的HVC异常返回。*/
bne 1f
orr \reg, \reg, #PSR_A_BIT
    /*设置返回到SVC态的地址是标号2,即该宏调用的后一条指令。*/ 
adr lr, BSYM(2f)
    //退出hyper后的cpsr寄存器
msr spsr_cxsf, \reg
    //放到ELR_hyp中
__MSR_ELR_HYP(14)
    //返回到SVC态
__ERET
    //当前处理器不在hyper状态,强制切换到SVC态
1: msr  cpsr_c, \reg
2:

2proccessor info的获取
cpu idprocinfo是一一对应的关系,所以可以通过cpu id来获取到对应的procinfo结构体。 
procinfo使用proc_info_list结构体,用来说明一个cpu的信息,包括这个cpuID号,对应的内核数据映射区的MMU标识等等。
注意: proc_info_list结构体中存在MMU标识,也就是我们需要在打开MMU之前需要先获取procinfo的原因,因为打开MMU之前需要配置临时内核页表,而配置临时内核页表需要这里的MMU标识来进行设置。
 
对应代码:
1mrc p15, 0, r9, c0, c0      @ get processor id

解释:arm体系将CPUID(处理器标识符,主标识符)存放在协处理器cp15c0寄存器中。 

2bl__lookup_processor_type   @r5 = procinfor9 = cupid

arch/arm/kernel/head-common.S 通过比较各个CPU的procinfo中的cpu id的值来查找对应的procinfo结构体。若不支持当前CPU,则r5=0

3)判断r5,若为0,则打印错误。

3地址的一次转换

  @ 在调用__enable_mmu前使用的都是物理地址,而内核却是以虚拟地址连接的,这里进行一次转换

#ifndef CONFIG_XIP_KERNEL
adr     r3,2f             @
r3= 第124行代码的物理地址
ldmia   r3, {r4,r8}         @r4= 第124行代码的虚似地址,r8=PAGE_OFFSET
sub     r4, r3,r4     @ (PHYS_OFFSET - PAGE_OFFSET)即物理地址与虚似地址差值
add     r8, r8,r4     @ PHYS_OFFSET r8=PAGE_OFFSET对应的物理地址
#else
ldr     r8,=PLAT_PHYS_OFFSET    @ RAM的起始物理地址,值为0x30000000

L124               2:     .long   .   @ "."号表示当前这行代码编译连接后的虚似地址
L125                       .long   PAGE_OFFSET

4、验证dtb    bl __vet_atags

实现代码:arch/arm/kernel/head-common.S

TIPS:

1dtb里面存放了各种硬件信息,如果dtb有问题,会导致后续开机过程中读取的设备信息有问题而导致无法开机。

2、在生成dtb的时候会在头部上添加一个幻数magic,而验证dtb是否合法主要也就是看这个dtbmagic是否和预期的值一致。其中magic是固定值:0xd00dfeed(大端)或者0xedfe0dd0(小端)我们只要提取待验证dtb的地址(Uboot传入的r2)上的数据的前四个字节,与0xd00dfeed(大端)或者0xedfe0dd0(小端)进行比较,如果匹配的话,就说明对应待验证dtb就是一个合法的dtb

代码注解:

Ldr r5, [r2, #0]    @获取前4个字节放在r5

Ldrr6, =OF_DT_MAGIC @获取magic放在r6

5bl__create_page_tables(创建临时内核页表====>打开MMU)

实现代码:arch/arm/kernel/head.S

TIPS:

1MMUMemory ManageUnion):

将线性地址(虚拟地址)映射为物理地址(RAM地址);

提供硬件机制的内存访问授权;

根据页表找到对应关系以及权限;

2为了打开MMU,内核需要创建一个临时内核页表,用于kenrel启动过程中的打开MMU的过渡阶段。 并且,使用的是段式管理的方法

代码注解:

1)获取内核页表的起始地址

pgtbl     r4, r8                @ page table address

pgtbl 宏用于通过DRAM物理地址来获取页表的物理地址。 
前面我们已经知道r8用于存放DRAM的起始物理地址,r4则是要存放计算得到的页表物理地址。 
pgtbl
宏如下:

arch/arm/kernel/head.S
    .macro    pgtbl, rd, phys
    add    \rd, \phys, #TEXT_OFFSET
    sub    \rd, \rd, #PG_DIR_SIZE
    .endm

kernel在放在DRAM上偏移TEXT_OFFSET的位置上。 linux规定将TEXT_OFFSET之前的PG_DIR_SIZE大小的空间用作临时页表。 
所以计算方式如下: 
kernel
起始地址=DRAM起始物理地址+TEXT_OFFSET=0x20008000 
内核页表地址=kernel起始地址-PG_DIR_SIZE=0x20004000 =>PG_DIR_SIZE=0x4000(16K)
所以代码换算成如下计算: 
\rd(r4) = phys(r8) +TEXT_OFFSET 
\rd(r4) = \rd(r4) -PG_DIR_SIZE
 

2)清零页表

    str    r3, [r0], #4      

@ r0(临时内核页表物理地址)指向的寄存器上开始写入0值,每16个字节一个循环

 

   str    r3, [r0], #4

   str    r3, [r0], #4

   str    r3, [r0], #4

    teq   r0, r6    

每16个字节一循环可以减少teq的频率,同时不会出现清零越界(因:页表大小为16K)

3)设置MMU的标识并存放到r7寄存器

ldr    r7,[r10, #PROCINFO_MM_MMUFLAGS]@ mm_mmuflags

PROCINFO_MM_MMUFLAGS对应如下

DEFINE(PROCINFO_MM_MMUFLAGS,    offsetof(struct proc_info_list, __cpu_mm_mmu_flags));

4)创建映射表

未懂

6b  __enable_mmu(调用平台特定的__CPU_flush函数(在结构体proc_info_list中),使能MMU)

代码:

ldr r13, =__mmap_switched       @ address to jump to after

                                @ mmu has been enabled

                                @ __mmap_switched函数的地址放在寄存器

                                @ __mmap_switched实现了打开MMU之后跳转到start_kernel

 

    adr    lr, BSYM(1f)              @ return (PIC) address

  @简单说,就是存储了调用子程序后返回的指令地址

 

    movr8, r4              @ set TTBR1 to swapper_pg_dir

@ 把临时内核页表的地址放在r8寄存器中

 

    ldrr12, [r10, #PROCINFO_INITFUNC]

@ cpu对应procinfo中的__cpu_flush存放到r12寄存器中

@ __cpu_flush成员存放的是cpu对应架构的setup函数的地址,在结构体proc_info_list中

@ 对于s5pv210来说,这个值就是__v7_setup的连接地址。

 

    addr12, r12, r10

    retr12

@ 这里实现为跳转到__v7_setup的物理地址上,也就是调用__v7_setup

 

1:  b   __enable_mmu

@ 跳转到__enable_mmu

__enable_mmu:

·        需要先将页表物理地址写入到cp15c2寄存器中

·        需要在cp15c3寄存器中写入位域相应的权限

·        配置cp15c1寄存器,用来控制MMU的相应功能

·        跳转到__turn_mmu_on

__turn_mmu_on:真正的打开MMU的函数,打开MMU后CPU会把所有地址都当做虚拟地址处理。

注:__turn_mmu_on中会执行命令:

mov r3, r13

ret r3

之前在head.S中有指令“ldr r13, =__mmap_switched”,因此会直接跳转去执行__mmap_switched,该函数负责跳转至内核

               

7、跳转至内核

准备阶段:

·        数据段的准备 

·        堆栈段的准备 

·        一些后续会访问到的变量的设置(CPU IDmachine id&dtb、当前进程堆栈指针)

·        当前进程堆栈指针的设置 

代码:

__mmap_switched_data结构体:

__mmap_switched_data:

    .long   __data_loc          @ r4

    .long   _sdata              @ r5

    .long   __bss_start         @ r6

    .long   _end                @ r7

    .long   processor_id            @ r4

    .long   __machine_arch_type     @ r5

    .long   __atags_pointer         @ r6

#ifdefCONFIG_CPU_CP15

    .long   cr_alignment            @ r7

#else

    .long   0               @ r7

#endif

    .long   init_thread_union +THREAD_START_SP @ sp

    .size  __mmap_switched_data, . - __mmap_switched_data

__mmap_switched:

    adrr3, __mmap_switched_data

@ __mmap_switched_data的地址加载到r3

 

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

@ __mmap_switched_data(r3)上的值分别加载到r4r5r6r7寄存器中,__mmap_switched_data前面说明了

@ 经过上述动作,r4r5r6r7寄存器分别存放了如下值

@ r4 -> __data_loc:数据段存储地址

@ r5 -> _sdata:数据段起始地址

@ r6 -> __bss_start:堆栈段起始地址

@ r7 -> _end:堆栈段结束地址

 

    cmpr4, r5              @ Copy data segment if needed

1:  cmpne   r5, r6

   ldrne   fp, [r4], #4

   strne   fp, [r5], #4

    bne 1b

@ 判断数据段存储地址(r4)和数据段起始地址(r5)

@ 如果不一样的话需要搬移到数据段起始地址(r5)上。

 

    movfp, #0              @ Clear BSS (and zero fp)

1:  cmp r6, r7

   strcc   fp, [r6],#4

    bcc 1b

@ 清空堆栈段。

@ 从堆栈段起始地址(r6)开始写入0,一直写到地址为堆栈段结束地址(r7)

 

 ARM(  ldmia   r3, {r4, r5, r6, r7, sp})

 THUMB(ldmia   r3, {r4, r5, r6, r7}    )

 THUMB(ldr sp, [r3, #16]       )

@ 继续将__mmap_switched_data(r3)上的值分别加载到r4r5r6r7sp寄存器中,注意是前面r3已经加载过一部分了,地址和__mmap_switched_data已经不一样了

@ 经过上述动作,r4r5r6r7寄存器分别存放了如下值

@ r4 -> processor_id变量地址:其内容是cpu处理器ID

@ r5 -> __machine_arch_type变量地址:其内容是machine id

@ r6 -> __atags_pointer变量地址:其内容是dtb的地址

@ r7 -> cr_alignment变量地址:其内容是cp15c1的寄存器的值

@ sp-> init_thread_union + THREAD_START_SP,设置了当前进程的堆栈

 

    strr9, [r4]            @ Save processor ID

@ cpu处理器id(r9)放到processor_id变量中([r4])

 

    strr1, [r5]            @ Save machine type

@ mechine id(r1)存放到__machine_arch_type变量中([r5])

 

    strr2, [r6]            @ Save atags pointer

@ dtb的地址指针(r2)存放到__atags_pointer变量中([r6])

 

    cmpr7, #0

   strne   r0, [r7]            @ Save control register values

@ cp15c1的寄存器的值(r0)存放到cr_alignment变量中([r7])

 

   b   start_kernel

@ 跳转到start_kernel中,也就是启动流程的第二阶段。

ENDPROC(__mmap_switched)

Kernel启动流程中的Tips:

1、 Kernel一般会存在于存储设备上,比如FLASH\EMMC\SDCARD. 因此,需要先将kernel镜像加载到RAM的位置上,CPU才可以去访问到kernel

但是注意,加载的位置是有要求的,一般是加载到物理RAM偏移0x8000的位置,也就是要在前面预留出32KRAMkernel会从加载的位置上开始解压,而kernel前面的32K空闲RAM中,16K作为boot params,16K作为临时页表 

2、 Arch/arm/kernel/head.S(kernel的入口函数)

3、 bootloader需要通过设置PC指针到kernel的入口代码处(也就是kernel的加载位置)来实现kernel的跳转。 

Kernel的硬件要求如下(解释了bootloader为何去那样初始化硬件):

MMU =off 
MMU用来处理物理地址到虚拟内存地址的映射,因此需要软件上需要先配置其映射表(也就是后续文章会说明的页表)。MMU关闭的情况下,CPU寻址的地址都是物理地址,也就是不需要经过转化直接访问相应的硬件。一旦打开之后,CPU寻址的所有地址都是虚拟地址,都会经过MMU映射到真正的物理地址上,即使你在代码中访问的是一个物理地址,也会被当作虚拟内存地址使用。 
而映射表是由kernel自己创建的,因此,在创建映射表之前kernel访问的地址都是物理地址,所以必须保证MMU是关闭状态。 

 D-cache= off 
CACHE
CPU和内存之间的高速缓冲存储器,又分成数据缓冲器D-cache和指令缓冲器I-cache
数据Cache一定要关闭,否则可能kernel刚启动的过程中,去取数据的时候,从Cache里面取,而这时候RAM中数据还没有Cache过来,导致数据预取异常 
博主的理解是,假设打开MMU之前,cache上存了一个项地址0x20000000、数据0xffff0000”,打开MMU之后,读取0x20000000地址上(虚拟地址)数据,但是此时会直接从cache中读到项地址0x20000000、数据0xffff0000”,但实际上对应物理地址上的数据并不是这个,所以会导致读取的数据错误。 


位置

默认值

说明

KERNEL_RAM_ADDR

arch/arm/kernel/head.S +26

0xc0008000

kernel在RAM中的虚拟地址

PAGE_OFFSET

include/asm-arm/memeory.h +50

0xc0000000

内核空间的起始虚拟地址

TEXT_OFFSET

arch/arm/Makefile +131

0x00008000

内核在RAM中起始位置相对于

RAM起始地址的偏移

TEXTADDR

arch/arm/kernel/head.S +49 

0xc0008000 

kernel的起始虚拟地址


PHYS_OFFSET

include/asm-arm/arch- *** /memory.h

平台相关

RAM的起始物理地址,对于s3c2410来说在include/asm-arm/arch-s3c2410/memory.h下定义,值为0x30000000(ram接在片选6上)

猜你喜欢

转载自blog.csdn.net/liangzaiEvil/article/details/78775602