您的位置:首页 > 编程语言 > Go语言

随笔之GoldFish Kernel启动过程中arm汇编分析

2013-07-16 15:30 459 查看
转自:
http://my.oschina.net/innost/blog/93302

随笔之GoldFishKernel启动过程中arm汇编分析

一分析

电子版下载http://download.csdn.net/detail/innost/4834459

本节介绍Kernel启动。此时Piggy已经将vimlinux解压,BL将执行权限传给了Kernel。

代码在arch/arm/kernel/head.S中。相关代码如下:

//将采用C/C++注释语句

01
/*
02
03
.section是GNUASM的语法。格式如下:
04
05
.sectionname[,"flags"[,@type]]其中,name是必须的,flags是可选。
06
07
"ax"表示:a为sectionisallocatable,x为executable。
08
09
*/
10
11
.section

".text.head"
,
"ax"
12
//这个ENTRY(stext)有相当的含义。在kernel/vmlinux.ld.S中,也定义了一个ENTRY。在ld
13
14
//语法中,ENTRY是一个command,用来定义入口点。所以,这里就是kernel执行的入口点函数。
15
16
ENTRY(stext)
17
18
/*
19
20
MSR:是ARM汇编指令,用来将数据copy到statusregister寄存器中。cpsr_c表示要操作
21
22
CPSR寄存器的Control标志。
23
24
*/
25
26
27
28
msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE@ensuresvcmode
29
30
@andirqsdisabled

1.1MSR设置I/F和CPUMode

CPSR全称是CurrentProcessStatusRegister,用来表示当前CPU的状态,也可用于控制。相关控制位如图1所示:





图1CPSR控制位

由图1可知:

qN/Z/C/V控制位用来表示负/零/进位/溢出,属于UserFlags,即可在UserMode下操作。A
qI/F表示Interrupt和FastInterrupt使能位。
qMode用来控制CPU当前的模式。ARMCPU一共有7种模式。

根据上面的代码,首先将禁止I/F中断,并进入Supervisor模式,也就是OS运行的模式。图2为ARMCPU支持的CPU模式。





图2ARMCPU支持的运行模式

另外,MSR指令操作的格式如下:





图3MSR指令格式

其中最重要的是fields,目前支持:

qc:设置controlbit。对应位为16。
qx:设置extensionbit。对应位为17。
qs:设置statusbit。对应位为18。
qf:设置flagsfield。对应位为19。





图4MSR二进制格式

直接看上面的解释,还不是很清楚,因为设置的是MSR指令本身的内容,具体对应到CPSR呢,则可通过下面的伪语句得到:





图5MSR设置说明

从代码可知:

1
msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE
//上面代码将设置CPSR的0到第7位,刚好是控制I/F和设置CPU模式的。

1.2ARMCP15协处理器控制

设置好CPU模式后,下面的工作就是获取CPU的信息。在ARM中,协处理(coprocessor)15中用于管理CPU信息和MMU相关的工作。CP15也是ARM中最重要的处理器,以后会经常碰到。

先看下面这条语句:

1
mrcp15,0,r9,c0,c0@getprocessorid
MRC是ARM指令,用来从协处理对应的寄存器读取信息到CPU的寄存器,对应写协处理寄存器的指令是MCR。二者的语法格式(注意,是操作CP15的时候)如图6所示:





图6MRC操作CP15的格式说明

qRd:本指令用得是R9,也就是协处理的信息会保存到R9中。
qCRn:MRC中协处理器的主要寄存器。此处用得是C0。标准写法是C0,C1一直到C15。
qCRm:附属信息。如果没有附属信息,则使用C0。
qopcode2,类似附属信息。根据CRn来决定是否需要。如果不指定,则使用0。

CP15有很重要的作用,可通过操作CP15的寄存器来控制它。如图7所示:





图7CP15各个寄存器的作用

先来看此处操作的C0寄存器。





opcode2在指令中默认是0,所以将取出MainIDregister的信息。

得到的结果将怎么使用呢?来看下一句指令:

1
bl__lookup_processor_type@r5=procinfor9=cpuid
BL是ARM中的跳转指令,相当于调用函数吧。__lookup_processor_type用来得到CPU信息。注意,这个函数调用的参数是R9,R9的值是从CP15C0寄存器读取出来的,而是是MainID。下面看看此函数如何处理R9。

1.3__lookup_procesoor_type分析

该函数在head-Common.S中定义。下面逐行分析它,这里会碰到几个重要的指令及其用法。

01
__lookup_processor_type:
02
03
//adr是一条伪指令,其作用是将3f标签的地址赋给R3。这个伪指令其实是可拆分成多条指令
04
05
//由于后面的3f是相对当前PC位置而言,所以R3实际上存储的是3f的物理地址。
06
07
adrr3,3f
//f是forward之意。标志3在此代码之后声明
08
09
/*
10
11
ldm是loadmultipleregister的意思,它的作用是将[r3]对应的内存内容存储到r5,r6,
12
13
r7寄存器中。DA是DecreaseAfter的意思。ARM汇编在这里有4种模式,DA,IA,DB,IB等
14
15
此处的ldmda,将把3F所在的内容依序传递给R7,R6,R5。每传递一次,R3递减4个字节。
16
17
*/
18
19
ldmdar3,{r5-r7}
20
21
上面语句执行完后:
22
23
qR5=__proc_info_begin,这个值是虚地址。
24
25
qR6=__proc_info_end。
26
27
qR7=.。
以上几个值都是虚地址。__proc_info_begin/end是ld在链接时候指定的信息。





图8arc/arm/kernel/vmlinx.lds.S文件

从中可以看出,__proc_info_begin/end包含了代码中定义在.proc.info.init段的内容。如图9所示。





图9proc-V7.s定义的proc.info.init的内容

为什么是proc-v7.S文件呢,因为goldfish编译的就是这个文件。从图9可以看出,其实也就是定义了一个数据结构罢了。

接着来看代码

1
//r3指向3f的物理地址,r7指向虚拟地址,而现在只能访问物理地址,所以需要找到一个offset
2
3
subr3,r3,r7@getoffsetbetweenvirt&phys
4
5
addr5,r5,r3@convertvirtaddressesto
6
7
addr6,r6,r3@physicaladdressspace
经过上面的换算,r5,r6现在都指向__proc_info_begin/end的物理地址了。

01
//ldmia将[r5]的内存信息存储到r3,r4中,每完成一次传输,r5自动加4.
02
03
1:ldmiar5,{r3,r4}@value,mask
04
05
//下面将测试R9和mask之后的值是否是我们想要的r3的值。根据图9。应该是0x000f0000。
06
07
//在MainIDregister中,这表明[16-19]位是都是1.
08
09
andr4,r4,r9@maskwantedbits
10
11
teqr3,r4
12
13
beq2f
//如果是我们想要的数据,则跳转到2f
14
15
//否则跳过一个PROC_INFO_SIZE,继续找,一般只有一个PROC_INFO结构体。
16
17
addr5,r5,#PROC_INFO_SZ@
sizeof
(proc_info_list)
18
19
cmpr5,r6
20
21
blo1b
22
23
//如果没找到,则设置R5寄存器为0
24
25
movr5,#0@unknownprocessor
26
27
2:movpc,lr
//从函数返回
28
29
ENDPROC(__lookup_processor_type)
30
31
32
33
/*
34
35
*提供一个C接口的lookup_process_type函数
36
37
*/
38
39
ENTRY(lookup_processor_type)
40
41
stmfdsp!,{r4-r7,r9,lr}
42
43
movr9,r0
44
45
bl__lookup_processor_type
46
47
movr0,r5
48
49
ldmfdsp!,{r4-r7,r9,pc}
50
51
ENDPROC(lookup_processor_type)
52
53
54
55
.
long

__proc_info_begin
56
57
.
long

__proc_info_end
58
59
3:.
long

.
60
61
.
long

__arch_info_begin
62
63
.
long

__arch_info_end
lookup_process_type其实比较简单,这里就不再多说。但图9的内容以后还要回过头来继续介绍。那里将初始化CPUMMU相关的内容。

1
//如果r5为空,则表示CPU信息获取是否,调用__error_p,退出整个启动
2
3
movsr10,r5@invalidprocessor(r5=0)?
4
5
beq__error_p@yes,error

'p'
否则,将调用__lookup_machine_type获取机器信息。

1.4__lookup_machine_type分析

该函数也是在head-comm.S中定义的。

01
__lookup_machine_type:
02
03
adrr3,3b
//b是backward的意思。标志3在此代码之前声明。
04
05
//r4,r5,r6分别指向label3,__arch_info_begin和__arch_info_end
06
07
ldmiar3,{r4,r5,r6}
08
09
subr3,r3,r4
10
11
addr5,r5,r3
12
13
addr6,r6,r3
14
15
//以上将得到__arch_info_begin/end的物理地址
16
17
18
19
1:ldrr3,[r5,#MACHINFO_TYPE]
20
21
//比较r1和MACHINFO_TYPE是不是一致。注意,r1的值是BL传递给它的
22
23
teqr3,r1@matchesloadernumber?
24
25
beq2f@found
26
27
addr5,r5,#SIZEOF_MACHINE_DESC@nextmachine_desc
28
29
cmpr5,r6
30
31
blo1b
32
33
movr5,#0@unknownmachine
34
35
2:movpc,lr
36
37
ENDPROC(__lookup_machine_type)
这里涉及到另一个关键数据结构,也就是定义在.arch.info.init段中的。如图10所示:





图10.arch.info.init段

从图10可知,这个段其实对应了一个数据结构,即machine_desc.在我们的goldfish平台中,它是这么定义的:

[arch/arm/mach-goldfish/board-goldfish.c]

01
//还需要加上:
02
03
nr=MACH_TYPE_GOLDFISH
04
05
name=

"Goldfish"
06
07
MACHINE_START(GOLDFISH,
"Goldfish"
)
08
09
.phys_io=IO_START,
10
11
.io_pg_offst=((IO_BASE)>>18)&0xfffc,
12
13
.boot_params=0x00000100,
14
15
.map_io=goldfish_map_io,
16
17
.init_irq=goldfish_init_irq,
18
19
.init_machine=goldfish_init,
20
21
.timer=&goldfish_timer,
22
23
MACHINE_END
完整的machine_desc定义如图11所示:





图11machine_desc定义

在Goldfish中,nr为1441。详情可参考arch/arm/tools/machine-types.h。

另外,在BootLoader调用kernel之前,传递参数情况如图12所示:





图12arch/arm/boot/head.S调用kernel前传递参数

从图12可知:

qr1保存的是machinenr。

这部分代码属于BootLoader,相当复杂。以后再细说。

假设__lookup_machine_type一切正常

1
bl__lookup_machine_type@r5=machinfo
2
3
movsr8,r5@invalidmachine(r5=0)?
4
5
beq__error_a@yes,error

'a'

1.5__vet_atags分析

接下来的任务就是Kernel校验BL传递的启动参数了。这部分内容和BootLoader有较大关系。

01
bl__vet_atags
02
03
此处的核心概念就是ATAG_CORE/END之类的,由BootLoader往Kernel传递参数,主要是tag结构体
04
05
在arch/arm/include/asm/setup.h中。BL传递的是
struct

tag的链表,该链表以ATAG_CORE开头,以ATAG_NONE结尾。
06
07
#defineATAG_CORE0x54410001
08
09
#defineATAG_NONE0x00000000
10
11
struct

tag_header{
12
13
__u32size;
14
15
__u32tag;
16
17
};
18
19
struct

tag{
20
21
struct

tag_headerhdr;
//首先是一个头,根据头部的tag来判断下面的union是哪个
22
23
union

{
24
25
struct

tag_corecore;
26
27
struct

tag_mem32mem;
28
29
struct

tag_videotextvideotext;
30
31
struct

tag_ramdiskramdisk;
32
33
struct

tag_initrdinitrd;
34
35
struct

tag_serialnrserialnr;
36
37
struct

tag_revisionrevision;
38
39
struct

tag_videolfbvideolfb;
40
41
struct

tag_cmdlinecmdline;
42
43
struct

tag_acornacorn;
44
45
struct

tag_memclkmemclk;
46
47
}u;
48
49
};
你可以根据上面的信息自行分析__vet_atags函数。

1.6__create_page_tables分析

下面的任务就是调用__create_page_tables创建pagetable。

1
bl__create_page_tables
//调用__create_page_tables函数
此函数就在head.S中定义,代码如下:

01
__create_page_tables:
02
03
/*
04
05
pgtbl是head.S中定义的一个宏,见下面的分析
06
07
*/
08
09
pgtblr4
10
11
pgtbl定义了一个宏,相关代码如下:
12
13
//TEXT_OFFSET是kernel镜像在内存中的偏移量。一般定义为0X8000,即32KB处
14
15
//PHYS_OFFSET:是内核镜像在内存中的起始物理地址。上面二者之和就是内核镜像在机器上的
16
17
//物理地址。Goldfish平台中,PHYS_OFFSET为0。
18
19
//PAGE_OFFSET是Kernel镜像在虚拟内存的起始地址,一般是3G处
20
21
#defineKERNEL_RAM_VADDR(PAGE_OFFSET+TEXT_OFFSET)
22
23
#defineKERNEL_RAM_PADDR(PHYS_OFFSET+TEXT_OFFSET)
24
25
.macropgtbl,rd
//此宏调用完毕后,r4的值就是0x4000,即16KB
26
27
ldr\rd,=(KERNEL_RAM_PADDR-0x4000)
28
29
.endm
30
31
接着看代码。
32
33
movr0,r4
34
35
movr3,#0
36
37
addr6,r0,#0x4000
38
39
//STR将寄存器的值往内存中传送。r3为0,故内存的值被设置为0.每调用一次str,r0递增4
40
41
//r0是baseaddress,其值可自动增减。由armaddressmode格式控制
42
43
1:strr3,[r0],#4
44
45
strr3,[r0],#4
46
47
strr3,[r0],#4
48
49
strr3,[r0],#4
50
51
teqr0,r6
52
53
bne1b
//此循环调用完毕后,0x4000-0x8000的内存都被设置为0。此时r0=32KB
54
55
56
57
//r10存储的是图9中proc_info的第三个long,也就是mmuflas,用于设置MMU参数
58
59
ldrr7,[r10,#PROCINFO_MM_MMUFLAGS]@mm_mmuflags

1.6.1ARMMMU设置介绍

虽然上面最后一条语句是一个简单的ldr,但背后的内容却相当丰富,不把它搞清楚,后面的内容将解释不清。来看proc-V7中mm_mmuflags对应的值是什么

1
.
long

PMD_TYPE_SECT|\
//#definePMD_TYPE_SECT(2<<0)
2
3
PMD_SECT_BUFFERABLE|\
//#definePMD_SECT_BUFFERABLE(1<<2)
4
5
PMD_SECT_CACHEABLE|\
//#definePMD_SECT_CACHEABLE(1<<3)
6
7
PMD_SECT_AP_WRITE|\
//#definePMD_SECT_AP_WRITE(1<<10)
8
9
PMD_SECT_AP_READ
//#definePMD_SECT_AP_READ(1<<11)
上面代码中把对应PMD_SECT_XXX的值显示出来,可知它无非是定义了一个32位的常量,某些位置的值为1,某些位置的值为0。为什么要怎么做呢?先来看ARMMMU所支持的虚实地址转换机制。





图13ARMMMU虚实地址转换

由图13可知:

q虚地址VA的[20-31]位和CP15CR2的[14-31]位共同构成FirstLevel地址。
q从FirstLevel地址将得到一个FirstLevelDescripter,也就是图13中标明memoryaccess的内容。
qFLD中不同字段表明其内容是段寻址还是页寻址。主要是根据前2位来判断。如果前2位是0b10则是段寻址。

结合图13和前面的代码:

qPMD_TYPE_SECT=2<<0,刚好就是0b10
qC|B控制Cachable和Buffable的,对应为[2,3]位
qAP对应为AccessPoint,对应为[10,11]位。

另外,Domain是ARMCPU的一个重要概念,主要和权限有关。以后碰到再说。

至此,当ldrr7xx执行完后,r7的值包含了sectionbaseaddress对应的[0-12]位的值。而sectionbaseaddress本身却还没有赋值。

接下来的代码就是为了构造一个FLD的值。根据图13,sectionbaseaddress应该是[20-31]位

1
//r6的值为当前PC值右移20位
2
3
movr6,pc,lsr#20
4
5
orrr3,r7,r6,lsl#20@flags+kernelbase
6
7
//此时,r3的值就是一个基于段寻址的FLD。把它存起来。位置是r4+r6<<2
8
9
strr3,[r4,r6,lsl#2]
现在r6存储的是段寻址的基地址,需要把这个值存储到对应表的位置,由于在表中,每一项是4个字节,所以这里需要乘以4,也就是lsl#2。

稍微解释下这里左移4的原因:

1r4存储的是表的起始地址
2r6存储的是offset
3r3存储的是往r4[offset]的值
4由于1个offset实际上是4个字节,所以真实存储的位置就是r4[4*offset]=r3

1.6.2设置页表

当理解上面代码后,下面就是把kernel虚拟地址的位置存储到r4表中了

继续看代码

01
//立即数的计算比较难理解,网上也没有相关说法。不过,只要知道下面这段代码就是存储kernel
02
03
//虚拟地址到对应页表位置即可
04
05
addr0,r4,#(KERNEL_START&0xff000000)>>18
06
07
strr3,[r0,#(KERNEL_START&0x00f00000)>>18]!
08
09
ldrr6,=(KERNEL_END-1)
10
11
addr0,r0,#4
//r0=ro+4
12
13
addr6,r4,r6,lsr#18
//r6=r4+r6>>18
14
15
1:cmpr0,r6
16
17
addr3,r3,#1<<20
//r3+=1<<20,每次递增1M
18
19
//ls是conditioncode,表示小于等于,即只要r0<=r6,strls就会执行
20
21
strlsr3,[r0],#4
22
23
bls1b
24
25
26
27
//map物理地址前1M到对应位置
28
29
addr0,r4,#PAGE_OFFSET>>18
30
31
orrr6,r7,#(PHYS_OFFSET&0xff000000)
32
33
.
if

(PHYS_OFFSET&0x00f00000)
34
35
orrr6,r6,#(PHYS_OFFSET&0x00f00000)
36
37
.endif
38
39
strr6,[r0]
40
41
42
43
movpc,lr
44
45
ENDPROC(__create_page_tables)
46
47
.ltorg

1.6.3总结

建议大家仔细体会create_page_tables这段内容。虽然以后不太可能会使用它们,但把这段代码搞清楚还是一个比较有意思的过程。

1.7剩余工作

回到head.S,最后还剩下几句代码:

01
//将__switch_data的位置存储到r13
02
03
ldrr13,__switch_data
04
05
//获取__enable_mmu标签的地址,并保存到lr中
06
07
adrlr,__enable_mmu
08
09
//r10存储的是__v7_proc_info的地址,#PROCINFO_INITFUNC是一个偏移量
10
11
//执行完下条语句后,pc指向__v7_proc_info的b__v7_setup,故下面这条语句就是
12
13
//执行__v7_setup函数
14
15
addpc,r10,#PROCINFO_INITFUNC
16
17
ENDPROC(stext)

1.7.1__switch_data说明

__switch_data标签如下,主要存储了一些数据。

[head-common.S]

01
.type__switch_data,%object
02
03
__switch_data:
04
05
.
long

__mmap_switched
06
07
.
long

__data_loc@r4
08
09
.
long

_data@r5
10
11
.
long

__bss_start@r6
12
13
.
long

_end@r7
14
15
.
long

processor_id@r4
16
17
.
long

__machine_arch_type@r5
18
19
.
long

__atags_pointer@r6
20
21
.
long

cr_alignment@r7
22
23
.
long

init_thread_union+THREAD_START_SP@sp
以后再讨论具体作用。

1.7.2__v7_setup

先来看

01
addpc,r10,#PROCINFO_INITFUNC
02
03
实际上就是执行__v7_setup函数。代码在mm/proc-v7.S中。
04
05
adrr12,__v7_setup_stack@thelocalstack
06
07
stmiar12,{r0-r5,r7,r9,r11,lr}
08
09
blv7_flush_dcache_all
10
11
ldmiar12,{r0-r5,r7,r9,r11,lr}
12
13
movr10,#0
14
15
dsb
16
17
#ifdefCONFIG_MMU//goldfish定义了这个配置项
18
19
mcrp15,0,r10,c8,c7,0@invalidateI+DTLBs
20
21
mcrp15,0,r10,c2,c0,2@TTBcontrol
register
22
23
orrr4,r4,#TTB_FLAGS
24
25
mcrp15,0,r4,c2,c0,1@loadTTB1
26
27
movr10,#0x1f@domains0,1=manager
28
29
mcrp15,0,r10,c3,c0,0@loaddomainaccess
register
30
31
#endif
32
33
ldrr5,=0xff0aa1a8
34
35
ldrr6,=0x40e040e0
36
37
mcrp15,0,r5,c10,c2,0@writePRRR
38
39
mcrp15,0,r6,c10,c2,1@writeNMRR
40
41
adrr5,v7_crval
42
43
ldmiar5,{r5,r6}
44
45
mrcp15,0,r0,c1,c0,0@readcontrol
register
46
47
bicr0,r0,r5@clearbitsthem
48
49
orrr0,r0,r6@setthem
50
51
//最后一句,将lr赋值给pc。执行完后,将跳到__enable_mmu函数。
52
53
movpc,lr@
return
tohead.S:__ret
54
55
ENDPROC(__v7_setup)
上面代码大多是执行ARMv7CPU的MMU相关设置的,而其中的汇编语句到比较简单。这也是ARMMMU设置的核心内容。下面我们将结合ARMCPURerference简单介绍下这些设置的内容。

请务必从ARM官方网页上下载下面两个文档:

qDDI0344D_cortex_a8_r2p1_trm.pdf:介绍CORTEXA8相关内容
qDDI0406B_arm_architecture_reference_manual_errata_markup_10_0:最新的ARM架构参考手册

1.如何看懂MMU设置并掌握理论知识
以下面这个设置为例:

mcrp15,0,r10,c8,c7,0

打开参考文档DDI0344D_cortex_a8_r2p1_trm.pdf的第112页。从这一页开始,C15协处理器的各个寄存器的配置都有详细的说明。如图14所示





图14C8寄存器的设置

上图中,左边空白区域对应的是C8。可知,c8,c7,0的组合对应的是InvalidateunifiedTLBunlockedentries.详细说明在page3-99。

如果在此文档中碰到有不理解的内容,就需要参考DDI0406B_arm_architecture_reference_manual_errata_markup_10_0。该文档会介绍一些理论知识。

篇幅原因,我就不在这里啰嗦。已经告诉大家如何钓鱼了,请大家自己尝试!

1.7.3__enable_mmu介绍

__v7_setup最后已经的movpc,lr将使得CPU跳转到__enable_mmu处,其代码如下所示:

01
__enable_mmu:
02
03
#ifdefCONFIG_ALIGNMENT_TRAP
04
05
orrr0,r0,#CR_A
06
07
#else
08
09
bicr0,r0,#CR_A
10
11
#endif
12
13
#ifdefCONFIG_CPU_DCACHE_DISABLE
14
15
bicr0,r0,#CR_C
16
17
#endif
18
19
#ifdefCONFIG_CPU_BPREDICT_DISABLE
20
21
bicr0,r0,#CR_Z
22
23
#endif
24
25
#ifdefCONFIG_CPU_ICACHE_DISABLE
26
27
bicr0,r0,#CR_I
28
29
#endif
30
31
//设置domain的权限,请参考前面的书籍了解DOMAIN在ARMMMU中的意义
32
33
movr5,#(domain_val(DOMAIN_USER,DOMAIN_MANAGER)|\
34
35
domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER)|\
36
37
domain_val(DOMAIN_TABLE,DOMAIN_MANAGER)|\
38
39
domain_val(DOMAIN_IO,DOMAIN_CLIENT))
40
41
//请参考前面的方法,了解下面这两条语句的实际作用
42
43
mcrp15,0,r5,c3,c0,0@loaddomainaccess
register
44
45
mcrp15,0,r4,c2,c0,0@loadpagetablepointer
46
47
b__turn_mmu_on
//跳转到__turn_mmu_on
48
49
ENDPROC(__enable_mmu)
50
51
简单看看__turn_mmu_on:
52
53
__turn_mmu_on:
54
55
movr0,r0

//类似nop的空指令,浪费一点CPU时间,怕引起racecondition发生
56
57
//c1,c0这两个控制MMU的设置
58
59
mcrp15,0,r0,c1,c0,0@writecontrolreg
60
61
mrcp15,0,r3,c0,c0,0@readidreg
62
63
movr3,r3
64
65
movr3,r3
66
67
//此时,MMU就正式启动了
68
69
movpc,r13

//r13指向__switch_data
70
71
ENDPROC(__turn_mmu_on)
MMU启动后,我们也无需管什么物理地址还是虚拟地址,直接去看对应地址的代码即可。如果您非对这个转换过程很感兴趣,建议您把那两个参考书好好瞅瞅。

1.7.4__mmaped_switched介绍

__switch_data第一个定义的就是__mmaped_switched,PC将执行这里的指令:

01
__mmap_switched:
02
03
adrr3,__switch_data+4
04
05
06
07
ldmiar3!,{r4,r5,r6,r7}
08
09
cmpr4,r5@Copydatasegment
if
needed
10
11
1:cmpner5,r6
12
13
ldrnefp,[r4],#4
14
15
strnefp,[r5],#4
16
17
bne1b
18
19
20
21
movfp,#0@ClearBSS(andzerofp)
22
23
1:cmpr6,r7
24
25
strccfp,[r6],#4
26
27
bcc1b
28
29
30
31
ldmiar3,{r4,r5,r6,r7,sp}
32
33
strr9,[r4]@SaveprocessorID
34
35
strr1,[r5]@Savemachinetype
36
37
strr2,[r6]@Saveatagspointer
38
39
bicr4,r0,#CR_A@Clear
'A'
bit
40
41
stmiar7,{r0,r4}@Savecontrol
register
values
1
//上面我就懒得废话了,下面这句代码相信各位都很了解。执行start_kernel函数。
1
<b>bstart_kernel</b>
1
ENDPROC(__mmap_switched)

二总结

我觉得需要说明下为什么写这篇文章:

早在2010年7月的时候,我就看了那本鼎鼎大名的《ARM体系结构与编程》,这应该是第一本系统介绍ARM体系结构和编程的书。但是没看懂,全是枯燥的ARMCPU设置,纯教科书。

最近因为工作的原因,想把ARM这块重新捡起来,想起2年的痛苦,觉得应该换个思路。ARM也好,汇编也好,我们应该关注它的目的,而不是具体它是怎么实现的。即了解Whattodo比了解Howtodo更重要(仅我个人目的而言,前者重要。不过在某些追求细节的时候,后者重要。需要你自己去判断)。根据这个思路,我选择以LinuxKernel启动为分析对象,大致研究流程如下:

q先花几天时间了解下ARM汇编的大概语句。
q直接上代码分析。不过你得对Kernel启动的流程稍有了解。还好我在《深入理解Android卷I》写完后,花了点时间把这块整理了下。请参考/article/1361287.html

q碰到不懂的汇编语句,就查参考手册。这些还只是针对一些没有背景知识的语句。
q当碰到类似CP15操作的语句时,其背后往往包含了较多的CPU相关的知识,这时候就需要查阅前面提到的两本参考书籍,去真真正正了解ARMCPU运行的相关原理。

大概经过2周先痛苦挣扎,到后面豁然开朗的过程,后续的研究就非常非常流畅了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: