linux内核启动 Android系统启动过程详解.docx
- 文档编号:27083349
- 上传时间:2023-06-26
- 格式:DOCX
- 页数:16
- 大小:20.51KB
linux内核启动 Android系统启动过程详解.docx
《linux内核启动 Android系统启动过程详解.docx》由会员分享,可在线阅读,更多相关《linux内核启动 Android系统启动过程详解.docx(16页珍藏版)》请在冰豆网上搜索。
linux内核启动Android系统启动过程详解
linux内核启动+Android系统启动过程详解
第一部分:
汇编部分
Linux启动之linux-rk3288-tchip/kernel/arch/arm/boot/compressed/head.S分析这段代码是linuxboot后执行的第一个程序,完成的主要工作是解压内核,然后跳转到相关执行地址。
这部分代码在做驱动开发时不需要改动,但分析其执行流程对是理解android的第一步
开头有一段宏定义这是gnuarm汇编的宏定义。
关于GUN的汇编和其他编译器,在指令语法上有很大差别,具体可查询相关GUN汇编语法了解
另外此段代码必须不能包括重定位部分。
因为这时一开始必须要立即运行的。
所谓重定位,比如当编译时某个文件用到外部符号是用动态链接库的方式,那么该文件生成的目标文件将包含重定位信息,在加载时需要重定位该符号,否则执行时将因找不到地址而出错
#ifdefDEBUG//开始是调试用,主要是一些打印输出函数,不用关心
#ifdefined(CONFIG_DEBUG_ICEDCC)
……具体代码略
#endif
宏定义结束之后定义了一个段,
.section".start",#alloc,#execinstr
这个段的段名是.start,#alloc表示Sectioncontainsallocateddata,#execinstr表示Section
containsexecutableinstructions.
生成最终映像时,这段代码会放在最开头
.align
start:
.typestart,#function/*.type指定start这个符号是函数类型*/
.rept8
movr0,r0//将此命令重复8次,相当于nop,这里是为中断向量保存空间
.endr
b1f
.word0x016f2818@Magicnumberstohelptheloader
.wordstart@absoluteload/runzImage
//此处保存了内核加载和运行的地址,实质上也是本函数的运行地址
address
.word_edata@内核结束地址
//注意这些地址在顶层vmlixu.lds(具体在/kernel文件夹里)里进行了定义,是链接的地址,加载内核后可能会进行重定位
1:
movr7,r1@保存architectureID,这里是从bootload传递进来的
movr8,r2@保存参数列表atags指针
r1和r2中分别存放着由bootloader传递过来的architecture
ID和指向标记列表的指针。
这里将这两个参数先保存。
#ifndef__ARM_ARCH_2__
/*
*BootingfromAngel-needtoenterSVCmodeanddisable
*FIQs/IRQs(numericdefinitionsfromangelarm.hsource).
*Weonlydothisifwewereinusermodeonentry.
*/
读取cpsr并判断是否处理器处于supervisor模式——从bootload进入kernel,系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两条指令。
之后是再次确认中断关闭,并完成cpsr写入
Angel是ARM的调试协议,一般用的是MULTI-ICE。
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
adrr0,LC0//当前运行时LC0符号所在地址位置,注意,这里用的是adr指令,这个指令会根据目前PC的值,计算符号相对于PC的位置,是个相对地址。
之所以这样做,是因为下面指令用到了绝对地址加载ldmia指令,必须要调整确定目前LC0的真实位置,这个位置也就是用adr来计算
ldmiar0,{r1,r2,r3,r4,r5,r6,ip,sp}
subsr0,r0,r1@//这里获得当前LCD0实际地址与链接地址差值
//r1即是LC0的连接地址,也即由vmlinux.lds定位的地址
//差值存入r0中。
beqnot_relocated//如果相等不需要重定位,因为已经在正确的//地址运行了。
重定位的原因是,MMU单元未使能,不能进行地址映射,必须要手工重定位。
下面举个简单例子说明:
如果连接地址是0xc0000000,那么LC0的连接地址假如连接为0xc0000010,那么LC0相对于连接起始地址的差为0x10,当此段代码是从0xc0000000运行的话,那么执行adr
r0,LC0的值实际上按下面公式计算:
R0=PC+0x10,由于PC=连接处的值,可知,此时是在ram中运行,同理如果是在不是在连接处运行,则假设是在0x00000000处运行,则R0=0x00000000+0x10,可知,此时不是在ram的连接处运行。
上面这几行代码用于判断代码是否已经重定位到内存中,LC0这个符号在head.S中定义如下,实质上相当于c语言的全局数据结构,结构的每个域存储的是一个指针。
指针本身的值代表不同的代码段,已经在顶层连接脚本vmlinux.lds里进行了赋值,比如_start是内核开始的地址
.typeLC0,#object
LC0:
.wordLC0@r1//这个要加载到r1中的LC0是链接时LC0的地址
.word__bss_start@r2
.word_end@r3
.wordzreladdr@r4
.word_start@r5
.word_got_start@r6
.word_got_end@ip
.worduser_stack+4096@sp
通过当前运行时LC0的地址与链接器所链接的地址进行比较判断。
若相等则是运行在链接的地址上。
如果不是运行在链接的地址上,则下面的代码必须修改相关地址,进行重新运行
/*
*r5-zImagebaseaddress
*r6-GOTstart
*ip-GOTend
*/
//修正实际运行的位置,否则跳转指令就找不到相关代码
addr5,r5,r0//修改内核映像基地址
addr6,r6,r0
addip,ip,r0//修改got表的起始和结束位置
#ifndefCONFIG_ZBOOT_ROM
/*若没有定义CONFIG_ZBOOT_ROM,此时运行的是完全位置无关代码
位置无关代码,也就是不能有绝对地址寻址。
所以为了保持相对地址正确,
需要将bss段以及堆栈的地址都进行调整
*r2-BSSstart
*r3-BSSend
*sp-stackpointer
*/
addr2,r2,r0
addr3,r3,r0
addsp,sp,r0
//全局符号表的地址也需要更改,否则,对全局变量引用将会出错
1:
ldrr1,[r6,#0]@relocateentriesintheGOT
addr1,r1,r0@table.Thisfixesupthe
strr1,[r6],#4@Creferences.
cmpr6,ip
blo1b
#else//若定义了CONFIG_ZBOOT_ROM,只对got表中在bss段以外的符号进行重定位
1:
ldrr1,[r6,#0]@relocateentriesintheGOT
cmpr1,r2@entry<bss_start||
cmphsr3,r1@_end<entry
addlor1,r1,r0@table.Thisfixesupthe
strr1,[r6],#4@Creferences.
cmpr6,ip
blo1b
#endif
如果运行当前运行地址和链接地址相等,则不需进行重定位。
直接清除bss段
not_relocated:
movr0,#0
1:
strr0,[r2],#4@clearbss
strr0,[r2],#4
strr0,[r2],#4
strr0,[r2],#4
cmpr2,r3
blo1b
之后跳转到cache_on处
blcache_on
cache_on定义
.align5
cache_on:
movr3,#8@cache_onfunction
bcall_cache_fn
把r3的值设为8。
这是一个偏移量,也就是索引proc_types中的操作函数。
然后跳转到call_cache_fn。
这个函数的定义如下:
call_cache_fn:
adrr12,proc_types//把proc_types的相对地址加载到r12中
#ifdefCONFIG_CPU_CP15
mrcp15,0,r6,c0,c0@getprocessorID
#else
ldrr6,=CONFIG_PROCESSOR_ID
#endif
1:
ldrr1,[r12,#0]@getvalue
ldrr2,[r12,#4]@getmask
eorr1,r1,r6@(real^match)
tstr1,r2@是否和CPUID匹配?
addeqpc,r12,r3@用刚才的偏移量,查找//到cache操作函数,找到后就执行相关操作,比如执行b__armv7_mmu_cache_on
//
addr12,r12,#4*5//如果不相等,则偏移到下个proc_types结构处
b1b
addeqpc,r12,r3@callcachefunction
proc_type的定义如下,实质上还是一张数据结构表
.typeproc_types,#object
proc_types:
.word0x41560600@ARM6/610
.word0xffffffe0
b__arm6_mmu_cache_off@works,butslow
b__arm6_mmu_cache_off
movpc,lr
@b__arm6_mmu_cache_on@untested
@b__arm6_mmu_cache_off
@b__armv3_mmu_cache_flush
.word0x00000000@oldARMID
.word0x0000f000
movpc,lr
movpc,lr
movpc,lr
.word0x41007000@ARM7/710
.word0xfff8fe00
b__arm7_mmu_cache_off
b__arm7_mmu_cache_off
movpc,lr
.word0x41807200@ARM720T(writethrough)
.word0xffffff00
b__armv4_mmu_cache_on
b__armv4_mmu_cache_off
movpc,lr
.word0x41007400@ARM74x
.word0xff00ff00
b__armv3_mpu_cache_on
b__armv3_mpu_cache_off
b__armv3_mpu_cache_flush
.word0x41009400@ARM94x
.word0xff00ff00
b__armv4_mpu_cache_on
b__armv4_mpu_cache_off
b__armv4_mpu_cache_flush
.word0x00007000@ARM7IDs
.word0x0000f000
movpc,lr
movpc,lr
movpc,lr
@EverythingfromhereonwillbethenewIDsystem.
.word0x4401a100@sa110/sa1100
.word0xffffffe0
b__armv4_mmu_cache_on
b__armv4_mmu_cache_off
b__armv4_mmu_cache_flush
.word0x6901b110@sa1110
.word0xfffffff0
b__armv4_mmu_cache_on
b__armv4_mmu_cache_off
b__armv4_mmu_cache_flush
@ThesematchonthearchitectureID
.word0x00020000@
.word0x000f0000//
b__armv4_mmu_cache_on
b__armv4_mmu_cache_on//指令的地址
b__armv4_mmu_cache_off
b__armv4_mmu_cache_flush
.word0x00050000@ARMv5TE
.word0x000f0000
b__armv4_mmu_cache_on
b__armv4_mmu_cache_off
b__armv4_mmu_cache_flush
.word0x00060000@ARMv5TEJ
.word0x000f0000
b__armv4_mmu_cache_on
b__armv4_mmu_cache_off
b__armv4_mmu_cache_flush
.word0x0007b000@ARMv6
.word0x0007f000
b__armv4_mmu_cache_on
b__armv4_mmu_cache_off
b__armv6_mmu_cache_flush
.word0@unrecognisedtype
.word0
movpc,lr
movpc,lr
movpc,lr
.sizeproc_types,.-proc_types
找到执行的cache函数后,就用上面的addeqpc,r12,r3直接跳转,例如执行下面这个处理器结构的cache函数
__armv7_mmu_cache_on:
movr12,lr//注意,这里需要手工保存返回地址!
!
这样做的原因是下面的bl指令会覆盖掉原来的lr,为保证程序正确返回,需要保存原来lr的值
bl__setup_mmu
movr0,#0
mcrp15,0,r0,c7,c10,4@drainwritebuffer
mcrp15,0,r0,c8,c7,0@flushI,DTLBs
mrcp15,0,r0,c1,c0,0@readcontrolreg
orrr0,r0,#0x5000@I-cacheenable,RRcachereplacement
orrr0,r0,#0x0030
bl__common_mmu_cache_on
movr0,#0
mcrp15,0,r0,c8,c7,0@flushI,DTLBs
movpc,r12//返回到cache_on
这个函数首先执行__setup_mmu,然后清空writebuffer、I/Dcache、TLB.接着打开i-cache,设置为Round-robin
replacement。
调用__common_mmu_cache_on,打开mmu和d-cache.把页表基地址和域访问控制写入协处理器寄存器c2、c3.
__common_mmu_cache_on函数数定义如下:
__common_mmu_cache_on:
#ifndefDEBUG
orrr0,r0,#0x000d@Writebuffer,mmu
#endif
movr1,#-1//-1的补码是ffffffff,
mcrp15,0,r3,c2,c0,0@把页表地址存于协处理器寄存器中
mcrp15,0,r1,c3,c0,0@设置domainaccesscontrol寄存器
b1f
.align5@cachelinealigned
1:
mcrp15,0,r0,c1,c0,0@loadcontrolregister
mrcp15,0,r0,c1,c0,0@andreaditbackto
subpc,lr,r0,lsr#32@properlyflushpipeline
重点来看一下__setup_mmu这个函数,定义如下:
__setup_mmu:
subr3,r4,#16384@Pagedirectorysize
bicr3,r3,#0xff@Alignthepointer
bicr3,r3,#0x3f00
这里r4中存放着内核执行地址,将16K的一级页表放在这个内核执行地址下面的16K空间里,上面通过
subr3,r4,#16384获得16K空间后,又将页表的起始地址进行16K对齐放在r3中。
即ttb的低14位清零。
//初始化页表,并在RAM空间里打开cacheable和bufferable位
movr0,r3
movr9,r0,lsr#18
movr9,r9,lsl#18@startofRAM
addr10,r9,#0x10000000@areasonableRAMsize
上面这几行把一级页表的起始地址保存在r0中,并通过r0获得一个ram起始地址(每个页面大小为1M)然后映射256M
ram空间,并把对应的描述符的C和B位均置”1”
movr1,#0x12//一级描述符的bit[1:
0]为10,表示这是一个section描述符。
也即分页方式为段式分页
orrr1,r1,#3<<10//一级描述符的accesspermissionbitsbit[11:
10]为11.即
addr2,r3,#16384//一级描述符表的结束地址存放在r2中。
1:
cmpr1,r9@ifvirt>startofRAM
orrhsr1,r1,#0x0c@setcacheable,bufferable
cmpr1,r10@ifvirt>endofRAM
bichsr1,r1,#0x0c@clearcacheable,bufferable
strr1,[r0],#4@1:
1mapping
addr1,r1,#1048576//下个1M物理空间,每个页框1M。
teqr0,r2
bne1b
因为打开cache前必须打开mmu,所以这里先对页表进行初始化,然后打开mmu和cache。
上面这段就是对一级描述符表(页表)的初始化,首先比较这个描述符所描述的地址是否在那个256M的空间中,如果在则这个描述符对应的内存区域是cacheable,bufferable。
如果不在则noncacheable,
nonbufferable.然后将描述符写入一个一级描述符表的入口,并将一级描述符表入口地址加4,而指向下一个1Msection的基地址。
如果页表
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux内核启动 Android系统启动过程详解 linux 内核 启动 Android 系统启动 过程 详解