在分析stext函数前,先介绍此时内存的布局如下图所示
在开发板tqs3c2440中,SDRAM连接到内存控制器的Bank6中,它的开始内存地址是0x30000000,大小为64M,即0x20000000。 ARM Linux kernel将SDRAM的开始地址定义为PHYS_OFFSET。经bootloader加载kernel并由自解压部分代码运行后,*终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。
在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址。
以arm kernel**个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?)。实际上这个变换可以利用简单的公式进行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux*终的kernel空间的页表,就是按照这个关系来建立。
之所以较早提及arm linux 的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一色的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数**条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。因此,下面有些代码,需要使用地址无关技术。
2. 一览stext函数
这里的启动流程指的是解压后kernel开始执行的一部分代码,这部分代码和ARM体系结构是紧密联系在一起的,所以*好是将ARM ARCHITECTURE REFERENCE MANUL仔细读读,尤其里面关于控制寄存器啊,MMU方面的内容~
stext函数定义在Arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进**个C语言函数start_kernel。
stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.
前面说过解压以后,代码会跳到解压完成以后的vmlinux开始执行,具体从什么地方开始执行我们可以看看生成的vmlinux.lds(arch/arm/kernel/)这个文件:
1.OUTPUT_ARCH(arm)
2.ENTRY(stext)
3.jiffies=jiffies_64;
4.SECTIONS
5.{
6..=0x80000000+0x00008000;
7..text.head:{
8._stext=.;
9._sinittext=.;
0.*(.text.h
很明显我们的vmlinx*开头的section是.text.head,这里我们不能看ENTRY的内容,以为这时候我们没有操作系统,根本不知道如何来解析这里的入口地址,我们只能来分析他的section(不过一般来说这里的ENTRY和我们从seciton分析的结果是一样的),这里的.text.head section我们很容易就能在arch/arm/kernel/head.S里面找到,而且它里面的**个符号就是我们的stext:
#.section".text.head","ax"
#
#ENTRY(stext)
#
#/*设置CPU运行模式为SVC,并关中断*/
#
#msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE@ensuresvcmode
#
#@andirqsdisabled
#
#mrcp15,0,r9,c0,c0@getprocessorid
#
#bl__lookup_processor_type@r5=procinfor9=cupid
#
#/*r10指向cpu对应的proc_info记录*/
#
#movsr10,r5@invalidprocessor(r5=0)?
#
#beq__error_p@yes,error'p'
#
#bl__lookup_machine_type@r5=machinfo
#
#/*r8指向开发板对应的arch_info记录*/
#
#movsr8,r5@invalidmachine(r5=0)?
#
#beq__error_a@yes,error'a'
#
#/*__vet_atags函数涉及bootloader造知kernel物理内存的情况,我们暂时不分析它。*/
#
#bl__vet_atags
#
#/*创建临时页表*/
#
#bl__create_page_tables
#/*
#
#*ThefollowingcallsCPUspecificcodeinapositionindependent
#
#*manner.Seearch/arm/mm/proc-*.Sfordetails.r10=baseof
#
#*xxx_proc_infostructureselectedby__lookup_machine_type
#
#*above.Onreturn,theCPUwillbereadyfortheMMUtobe
#
#*turnedon,andr0willholdtheCPUcontrolregistervalue.
#
#*/
#
#/*这里的逻辑关系相当复杂,先是从proc_info结构中的中跳进__arm920_setup函数,
#
#*然后执__enable_mmu函数。*后在__enable_mmu函数通过movpc,r13来执行__switch_data,
#
#*__switch_data函数在*后一条语句,鱼跃龙门,跳进**个C语言函数start_kernel。
#*/
#
#ldrr13,__switch_data@addresstojumptoafter
#
#@mmuhasbeenenabled
#
#adrlr,__enable_mmu@return(PIC)address
#
#addpc,r10,#PROCINFO_INITFUNC
#
#ENDPROC(stext)
这里的ENTRY这个宏实际我们可以在include/linux/linkage.h里面找到,可以看到他实际上就是声明一个GLOBAL Symbol,后面的ENDPROC和END**的区别是前面的声明了一个函数,可以在c里面被调用。
1.#ifndefENTRY
2.#defineENTRY(name)/
3..globlname;/
4.ALIGN;/
5.name:
6.#endif
7.#ifndefWEAK
8.#defineWEAK(name)/
9..weakname;/
10.name:
11.#endif
12.#ifndefEND
13.#defineEND(name)/
14..sizename,.-name
15.#endif
16./*Ifsymbol'name'istreatedasasubroutine(getscalled,andreturns)
17.*thenpleaseuseENDPROCtomark'name'asSTT_FUNCforthebenefitof
18.*staticanalysistoolssuchasstackdepthanalyzer.
19.*/
20.#ifndefENDPROC
21.#defineENDPROC(name)/
22..typename,@function;/
23.END(name)
24.#endif
找到了vmlinux的起始代码我们就来进行分析了,先总体概括一下这部分代码所完成的功能,head.S会**检查proc和arch以及atag的有效性,然后会建立初始化页表,并进行CPU必要的处理以后打开MMU,并跳转到start_kernel这个symbol开始执行后面的C代码。这里有很多变量都是我们进行kernel移植时需要特别注意的,下面会一一讲到。
在这里我们**看看这段汇编开始跑的时候的寄存器信息,这里的寄存器内容实际上是同bootloader跳转到解压代码是一样的,就是r1=arch r2=atag addr。下面我们就具体来看看这个head.S跑的过程:
1.msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE@ensuresvcmode
百检网秉承“客户至上,服务为先,精诚合作,以人为本”的经营理念,始终站在用户的角度解决问题,为客户提供“一站购物式”的新奇检测体验,打开网站,像挑选商品一样简单,方便。打破行业信息壁垒,建构消费和检测机构之间高效的沟通平台