linux内核启动资料_第1页
linux内核启动资料_第2页
linux内核启动资料_第3页
linux内核启动资料_第4页
linux内核启动资料_第5页
已阅读5页,还剩85页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux启动(1)【转】

LinuxB^}\kemeKarch\arm\boot\compressed\head.S分析

这段代码是linuxboot后执行的第一个程序,完成的主要工作是解压内核,然后

跳转到相关执行地址。这部分代码在做驱动开发时不需要改动,但分析其执行流

程对是理解android的第一步

开头有一段宏定义这是gnuarm汇编的宏定义。关于GUN的汇编和其他编译器,

在指令语法上有很大差别,具体可查询相关GUN汇编语法了解

另外此段代码必须不能包括重定位部分。因为这时一开始必须要立即运行的。所

谓重定位,比如当编译时某个文件用到外部符号是用动态链接库的方式,那么该

文件生成的目标文件将包含重定位信息,在加载时需要重定位该符号,否则执行

时将因找不到地址而出错

#ifdefDEBUG〃开始是调试用,主要是一些打印输出函数,不用关心

#ifdefined(CONFIG_DEBUGJCEDCC)

……具体代码略

#endif

宏定义结束之后定义了一个段,

.section".start",#alloc,#execinstr

这个段的段名是.start,#alloc表示Sectioncontainsallocateddata,

#execinstr表示Sectioncontainsexecutableinstructions.

生成最终映像时,这段代码会放在最开头

.align

start:

.typestart,#function/*.type指定start这个符号是函数类型*/

.rept8

movrO,rO〃将此命令重复8次,相当于nop,这里是为中断向量保存空间

.endr

bIf

.word0x016f2818@Magicnumberstohelptheloader

.wordstart@absoluteload/runzlmage

〃此处保存了内核加载和运行的地址,实质上也是本函数的运行地址

address

.word_edata@内核结束地址

〃注意这些地址在顶层vmlixu.lds(具体在/kernel文件夹里)里进行了定义,是

链接的地址,加载内核后可能会进行重定位

1:movr7,rl@保存architectureID,这里是从bootload传递进来的

movr8,r2@保存参数列表atags指针

rl和己中分别存放着由bootloader传递过来的architectureID和指向标记列表

的指针。这里将这两个参数先保存。

#ifndef_ARM_ARCH_2_

/*

*BootingfromAngel-needtoenterSVCmodeanddisable

*FIQs/IRQs(numericdefinitionsfromangelarm.hsource).

*Weonlydothisifwewereinusermodeonentry.

7

读取cpsr并判断是否处理器处于supervisor模式从bootload进入kernel.

系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两

条指令。之后是再次确认中断关闭,并完成cpsr写入

Angel是ARM的调试协议,一般用的是MULTMCE°ANGLE需要在板子上有驻

留程序,然后通过串口就可以调试了。用过的AXD或trace调试环境的话,对此

应该比较熟悉。

not_angel:〃若不是通过angel调试进入内核

mrsr2,cpsr@turnoffinterruptsto

orrr2,r2,#0xc0@preventangelfromrunning

msrcpsr_c,r2〃这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ

#else

teqppc,#0x0c000003@turnoffinterrupts

常用TEQPPC,#(新模式编号)来改变模式

#endif

另外链接器会把一些处理器相关的代码链接到这个位置,也就是

arch/arm/boot/compressed/head-xxx.S文件中的代码。在高通平台下,这个

文件是head-msm.S连接脚是compress/vmlinux.lds,其中部分内容大致如下

在连接时,连接器根据每个文件中的段名将相同的段合在一起,比如将head.S

和head-msm.S的.start段合在一起

SECTIONS

=TEXT_START;

_text=

.text:{

_start=

*(.start)

*(.text)

*(.text.*)

*(.fixup)

*(.gnu.warning)

*(.rodata)

*(.rodata.*)

*(.glue_7)

*(.glue_7t)

*(.piggydata)

=ALIGN(4);

)

_etext=

)

下面即进入.text段

.text

adrrO,LCO〃当前运行时LCO符号所在地址位置,注意,这里用的是adr指令,

这个指令会根据目前PC的值,计算符号相对于PC的位置,是个相对地址。之所

以这样做,是因为下面指令用到了绝对地址加载Idmia指令,必须要调整确定目

前LCO的真实位置,这个位置也就是用adr来计算

IdmiarO,{rl,r2,r3,r4,r5,r6,ip,sp}

subsrO,rO,rl@〃这里获得当前LCDO实际地址与链接地址差值

〃rl即是LC0的连接地址,也即由vmlinux.lds定位的地址

〃差值存入rO中。

beqnotjelocated〃如果相等不需要重定位,因为已经在正确的〃地址运行了,

重定位的原因是,MMU单元未使能,不能进行地址映射,必须要手工重定位。

下面举个简单例子说明:

如果连接地址是OxcOOOOOOO,那么LCO的连接地址假如连接为OxcOOOOOlO,那

么LCO相对于连接起始地址的差为0x10,当此段代码是从OxcOOOOOOO运行的话,

那么执行adrr0,LCO的值实际上按下面公式计算:

RO=PC+OxlO,由于PC=连接处的值,可知,此时是在ram中运行,同理如果

是在不是在连接处运行,则假设是在0x00000000处运行,则

R0=0x00000000+0xl0,可知,此时不是在ram的连接处运行。

上面这几行代码用于判断代码是否已经重定位到内存中,LCO这个符号在head.S

中定义如下,实质上相当于c语言的全局数据结构,结构的每个域存储的是一个

指针。指针本身的值代表不同的代码段,已经在顶层连接脚本vmlinuxJds里进

行了赋值,比如一start是内核开始的地址

.typeLCO,#object

LCO:.wordLCO@rl〃这个要加载到rl中的LCO是链接时LCO的地址

.word_bss_start@r2

.word_end@r3

.wordzreladdr@r4

.word_start@r5

.word_got_start@r6

.word_got_end@ip

.worduser_stack+4096@sp

通过当前运行时LCO的地址与链接器所链接的地址进行比较判断。若相等则是运

行在链接的地址上。

如果不是运行在链接的地址上,则下面的代码必须修改相关地址,进行重新运行

/*

*r5-zlmagebaseaddress

*r6-GOTstart

*ip-GOTend

7

〃修正实际运行的位置,否则跳转指令就找不到相关代码

add⑸r5,rO〃修改内核映像基地址

addr6,r6,rO

addip,ip,rO〃修改got表的起始和结束位置

#ifndefCONFIG_ZBOOT_ROM

/*若没有定义CONFIG_ZBOOT_ROM,此时运行的是完全位置无关代码

位置无关代码,也就是不能有绝对地址寻址。所以为了保持相对地址正确,

需要将bss段以及堆栈的地址都进行调整

*r2-BSSstart

*r3-BSSend

*sp-stackpointer

7

add22ro

addr3,r3,rO

addsp,sp,rO

〃全局符号表的地址也需要更改,否则,对全局变量引用将会出错

1:Idrrl,[r6,#0]@relocateentriesintheGOT

addrl,rl,rO@table.Thisfixesupthe

strrl,[r6],#4@Creferences.

cmpr6,ip

biolb

#else〃若定义了CONFIG_ZBOOT_ROM,只对got表中在bss段以外的符号

进行重定位

1:Idrrl,[r6,#0]@relocateentriesintheGOT

cmprl,r2@entry<bss_start||

cmphsr3,rl@_end<entry

addlorl,rl,rO@table.Thisfixesupthe

strrl,[r6],#4@Creferences.

cmpr6,ip

biolb

#endif

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

not_relocated:movrO,#0

1:strrO,[r2],#4@clearbss

strrO,[r2],#4

strrO,[r2],#4

strrO,[r2],#4

cmpr2,r3

biolb

之后跳转到cache_on处

blcache_on

cache_on定义

.align5

cache_on:movr3,#8@cache_onfunction

bcall_cache_fn

把r3的值设为8。这是一个偏移量,也就是索引projtypes中的操作函数。

然后跳转到call_cache_fn。这个函数的定义如下:

call_cache_fn:

adrrl2,proc_types〃把proc_types的相对地址加载到r12中

#ifdefCONFIG_CPU_CP15

mrcpl5,0,r6,cO,cO@getprocessorID

#else

Idrr6,=CONFIG_PROCESSOR_ID

#endif

1:Idrrl,[rl2,#0]@getvalue

Idrr2,[rl2,#4]@getmask

eorrl,rl,r6@(realAmatch)

tstrl,r2@是否和CPUID匹配?

addeqpc,rl2,r3@用刚才的偏移量,查找〃到cache操作函数,找到后就执行

相关操作,比如执行b_armv7_mmu_cache_on

//

addrl2,rl2,#4*5〃如果不相等,则偏移到下个proc_types结构处

bib

addeqpc,rl2,r3@callcachefunction

proc_type的定义如下,实质上还是一张数据结构表

.typeproc_types,#object

proc_types:

.word0x41560600@ARM6/610

.wordOxffffffeO

b_arm6_mmu_cache_off@works,butslow

b_arm6_mmu_cache_off

movpc,Ir

@b_arm6_mmu_cache_on@untested

@b_arm6_mmu_cache_off

@b_armv3_mmu_cache_flush

.word0x00000000@oldARMID

.wordOxOOOOfOOO

movpc,Ir

movpc,Ir

movpc,Ir

.word0x41007000@ARM7/710

.word0xfff8fe00

b_arm7_mmu_cache_off

b_arm7_mmu_cache_off

movpc,Ir

.word0x41807200@ARM720T(writethrough)

.wordOxffffffOO

b_armv4_mmu_cache_on

b_armv4_mmu_cache_off

movpc,Ir

.word0x41007400@ARM74x

.wordOxffOOffOO

b_armv3_mpu_cache_on

b_armv3_mpu_cache_off

b_armv3_mpu_cache_flush

.word0x41009400@ARM94x

.wordOxffOOffOO

b_armv4_mpu_cache_on

b_armv4_mpu_cache_off

b_armv4_mpu_cache_flush

.word0x00007000@ARM7IDs

.wordOxOOOOfOOO

movpc,Ir

movpc,Ir

movpc,Ir

@EverythingfromhereonwillbethenewIDsystem.

.word0x4401al00@sallO/sallOO

.wordOxffffffeO

b_armv4_mmu_cache_on

b_armv4_mmu_cache_off

b_armv4_mmu_cache_flush

.word0x6901bll0@salllO

.wordOxfffffffO

b_armv4_mmu_cache_on

b_armv4_mmu_cache_off

b_armv4_mmu_cache_flush

@ThesematchonthearchitectureID

.word0x00020000@

.word0x00Of0000//

b_armv4_mmu_cache_on

b_armv4_mmu_cache_on〃指令的地址

b_armv4_mmu_cache_off

b_armv4_mmu_cache_flush

.word0x00050000@ARMv5TE

.word0x00Of0000

b_armv4_mmu_cache_on

b_armv4_mmu_cache_off

b_armv4_mmu_cache_flush

.word0x00060000@ARMv5TEJ

.word0x00Of0000

b_armv4_mmu_cache_on

b_armv4_mmu_cache_off

b_armv4_mmu_cache_flush

.word0x0007bOO0@ARMv6

.word0x0007f000

b_armv4_mmu_cache_on

b_armv4_mmu_cache_off

b_armv6_mmu_cache_flush

.word0@unrecognisedtype

.word0

movpc,Ir

movpc,Ir

movpc,Ir

.sizeproc_types,.-proc_types

找到执行的cache函数后,就用上面的addeqpc,rl2,r3直接跳转,例如执行

下面这个处理器结构的cache函数

Linux启动(2)【转】

\kerneKarch\arm\boot\compressed\head・S分析(2)

_armv7_mmu_cache_on:

movrl2"r〃注意,这里需要手工保存返回地址!!这样做的原因是下面的bl

指令会覆盖掉原来的Ir,为保证程序正确返回,需要保存原来Ir的值

bl_setup_mmu

movrO,#0

mcrpl5,0,rO,c7,clO,4@drainwritebuffer

mcrpl5,0,rO,c8,c7,0@flushI,DTLBs

mrcpl5,0,rO,cl,cO,0@readcontrolreg

orrrO,rO,#0x5000@I-cacheenable,RRcachereplacement

orrrO,rO,#0x0030

bl_common_mmu_cache_on

movrO,#0

mcrpl5,0,rO,c8,c7,0@flushI,DTLBs

movpc,rl2〃返回至Ucache_on

这个函数首先执行_setup_mmu,然后清空writebuffer、I/Dcache、TLB.接着

打开i-cache,设置为Round-robinreplacement。调用

_common_mmu_cache_on,JTJFmmu和d-cache.把页表基地址和域访问控

制写入协处理器寄存器c2、c3._common_mmu_cache_on函数数定义如下:

_common_mmu_cache_on:

#ifndefDEBUG

orrrO,rO,#0x000d@Writebuffer,mmu

#endif

movrl,#-1〃-1的补码是ffffffff,

mcrpl5,0,r3,c2,cO,0@把页表地址存于协处理器寄存器中

mcrpl5,0,rl,c3,cO,0@设置domainaccesscontrol寄存器

blf

.align5@cachelinealigned

1:mcrpl5,0,rO,cl,cO,0@loadcontrolregister

mrcpl5,0,rO,cl,cO,0@andreaditbackto

subpc,Ir,rO,Isr#32@properlyflushpipeline

重点来看一下_setup_mmu这个函数,定义如下:

_setup_mmu:subr3,r4,#16384@Pagedirectorysize

bicr3,r3,#Oxff@Alignthepointer

bicr3,r3,#Ox3fOO

这里r4中存放着内核执行地址,将16K的一级页表放在这个内核执行地址下面的

16K空间里,上面通过subr3,r4,#16384获得16K空间后,又将页表的起始地

址进行16K对齐放在r3中。即ttb的低14位清零。

〃初始化页表,并在RAM空间里打开cacheable和bufferable位

movrO,r3

movr9,rO,Isr#18

movr9,r9,Isl#18@startofRAM

addrlO,r9,#0x10000000@areasonableRAMsize

上面这几行把一级页表的起始地址保存在r0中,并通过rO获得一个ram起始地

址(每个页面大小为1M)然后映射256Mram空间,并把对应的描述符的C和B

位均置“1”

movrl,#0x12〃一级描述符的bit[l:O]为10,表示这是一个section描述符。

也即分页方式为段式分页

orrrl,rl,#3<<10〃一级描述符的accesspermissionbits为31.

add2r3,#16384〃•级描述符表的结束地址存放在己中。

1:cmprl,r9@ifvirt>startofRAM

orrhsrl,rl,#0x0c@setcacheable,bufferable

cmprl,rlO@ifvirt>endofRAM

bichsrl,rl,#0x0c@clearcacheable,bufferable

strrl,[rO],#4@1:1mapping

addrl,rl,#1048576〃下个IM物理空间,每个页框IM。

teqrO,r2

bnelb

因为打开cache前必须打开mmu,所以这里先对页表进行初始化,然后打开

mmucache0

上面这段就是对一级描述符表(页表)的初始化,首先比较这个描述符所描述的

地址是否在那个256M的空间中,如果在则这个描述符对应的内存区域是

如果不在则然后将描

cacheable,bufferableononcacheable,nonbufferable.

述符写入一个一级描述符表的入口,并将一级描述符表入口地址加4,而指向下一

个IMsection的基地址。如果页表入口未初始化完,则继续初始化。

页表大小为16K,每个描述符4字节,刚好可以容纳4096个描述符,每个描述符

映射1M,那么4096*所以这里就映射了4O96*1M=4G的空间。因此16K的页

完全可以把256M地址空间全部映射

movrl,#0xle

orrrl,rl,#3<<10〃这两行将描述的bit[ll:10]bit[4:l]置位,

〃具体置位的原因,在ARM11的页表项描述符里有说明,由于没找到完整的文档,

这里只给出图示:

movr2,pc,Isr#20

orrrl,rl,r2,Isl#20〃将当前地址进IM对齐,并与rl中的内容结合形成一个描

述当前指令所在section的描述符。

addr0,r3,r2,Isl#2〃r3为刚才建立的一级描述符表的起始地址。通过将当前地

〃址(pc)的高12位左移两位(形成14位索弓I)与r3中的地址

〃(低14位为0)相加形成一个4字节对齐的地址,这个

〃地址也在16K的一级描述符表内。当前地址对应的

〃描述符在一级页表中的位置

strrl,[r0],#4

addrl,rl,#1048576

strrl,r0]〃这里将上面形成的描述符及其连续的下一个section描述

〃写入上面4字节对齐地址处(一级页表中索引为r2左移

〃2位)

movpc,lr〃返回,调用此函数时,调用指令的下一语句movr0,#0的地址保存

在lr中

这里进行的是一致性的映射,物理地址和虚拟地址是一样。

_common_mmu_cache_on最后执行movpc,rl2返回cache_on,为何返回

到的是cacheqn呢?这就是上面解释保存上的原因,因为原来的lr保存了执行

blcache_on语句的下条指令,因此能正确返回!

下一条指令也即是下面开始

movrl,sp@栈空间大小是4096字节,那〃么在栈空间地址上面再分配64K字节

空间

addr2,sp,#0x10000@分配64k字节。

栈的分配如下:

.align

.section".stack","w"

user_stack:.space4096〃lc0对SP进行了定义.worduser_stack+4096@sp

由此可见sp是往下增长的

分配了解压缩用的缓冲区,那么接下来就判断这个数据区是否和我们目前运行的

代码空间重叠,如果重叠则需调整

/*

*Checktoseeifwewilloverwriteourselves.

*r4=finalkerneladdress

*r5=startofthisimage

*r2=endofmallocspace(andthereforethisimage)

*Webasicallywant:

*r4>=r2->OK

*r4+imagelength<=r5->OK

7

cmpr4,r2

bhswont_overwrite

subr3,sp,r5@>compressedkernelsize

addrO,r4,r3,Isl#2@allowfor4xexpansion

cmprO,r5

blswont_overwrite

缓冲区空间的起始地址和结束地址分别存放在rl、r2中。然后判断最终内核地址,

也就是解压后内核的起始地址,是否大于malloc空间的结束地址,如果大于就

跳到wont_overwrite执行,wont_overwrite函数后面会讲到。否则,检查最终

内核地址加解压后内核大小,也就是解压后内核的结束地址,是否小于现在未解

压内核映像的起始地址。小于也会跳到wont_owerwrite执行。如两这两个条件

都不满足,则继续往下执行。

movr5,r2@decompressaftermallocspace

movrO,r5

movr3,r7

bldecompress_kernel

这里将解压后内核的起始地址设为malloc空间的结束地址。然后后把处理器id

(开始时保存在r7中)保存到r3中,调用decompress_kernel开始解压内核。

这个函数的四个参数分别存放在rO-r3中,它在

arch/arm/boot/compressed/misc.c中定义。解压的过程为先把解压代码放到

缓冲区,然后从缓冲区在拷贝到最终执行空间。

addrO,rO,#127

bicrO,rO,#127@alignthekernellength

/*

*rO=decompressedkernellength

*rl-r3=unused

*r4=kernelexecutionaddress

*r5=decompressedkernelstart

*r6=processorID

*r7=architectureID

*r8=atagspointer

*r9-rl4=corrupted

7

addrl,r5,rO@endofdecompressedkernel

adrr2,reloc_start

Idrr3,LC1

addr3,r2,r3

1:Idmiar2!,{r9-rl4}@copyrelocationcode

stmiarl!,{r9-rl4}

Idmiar2!,{r9-rl4}

stmiarl!({r9-rl4}

cmpr2,r3

biolb

这里首先计算出重定位段,也即relojstart段,然后对它的进行重定位

blcache_clean_flush

addpc,r5,rO@callrelocationcode

重定位结束后跳到解压后执行bcalLkernel,不再返回。call_kernel定义如下:

call_kernel:

blcache_clean_flush

blcache_off

movrO,#0@mustbezero

movrl,r7@restorearchitecturenumber

movr2,r8@restoreatagspointer

movpc,r4@callkernel

在运行解压后内核之前,先调用了

cachecleanflush这个函数。这个函数的定义如下:

cachecleanflush:

movr3,#16

bcall_cache_fn

其实这里又调用了call_cache_fn这个函数,注意,这里r3的值为16,上面对cache

操作已经比较详细,不再讨论。

刷新cache后,则执行movpc,r4跳入内核,开始进行下个阶段的处理。

整个代码流程如下:

机制

_lookup_processortype,_lookupmachinetype,_vetatags函数都在

kemel\head-comm.S内,这个文件实际上是被包含在head.S内

Linux之所以把搜索机器类型和CPU类型独立出来,就是为了让内核尽可能的和

bootload独立,增强移植性,把不同CPU的差异性处理减到最小。比如不同ARM

架构的CPU处理中断的,打开MMU,each操作是不同的,因此,在内核开始

执行前需要定位CPU架构,比如高通利用的ARM11,Ti用的cortex-8架构

_lookupmachinetype寻找的机器类型结构定义在arch\arm\include\asm\mach.h

查询方法比较简单,利用bootloa传进来的参数依次查询上述结构表项

这个表项是在编译阶段将#defineMACHINE_START(_type,_name)宏定义的结构

体structmachinedesc连接到

_arch_infb段,那么结构体开始和结束地址用_arch_infb_begin和

_archinfbend符号引用

3:.long.

.long_archinfobegin

.long_archinfbend

//rl=机器架构代码number,由bootload最后阶段传进来

.type_lookupmachinetype,%function

_lookupmachinetype:

adrr3,3b

Idmiar3,{r4,r5,r6}

subr3,r3,r4@此时没有开MMU,因此需要确定放置_arch_info_begin的实际物

理地址

addr5/5,r3@调整地址,找到_arch_infb的实际地址(连接地址和物理地址不•

定一样,因此需要调整)

addr6,r6,r3@

l:ldrr3,[r5,#MACHINFO_TYPE]@MACHINFO_TYPE=机器类型域的偏移量

teqr3,rl@是否和bootload传进来的参数相同?

beq2f@找到则跳出循环

addr5,r5,#SIZEOF_MACHINE_DESC@地址偏移至下个_arch_inf表项

cmpr5,r6

biolb

movr5,#0@未知的类型

2:movpc,lr//返回

_lookup_processor_type的查询的结构为structproc_info_list

机器类型确定后即开始解析(_vet_atags)内核参数列表,判断第一个参数类型

是不是ATAG_COREo

内核参数列表一般放在内核前面16K地址空间处。列表的表项由structtag构成,

每个structtag有常见的以下类型:

:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK>ATAGJNITRD等。

这些类型是宏定义,比如#defineATAG_CORE0x54410001

arch\ann\include\asm\setup.h

structtag_header{

_u32size;

_u32tag;

};

structtag{

structtag_headerhdr;

union{

structtag_corecore;//有效的内核

structtag_mem32mem;

structtag_videotextvideotext;

structtagramdiskramdisk;〃文件系统

structtaginitrdinitrd;//临时根文件系统

structtagserialnrserialnr;

structtagrevisionrevision;

structtag_videolfbvideolfb;

structtag_cmdlinecmdline;〃命令行

);

接下来就是创建页表,因为要使能MMU进行虚拟内存管理,因此必须创建映射

用的页表。页表就像一个函数发生器,保证访问虚拟地址时能从物理地址里取到

正确代码

pgtblr4@pagetableaddress

〃页表放置的位置可由下面的宏确定,即在内核所在空间的前16K处

.macropgtbl,rd

Idr\rd,=(KERNEL_RAM_PADDR-0x4000)

.endm

movrO,r4

movr3,#0

addr6,rO,#0x4000//16K的空间,r6即是页表结束处

1:strr3,[rO],#4〃清空页表项,页表项共有16K/4项

strr3,[r0],#4

strr3,[rO],#4

strr3,[rO],#4

teqrO,r6

bnelb

Idrr7,[rlO,#PROCINFO_MM_MMUFLAGS]

//从从差得的procinfblist结构PROCINFOMMMMUFLAGS处获取MMU的

信息

/*

为内核创建IM的映射空间,这里是按照1:1一致映射,即代码的基地址(高12bit)

对应相同的物理块地址。这种映射关系只是在启动阶段,在跳进start_kemel后

会被paging_init().移除。这种映射可以直接利用当前地址的高12bit作为基地址,

这种方式很巧妙,因为当前的PC(加颜色处的地址)依然在1M空间内,因此,高

12bit(段基地址)在1M空间内都是相同的。

*/

movr6,pc,Isr#20@内核映像的基地址

orrr3,r7,r6,Isl#20@基地址偏移后再加上标示符,即可得一个页表项的值

strr3,[r4,r6,Isl#2]@将此表项按照页表项的索引存入对应的表项中。比如,若〃

基地址是OxcOOO1000,那么存入页表的第OxcOO项中

//目前的映射依然是1:1的映射

〃然后移到下个段基地址处,开始映射此KERNEL_START对应的空间

//这个空间映射的物理地址与上面的相同,也就是两个虚拟地址映射到了同一个

物理地址空间

〃r0+基地址组成〃在第一级页表中索引到相关的项

addr0,r4,#(KERNEL_START&OxffDOOOOO)»18

strr3,[rO,#(KERNEL_START&OxOOfOOOOO)»18]!

Idrr6,=(KERNEL_END-1)

addrO,rO,#4〃移到下个表项

addr6,r4,r6,Isr#18〃结束的基地址

1:cmprO,r6

addr3,r3,#1«20〃下个IM物理地址空间

strlsr3,[rO],#4〃建立映射表项,开始创建所有的内核空间页表项

bls1b//

#ifdefCONFIGXIPKERNEL

/*

*Mapsomeramtocoverour.dataand.bssareas.

*/

orrr3,r7,#(KERNEL_RAM_PADDR&OxffOOOOOO)

.if(KERNELRAMPADDR&OxOOfOOOOO)

onr3,r3,#(KERNEL_RAM_PADDR&OxOOfOOOOO)

.endif

addr0,r4,#(KERNEL_RAM_VADDR&OxffOOOOOO)»18

strr3,[rO,#(KERNEL_RAM_VADDR&OxOOfOOOOO)»18]!

Idrr6,=(_end-1)

addrO,rO,#4

addr6,r4,r6,Isr#18

1:cmprO,r6

addr3,r3,#1«20

strIsr3,[rO],#4

blslb

#endif

/*

*Thenmapfirst1MBoframincaseitcontainsourbootparams.

*/

//虚拟ram地址的第一个IM空间包含了参数列表,也需要映射

addr0,r4,#PAGE_OFFSET»18

orrr6,r7,#(PHYS_OFFSET&OxffOOOOOO)

.if(PHYS_OFFSET&OxOOiDOOOO)

orrr6,r6,#(PHYS_OFFSET&OxOOfOOOOO)

.endif

strr6,[rO]

movpc,h7/页表建立完成,返回

页表创建后,具体的映射空间如下图:

执行完上述页表创建,开始执行内核跳转:

Idrrl3,_switchdata@addresstojumptoafter

@mmuhasbeenenabled

adrIr,_enablemmu@return(PIC)address

addpc,rlO,#PROCINFO_INITFUNC

_switch_data是一个数据结构,如下

.type_switchdata,%object

_switch_data:

.longmmapswitched

.long_dataloc@r4

.long_data_start@r5

.long_bss_start@r6

.long_end@r7

.longprocessor_id@r4

.long_machine_arch_type@r5

.long_atags_pointer@r6

.longcr_alignment@r7

Jonginitthreadunion+THREADSTARTSP@sp

语句“addpc,rlO,#PROCINFOJNnTUNC"通过查表调用proc-v7.s中

_v7_setup函数,该函数末尾通过将lr寄存器赋给pc,导致对_enablemmu的

词用;完成使能mmu的操作,之后将rl3寄存器值赋给pc,调甬_switdi_data

数据结构中的第一个函数_mmap_switched,

.typemmapswitched,%function

_mmap_switched:

adrr3,_switchdata+4

Idmiar3!,{r4,r5,r6,r7}

cmpr4,r5@拷贝数据段

1:cmpner5,r6

Idmefp,[r4],#4

strnefp,[r5],#4

bnelb

movfp,#0@清除BSS段

1:cmpr6,r7

strccfp,[r6],#4

bcclb

Idmiar3,{r4,r5,r6,r7,sp}//然后调整指针到processor_id域

strr9,[r4]@保存CPUID

strrl,[r5]@保存机器类型

strr2,[r6]@保存参数列表指针

bicr4,rO,#CR_A@Clear'A'bit

stmiar7,{rO,r4}@保存控制信息

bstartkemel

最终调用init\main.c文件中的startkemel函数。

这个startkemel正是kemel\init\main.c的内核起始函数

Linux启动(4)【转】

Linux2.6启动4--start_kernel篇

当内核与体系架构相关的汇编代码执行完毕,即跳入start_kernel。这个函数在

kernel/init/main.c中。由于这部分涉及linux众多数据结构的初始化,包括内核

命令行解析,内存缓冲区建立初始化,页面分配和初始化,虚拟文件系统建立,

根文件系统挂载,驱动文件挂载,二进制程序文件的执行等,限于篇幅和理解水

平,只能流程上的大致梳理,以上提及方面后期再做详细分析。为保证准确性,

参考了一部分书籍和网上技术文档,如有疑问请及时提出,共同学习探讨。

asmlinkagevoid_initstart_kernel(void)

(

char*commandjine;

externstructkernel_param_start_param[],_stop_pararnQ;

〃这里引用两个符号,是内核编译脚本定位的内核参数起始地址

smp_setup_processor_id();〃多CPU架构的初始化,目前我们的高通linux侧

是单核的,此多核不做分析

unwind_initO;〃本架构中没有用

lockdep_init();〃本架构为空

debug_objects_early_init();

cgroup_init_earlyO;

local_irq_disable();

early_boot_irqs_offO;

early_init_irqJock_cIass();

lock_kemel();〃本架构为空函数

tickjnitO;

〃时钟中断初始化函数,调用clockevents_register_notifier函数向

时钟事件链注册时钟控制函数这是个回调函

clockevents_chaintick_notifiero

数,指明了当时钟事件发生变化时应该执行的哪些操作,比如时钟的挂起操作等

boot_cpu_init();〃用于多核CPU的初始化

page_address_initO;〃用于高地址内存,我们者B用32位CPU,此函数为空

printk(KERN_NOTICE);

printk(linux_banner);

setup_arch(&command_line);

〃具体看一下这个架构初始化函数完成哪些功能

void_initsetup_arch(char**cmdline_p)

(

structtag*tags=(structtag*)&init_tags;〃定义了一个默认的内核参数列表

structmachine_desc*mdesc;

char*from=default_command_line;

setup_processor();〃汇编的CPU初始化部分已讲过,不再讨论

mdesc=setup_machine(machine_arch_type);

machine_name=mdesc->name;

if(mdesc->soft_reboot)

reboot_setup("s");

if(_atags_pointer)

tags=phys_to_virt(_atags_pointer);

elseif(mdesc->boot_params)

tags=phys_to_virt(mdesc->boot_params);

〃由于MMU单元已打开,此处需要而boot_params是物理地址,需要转换成

虚拟地址才能访问,因为此时CPU访问的都是虚拟地址

/*

*Ifwehavetheoldstyleparameters,convertthemto

*ataglist.

7

〃内核参数列表第一项必须是ATAGJ2ORE类型

if(tags->hdr.tag!=ATAGJZORE)〃如果不是,则需要转换成新的内核参数类

型,新的内核参数类型用下面structtag结构表示

convert_to_tag」ist(tags);〃此函数完成新旧参数结构转换

structtag{

structtag_headerhdr;

union{

structtag_corecore;

structtag_mem32mem;

structtag_videotextvideotext;

structtag_ramdiskramdisk;

structtagjnitrdinitrd;

structtag_serialnrserialnr;

structtag_revisionrevision;

structtag_videolfbvideolfb;

structtag_cmdlinecmdline;

}u;

};

〃旧的内核参数列表用下面结构表示

structparam_struct{

union{

struct{

unsignedlongpage_size;/*0*/

unsignedlongnr_pages;/*4*/

unsignedlongramdisk_size;/*8*/

unsignedlongflags;/*12*/

oooooooooooo〃车父长,省略

)

if(tags->hdr.tag!=ATAG_CORE)〃如果没有内核参数

tags=(structtag*)&init_tags;〃则选用默认的内核参数

if(mdesc->fixup)

mdesc->fixup(mdesc,tags,&from,&meminfo);〃用内核参数列表填充

meminfo

if(tags->hdr.tag==ATAG_CORE){

if(meminfo.nr_banks!=0)

squash_mem_tags(tags);

save_atags(tags);

parse_tags(tags);〃解析内核参数列表,然后调用内核参数列表的处理函数对这

些参数进行处理。比如,如果列表为命令行,则最终会用parse_tag_cmdlin函

数进行解析,这个函数用_tagtable编译连接到了内核里

_tagtable(ATAG_CMDLINE,parse_tag_cmdline);

)

〃下面是记录内核代码的起始,结束虚拟地址

init_mm.start_code=(unsignedlong)&_text;

init_mm.end_code=(unsignedlong)&_etext;

init_mm.end_data=(unsignedlong)&_edata;

init_mm.brk=(unsignedlong)&_end;

〃下面是对命令行的处理,刚才在参数列表处理parse_tag_cmdline函数已把命

令行拷贝到了from空间

memcpy(boot_command_line,from,COMMAND_LINE_SIZE);

boot_command_line[COMMAND_LINE_SIZE-l]='\0';

parse_cmdline(cmdline_p,from);〃解析出命令行,命令行解析出以后,同样会

调用相关处理函数进行处理。系统用_early_param宏在编译阶段把处理函数编

译进内核。

paging_init(&meminfo,mdesc);

〃这个函数完成页表初始化,具体的方法为建立线性地址划分后每个地址空间的

标志;清除在boot阶段建立的内核映射空间,也即把页表项全部清零;调用

bootmemjnit,禁止无效的内存节点,由于我们的物理内存都是连续的空间,

因此,内存节点为1个。接下来判断INITRD映像是否存在,若存在则检查其所在

的地址是否在一个有效的地址内,然后返回此内存节点号。

先看两个数据结构。

structmeminfo表示内存的划分情况。Linux的内存划分为bank。每个bank

structmembank表示,start表示起始地址,这里是物理地址,size表示大小,

node表示此bank所在的节点号,对于只有一个节点的内存,所有bank节点都

相等

structmembank{

unsignedlongstart;

unsignedlongsize;

intnode;

);

structmeminfo{

intnr_banks;

structmembankbank[NR_BANKS];

};

〃在pagejnit函数中比较重要的是bootmemjnit函数,此函数在完成原来映

射页表的清除后,最终调用bootmem_init_node如下:

bootmemjnit_node(intnode,intinitrd_node,structmeminfo*mi)

(

unsignedlongzone_size[MAX_NR_ZONES],zhole_size[MAX_NR_ZONES];

unsignedlongstart_pfn,end_pfn,boot_pfn;

unsignedintboot_pages;

pg_data_t*pgdat;//每个节点用pg_data_t描述,这个结构用在非一致性内存

中,我们的内存只有一个,地址是连续的

inti;

start_pfn=-1UL;

end_pfn=0;

for_each_nodebank(i,mi,node){

structmembank*bank=&mi->bank[i];

unsignedlongstart,end;

start=bank->start>>PAGE_SHIFT;〃计算出页表号,实际也表示第几个物理

页号

end=(bank->start+bank->size)>>PAGE_SHIFT;

if(start_pfn>start)

start_pfn=start;

if(end_pfn<end)

end_pfn=end;

map_memory_bank(bank);〃将每个节点的每个bank重新映射,比如重新映射

内核空间

)

if(end_pfn==0)

returnend_pfn;

〃一个字节代表8个页,因此找到一个

〃可放置这些所有自己的页面即可。用一个bit位表示一个页是否已占用,那么一

个字节为8个页,比如4096个页需要4096/8=512字节,容纳这个位图需要一个

boot_pages=bootmem_bootmap_pages(end_pfn-start_pfn);

boot_pfn=find_bootmap_pfn(node,mi,boot_pages);//在node节点内存的

bank中找到一个可以放置位图的页面的页面序列,然后返回这个页面序列的首个

页面号

node_set_online(node);〃设置本节点有效

pgdat=NODE_DATA(node);〃获取节点描述符pgdat

init_bootmem_node(pgdat,boot_pfn,start_pfn,end_pfn);〃设置本节点内所

有映射页的位图,即每个字节全部置为Oxff,表示已经映射使用。然后填充pgda

结构

for_each_nodebank(i,mi,node)

free_bootmem_node(pgdat,mi->bank[i].start,mi->bank。].size);〃设置每

个映射的页面空闲,实际是对位图的操作,对每个bit清零

reserve_bootmem_node(pgdat,boot_pfn<<PAGE_SHIFT,

boot_pages<<PAGE_SHIFT,BOOTMEM_DEFAULT);

〃标示位图所占的页面被占用

if(node==0)

reserve_node_zero(pgdat);

#ifdefCONFIG_BLK_DEV」NITRD

/*

*Iftheinitrdisinthisnode,reserveitsmemory.

*/

if(node==initrd_node){

intres=reserve_bootmem_node(pgdat,phys_initrd_start,

phys_initrd_size,BOOTMEM_EXCLUSIVE);

//INITRD映像占用的空间需要标示占用,INITRD是虚拟根文件系统,此时还未

加载,因此挂载之前这个物理空间不能再被分配使用

if(res==0){

initrd_start=_phys_to_virt(phys_initrd_start);

initrd_end=initrd_start+phys_initrd_size;

}else{

printk(KERN_ERR

"INITRD:0x%08lx+0x%08lxoverlapsin-use"

"memoryregion-disablinginitrd\n",

phys_initrd_start,phys_initrd_size);

)

#endif

*initialisethezoneswithinthisnode.

7

memset(zone_size,0,sizeof(zone_size));

memset(zhole_size,0,sizeof(zhole_size));

/*

*Thesizeofthisnodehasalreadybeendetermined.Ifweneed

*todoanythingfancywiththeallocationofthismemorytothe

*zones,nowisthetimetodoit.

*/

zone_size[0]=end_pfn-start_pfn;

zhole_size[0]=zone_size[0];

for_each_nodebank(i,mi,node)

zhole_size[0]-=mi->bank[i].size>>PAGE_SHIFT;

〃计算共有多少页空洞,注意,有些bank的起始结束地址并不是刚好4K对齐的,

因此,可能存在某些空白页框。用节点总的物理页框减去每个bank页框,就得

到页空洞

〃这个函数里面主要完成zone区的初始化,linux内存管理将内存节点又分为

ZONE区管理,比如ZONE_DMA和ZONE_NORMAL等,因此需要初始化。由

于平台只针对一致性内存管理,即物理内存空间只包含DDR部分,此处很多函

数是空的,再次略过

arch_adjust_zones(node,zone_size,zhole_size);

free_area_init_node(node,zone_size,start_pfn,zhole_size);

returnend_pfn;

)

〃在page」nit的最后完成devicemaps_init初始化,比如中断向量的映射。映

射的大致过程是,申请一个物理框,然后调用creat_map将此物理页框映射到

OxffffOOOO.最后再调用structmachine_desc的map_io完成10设备的映射

〃在完成内存页映射后即进入request_standard_resources,这个函数比较简

单,主要完成从iomem,esource空间申请所需的内存资源,比如内核代码和视

频所需的资源等

request_standard_resources(&meminfo,mdesc);

#ifdefCONFIGSMP

smp_init_cpus();

#endif

cpu_init();//止匕函数为空

init_arch_irq=mdesc->init_irq;〃初始化与硬件体系相关的指针

system_timer=mdesc->timer;

init_machine=mdesc->init_machine;

#ifdefCONFIG_VT

#ifdefined(CONFIG_VGA_CONSOLE)

conswitchp=&vga_con;

#elifdefined(CONFIG_DUMMY_CONSOLE)

conswitchp=&dummy_con;

#endif

#endif

early_trap_init();〃重定位中断向量,将中断向量代码拷贝到中断向量页,并把信

号处理代码指令拷贝到向量页中

)

mm_init_owner(&init_mm,&init_task);//空函数

setup_command」ine(command_line);〃保存命令行,以备后用,此保存空间

需申请

〃这个函数调用完了,就开始执行下面初始化函数

unwind_setupO;〃空函数

setup_per_cpu_areas();〃设置每个CPU信息,单核CPU为空函数

setup_nr_cpu_ids();〃空函数

smp_prepare_boot_cpu();〃设置启动的CPU为在线状态.在多CPU架构下

〃第一个启动的cpu启动到一定阶段后,开始启动其它的cpu,它会为每个后来

启动的cpu创建一个0号进程,而这些0号进程的堆栈的thread_info结构中的

cpu成员变量则依次被分配出来(利用alloc_cpu_id()函数)并设置好,这样当

这些cpu开始运行的时候就有了自己的逻辑cpu号。

sched_init();〃初始化调度器,对调度机制进行初始化,对每个CPU的运行队列

preempt_disable();〃启动阶段系统比较脆弱,禁止进程调度

build_all_zonelists();//建立内存区域链表

page_alloc_init();〃内存页初始化,此处无执行

printk(KERN_NOTICE"Kernelcommandline:%s\n",boot_command_line);

parse_early_param();

parse_args("Bootingkernel",static_command_line,_start_param,

_stop_param-_start_param

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论