anyuan2002.com - vwin网

查找: 您的方位主页 > 电脑频道 > 电脑教程 > 阅览资讯:Linux kernel发动流程第一阶段
vwin娱乐场

Linux kernel发动流程第一阶段

2019-04-04 06:36:38 来历:www.anyuan2002.com 【

 

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
CACHECPU和内存之间的高速缓冲存储器,又分成数据缓冲器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上)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
 

本文地址:http://www.anyuan2002.com/dnjc/100247.html
Tags: 发动 linux kernel
修改:vwin网
关于咱们 | 联络咱们 | 友情链接 | 网站地图 | Sitemap | App | 回来顶部