注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

liuyue18301的个人主页

追逐梦想 光辉岁月

 
 
 

日志

 
 

ARM Linux启动分析----head-armv.S内幕-1  

2009-10-30 17:24:59|  分类: uboot |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Linux启动后执行的第一个文件是arch/arm/kernel下的head-($PROCESSOR).S文件,processor代表的是该cpu的类型。ARM 6及其以后的处理器核心支持32位地址空间。这些处理器可以在26位和 32位PC的模式下操作。在26位PC模式下,R15寄存器的表现如同在以前的处理器上,代码只能运行在地址空间的最低的64M字节空间中。在32位PC模式下,32位的R15寄存器被用做程序计数器。使用独立的状态寄存器来存储处理器模式和状态标志。对于26位的arm处理器类型,linux用armo来表示;对于32位的arm处理器,使用armv表示。在include/linux/autoconf.h文件中通过

#define CONFIG_CPU_32   1

将处理器类型设置为支持32位PC模式。然后在arch/arm/Makefile中通过

ifeq ($(CONFIG_CPU_32),y)

PROCESSOR = armv

TEXTADDR = 0xC0008000

LDSCRIPT = arch/arm/vmlinux-armv.lds.in

endif

设置处理器类型为armv,这样linux运行所执行的第一个文件就是head-armv.S。接着,Makefile定义了内核中代码和数据所使用的虚拟地址TEXRADDR,最后,定义了链接器所使用的脚本文件,这个文件也是与处理器类型相关的。

在执行head-armv.S文件之前,有一点需要注意的是,bootloader已经在处理器的R1寄存器中存放了机器体系结构的类型号。由于在文件的执行过程中将要针对当前的机器体系结构设置相关的参数,如果没有这个步骤,系统将显示“ERROR:a”,同时停止执行。当然,也可以在head-armv.S文件的开头添加代码,手工对R1赋值,具体的机器类型号在arch/arm/tools/mach-types文件中。

好了,接下来我们可以开始阅读head-armv.S文件了,看看它到底作了些什么事情。由于篇幅的限制,对一些不是很关键的代码和英文注释予以省略,但是在每段代码后,我会根据自己的理解给出解释。

 

#if (TEXTADDR & 0xffff) != 0x8000

#error TEXTADDR must start at 0xXXXX8000

#endif

 

      .globl  SYMBOL_NAME(swapper_pg_dir)

      .equ    SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000

 

      .macro pgtbl, reg, rambase

      adr   \reg, stext

      sub   \reg, \reg, #0x4000

      .endm

 

      .macro krnladr, rd, pgtable, rambase

      bic   \rd, \pgtable, #0x000ff000

      .endm

首先,系统确保TEXTADDR的地址是以0x8000结尾的,前面已经提到过,TEXTADDR的地址是0xC0008000,是内核所使用的虚拟地址,而我所使用的PXA255处理器上支持的SDRAM空间是从0xA0000000开始的,这就需要通过MMU进行虚拟地址到实际物理地址的转换,也就是说将0xC0008000映射到0xA0008000。地址转换所使用的页表将存放在从0xA0008000网上的16K空间中,即从0xA0004000到0xA0008000这一段。因此,系统必须空出0xA0000000到0xA0008000这一段,存放页表和其它的一些内核将使用到的数据结构。虽然上面的代码判断的是TEXTADDR的地址是否以0x8000结尾,但从效果上说是一样的。

接着,代码定义了全局变量swapper_pg_dir,它是页表目录项的虚拟地址。前面用SYMBOL_NAME()修饰,这是因为在有的系统中,C编绎器对.C文件中的符号名有"_"前缀,SYMBOL_NAME()可以使汇编代码也适应这种变化。但是在当前的Linux中,SYMBOL_NAME实际上不起任何作用。大家可以参考include/linux/linkage.h中对该修饰符的定义。

然后,代码定义了pgtbl和krnladr两个宏。Pgtbl宏得到的是与位置无关的页表目录项地址,值为0xA000800往上16k的地址,即0xA0004000。stext所代表的也是内核的起始地址,通过arch/arm/vmlinux-armv.lds.in的链接脚本可以发现它在内核中的链接地址和TEXTADDR一致。那么为什么页表地址不是0xC0004000呢?因为我们在定义/reg寄存器时使用的adr指令,adr指令是在当前的PC值上+/-一个标号的偏移得到的, 所以得到的地址只跟PC和标号到PC的偏移相关, 跟编译地址无关。在MMU打开前, 代码要是地址无关的, 会经常用到adr指令。由于当前的PC运行的地址是从0xA0008000开始的地址空间,所以最后得到的页表地址为0xA0004000。krnladr宏需要配合其它代码使用,它的本意是为了使对从0xA0000000开始的内核的地址空间的寻址不会因为MMU的原因而被映射到其它的地址。因此需要将定义0xA0000000地址转换的页表项中的值的高20位定义为0xA0000,最低的12位保存的是页表的标志位。由于该页表项的索引值是由地址的最高12位所决定的,因此krnladr宏将地址的最低20位清零。在本文件中,只清空了第4--11位,是因为有其它的代码屏蔽了低12位的作用。如果将上面的代码改成bic \rd, \pgtable, #0x000fffff,效果是一样的。

 

.section ".text.init",#alloc,#execinstr

     .type stext, #function

ENTRY(stext)

     mov   r12, r0

 

mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode

msr cpsr_c, r0        @ and all irqs disabled

bl     __lookup_processor_type

teq r10, #0           @ invalid processor?

moveq r0, #'p'       @ yes, error 'p'

beq __error

bl     __lookup_architecture_type

teq r7, #0            @ invalid architecture?

moveq r0, #'a'       @ yes, error 'a'

beq __error

bl  __create_page_tables

adr lr, __ret         @ return address

add  pc, r10, #12         @ initialise processor

                    @ (return control reg)

接着我们进入了head-armv.S的主程序段,参考上面的代码。首先,确保处理器进入SVC模式,屏蔽所有外部中断。接着查询处理器类型和机器的体系结构类型,其中任何一步发生错误,显示“ERROR:p”或者“ERROR:a”。然后建立页表目录项。我们来看看每个子程序段具体是如何工作的。

 

__lookup_processor_type:

      adr       r5, 2f

      ldmia r5, {r7, r9, r10}

      sub   r5, r5, r10               @ convert addresses

      add       r7, r7, r5            @ to our address space

      add       r10, r9, r5

      mrc       p15, 0, r9, c0, c0   @ get processor id

1:    ldmia r10, {r5, r6, r8}    @ value, mask, mmuflags

      and       r6, r6, r9            @ mask wanted bits

      teq       r5, r6

      moveq pc, lr

      add       r10, r10, #36        @ sizeof(proc_info_list)

      cmp       r10, r7

      blt       1b

      mov       r10, #0               @ unknown processor

      mov       pc, lr

 

2:    .long __proc_info_end

      .long __proc_info_begin

      .long 2b

      .long __arch_info_begin

      .long __arch_info_end

代码首先在R5寄存器中存放标号2所代表的相对地址,然后通过ldmia r5, {r7, r9, r10}在R7和R9中放置__proc_info_end、__proc_info_begin的链接地址,在R10中放置标号2的链接地址。通过将R5和R10中的数值相减,得到符号的链接地址和实际地址之间的差值,进而得到__proc_info_end、__proc_info_begin的实际地址。其实这些代码的作用和adr __proc_info_end,adr __proc_info_begin的效果是一样的。在MMU还没有被打开的情况下,一般采取这种办法来进行地址之间的映射。

这里有一点要注意,在引用标号2的地址时,采取了2f和2b两种不同的表示法,这是什么原因呢?在代码中你可以使用0--99之间的数字作为标号,它们会被视为临时性的符号,可以在代码中重复使用同一个数字作为label。在一个分支指令(branch instruction)中“2f”指向下一个“2:”,而“2b”指向前一个“2:”,这样就不用费心为那些随手而写的跳转和循环起名字了,省下这些名称可以去命名那些子程序、还有那些比较关键的跳转。

接着代码通过访问P15协处理器,得到当前的CPU的处理器ID,然后与以__proc_info_begin开始的处理器信息结构中的处理器ID相比较,相等则返回,不等则跳转到下一个处理器信息结构继续比较。从__proc_info_begin开始的保存处理器信息的结构的类型为struct proc_info_list,在include/asm-arm/procinfo.h中有具体的定义。实际的各处理器信息结构的赋值在arch/arm/mm/proc-xscale.S文件的.section ".proc.info", #alloc, #execinstr语句下面。为什么是在.proc.info段的下面呢?这是由vmlinux-armv.lds.in文件中的代码

__proc_info_begin = .;

   *(.proc.info)

__proc_info_end = .;

所决定的。

该段子程序完成后,各寄存器情况如下:

R8 = 页表目录项的标志位

R9 = 处理器ID

R10 = 指向当前处理器信息结构的指针

 

__lookup_architecture_type:

      adr       r4, 2b

      ldmia r4, {r2, r3, r5, r6, r7}   @ throw away r2, r3

      sub       r5, r4, r5        @ convert addresses

      add       r4, r6, r5        @ to our address space

      add       r7, r7, r5

1:    ldr       r5, [r4]          @ get machine type

      teq       r5, r1

      beq       2f

      add       r4, r4,           #SIZEOF_MACHINE_DESC

      cmp       r4, r7

      blt       1b

      mov       r7, #0            @ unknown architecture

      mov       pc, lr

2:    ldmib r4, {r5, r6, r7} @ found, get results

      mov       pc, lr

这里开始查找机器的体系结构信息。前面已经提到过,在开始执行head-armv.S文件之前,R1中已经包含了当前的体系结构的类型号。现在所要做的,就是在__arch_info_begin开始的地址中,查找与R1中的值相匹配的机器类型信息。从__arch_info_begin开始的保存机器体系结构信息的类型为struct machine_desc,该结构在include/asm-arm/mach/arch.h中有具体的定义。对该结构的赋值使用MACHINE_START宏,该宏的定义同样在arch.h文件中。具体的机器体系结构的信息在arch/arm/kernel/arch.c中,当然也可以在arch/arm/mach-($machine-type)目录下的文件中添加与你自己的机器体系相对应的代码。这一点在进行linux移植的工作中很重要

  评论这张
 
阅读(188)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018