basonjiang的专栏

心智决定性格,性格决定格局,格局确定命运,命运确定未来//互联移动,一起来...

Linux启动(2)-

            Linux启动/kernel/arch/arm/boot/compressed/ head.S分析

这段代码是linux boot后执行的第一个程序,完成的主要工作是解压内核,然后跳转到相关执行地址。这部分代码在做驱动开发时不需要改动,但分析其执行流程对是理解android的第一步

 

开头有一段宏定义这是gnu arm汇编的宏定义。关于GUN的汇编和其他编译器,在指令语法上有很大差别,具体可查询相关GUN汇编语法了解

另外此段代码必须不能包括重定位部分。因为这时一开始必须要立即运行的。所谓重定位,比如当编译时某个文件用到外部符号是用动态链接库的方式,那么该文件生成的目标文件将包含重定位信息,在加载时需要重定位该符号,否则执行时将因找不到地址而出错

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

 

#if defined(CONFIG_DEBUG_ICEDCC)

……具体代码略

#endif

 

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

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

 

这个段的段名是 .start#alloc表示Section contains allocated data, #execinstr表示Section contains executable instructions.

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

              .align

start:

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

              .rept 8

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

.endr

 

              b     1f

              .word      0x016f2818           @ Magic numbers to help the loader

              .word      start               @ absolute load/run zImage

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

address

              .word      _edata                   @ 内核结束地址

//注意这些地址在顶层vmlixu.lds(具体在/kernel文件夹里)里进行了定义,是链接的地址,加载内核后可能会进行重定位

1:            mov r7, r1                    @ 保存architecture ID ,这里是从bootload传递进来的

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

r1r2中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。这里将这两个参数先保存。

 

#ifndef __ARM_ARCH_2__

              /*

               * Booting from Angel - need to enter SVC mode and disable

               * FIQs/IRQs (numeric definitions from angel arm.h source).

               * We only do this if we were in user mode on entry.

               */

读取cpsr并判断是否处理器处于supervisor模式——从bootload进入kernel,系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入

 

Angel ARM 的调试协议,一般用的 MULTI-ICE ANGLE 需要在板子上有 驻留程序,然后通过 串口就可以调试了 。用过的AXDtrace调试环境的话,对此应该比较熟悉。  

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

              mrs  r2, cpsr          @ turn off interrupts to

              orr   r2, r2, #0xc0         @ prevent angel from running

              msr  cpsr_c, r2   //这里将cpsrIF位分别置“1,关闭IRQFIQ

#else

              teqp pc, #0x0c000003          @ turn off interrupts

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

#endif

  另外链接器会把一些处理器相关的代码链接到这个位置,也就是arch/arm/boot/compressed/head-xxx.S文件中的代码。在高通平台下,这个文件是head-msm.S连接脚是compress/vmlinux.lds,其中部分内容大致如下,在连接时,连接器根据每个文件中的段名将相同的段合在一起,比如将head.Shead-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

    adr   r0, LC0 //当前运行时LC0符号所在地址位置 ,注意,这里用的是adr指令,这个指令会根据目前PC的值,计算符号相对于PC的位置,是个相对地址。之所以这样做,是因为下面指令用到了绝对地址加载ldmia指令,必须要调整确定目前LC0的真实位置,这个位置也就是用adr来计算

    ldmia       r0, {r1, r2, r3, r4, r5, r6, ip, sp}

   subs r0, r0, r1        @ //这里获得当前LCD0实际地址与链接地址 差值

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

//差值存入r0中。

              beq  not_relocated //如果相等不需要重定位,因为已经在正确的//地址运行了。重定位的原因是,MMU单元未使能,不能进行地址映射,必须要手工重定位。

下面举个简单例子说明:

如果连接地址是0xc0000000,那么LC0的连接地址假如连接为0xc0000010,那么LC0相对于连接起始地址的差为0x10,当此段代码是从0xc0000000运行的话,那么执行adr r0LC0的值实际上按下面公式计算:

R0=PC+0x10,由于PC=连接处的值,可知,此时是在ram中运行, 同理如果是在不是在连接处运行,则假设是在0x00000000处运行,则R0=0x00000000+0x10,可知,此时不是在ram的连接处运行。

上面这几行代码用于判断代码是否已经重定位到内存中,LC0这个符号在head.S中定义如下,实质上相当于c语言的全局数据结构,结构的每个域存储的是一个指针。指针本身的值代表不同的代码段,已经在顶层连接脚本vmlinux.lds里进行了赋值,比如_start是内核开始的地址

              .type       LC0, #object

LC0:              .word      LC0               @ r1 //这个要加载到r1中的LC0是链接时LC0的地址

              .word      __bss_start            @ r2

              .word      _end                     @ r3

              .word      zreladdr          @ r4

              .word      _start                    @ r5

              .word      _got_start              @ r6

              .word      _got_end        @ ip

              .word      user_stack+4096           @ sp

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

 

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

              /*

               *   r5 - zImage base address

               *   r6 - GOT start

               *   ip - GOT end

               */

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

              add  r5, r5, r0 //修改内核映像基地址

              add  r6, r6, r0

              add  ip, ip, r0 //修改got表的起始和结束位置

 

#ifndef CONFIG_ZBOOT_ROM

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

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

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

            

               *   r2 - BSS start

               *   r3 - BSS end

               *   sp - stack pointer

               */

              add  r2, r2, r0

              add  r3, r3, r0

              add  sp, sp, r0

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

1:            ldr   r1, [r6, #0]            @ relocate entries in the GOT

              add  r1, r1, r0        @ table.  This fixes up the

              str   r1, [r6], #4            @ C references.

              cmp r6, ip

              blo   1b

#else //若定义了CONFIG_ZBOOT_ROM,只对got表中在bss段以外的符号进行重定位

            

1:            ldr   r1, [r6, #0]            @ relocate entries in the GOT

              cmp r1, r2                    @ entry < bss_start ||

              cmphs     r3, r1                    @ _end < entry

              addlo       r1, r1, r0        @ table.  This fixes up the

              str   r1, [r6], #4            @ C references.

              cmp r6, ip

              blo   1b

#endif

 

 

 

 

 

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

not_relocated: mov r0, #0

1:            str   r0, [r2], #4            @ clear bss

              str   r0, [r2], #4

              str   r0, [r2], #4

              str   r0, [r2], #4

              cmp r2, r3

              blo   1b

 

之后跳转到cache_on

         

              bl     cache_on

 

cache_on定义

              .align       5

cache_on:       mov r3, #8                    @ cache_on function

              b     call_cache_fn

 

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

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

 

call_cache_fn:

adr   r12, proc_types  //proc_types的相对地址加载到r12

#ifdef CONFIG_CPU_CP15

              mrc p15, 0, r6, c0, c0   @ get processor ID

#else

              ldr   r6, =CONFIG_PROCESSOR_ID

#endif

1:            ldr   r1, [r12, #0]          @ get value

              ldr   r2, [r12, #4]          @ get mask

              eor   r1, r1, r6        @ (real ^ match)

              tst    r1, r2           @否和CPU ID匹配?

              addeq      pc, r12, r3             @ 用刚才的偏移量,查找//cache操作函数,找到后就执行相关操作,比如执行b     __armv7_mmu_cache_on

 // 

 

  add  r12, r12, #4*5 //如果不相等,则偏移到下个proc_types结构处

   b     1b

 

addeq      pc, r12, r3             @ call cache function

 

 

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

              .type       proc_types,#object

proc_types:

              .word      0x41560600           @ ARM6/610

              .word      0xffffffe0

              b     __arm6_mmu_cache_off      @ works, but slow

              b     __arm6_mmu_cache_off

              mov pc, lr

@           b     __arm6_mmu_cache_on              @ untested

@           b     __arm6_mmu_cache_off

@           b     __armv3_mmu_cache_flush

 

              .word      0x00000000           @ old ARM ID

              .word      0x0000f000

              mov pc, lr

              mov pc, lr

              mov pc, lr

 

              .word      0x41007000           @ ARM7/710

              .word      0xfff8fe00

              b     __arm7_mmu_cache_off

              b     __arm7_mmu_cache_off

              mov pc, lr

 

              .word      0x41807200           @ ARM720T (writethrough)

              .word      0xffffff00

              b     __armv4_mmu_cache_on

              b     __armv4_mmu_cache_off

              mov pc, lr

 

              .word      0x41007400           @ ARM74x

              .word      0xff00ff00

              b     __armv3_mpu_cache_on

              b     __armv3_mpu_cache_off

              b     __armv3_mpu_cache_flush

             

              .word      0x41009400           @ ARM94x

              .word      0xff00ff00

              b     __armv4_mpu_cache_on

              b     __armv4_mpu_cache_off

              b     __armv4_mpu_cache_flush

 

              .word      0x00007000           @ ARM7 IDs

              .word      0x0000f000

              mov pc, lr

              mov pc, lr

              mov pc, lr

 

              @ Everything from here on will be the new ID system.

 

              .word      0x4401a100           @ sa110 / sa1100

              .word      0xffffffe0

              b     __armv4_mmu_cache_on

              b     __armv4_mmu_cache_off

              b     __armv4_mmu_cache_flush

 

              .word      0x6901b110           @ sa1110

              .word      0xfffffff0

              b     __armv4_mmu_cache_on

              b     __armv4_mmu_cache_off

              b     __armv4_mmu_cache_flush

 

              @ These match on the architecture ID

 

              .word      0x00020000    @

              .word      0x000f0000        //

b   __armv4_mmu_cache_on

              b     __armv4_mmu_cache_on  //指令的地址

              b     __armv4_mmu_cache_off

              b     __armv4_mmu_cache_flush

 

              .word      0x00050000           @ ARMv5TE

              .word      0x000f0000

              b     __armv4_mmu_cache_on

              b     __armv4_mmu_cache_off

              b     __armv4_mmu_cache_flush

 

              .word      0x00060000           @ ARMv5TEJ

              .word      0x000f0000

              b     __armv4_mmu_cache_on

              b     __armv4_mmu_cache_off

              b     __armv4_mmu_cache_flush

 

              .word      0x0007b000           @ ARMv6

              .word      0x0007f000

              b     __armv4_mmu_cache_on

              b     __armv4_mmu_cache_off

              b     __armv6_mmu_cache_flush

 

              .word      0                   @ unrecognised type

              .word      0

              mov pc, lr

              mov pc, lr

              mov pc, lr

 

              .size proc_types, . - proc_types

   找到执行的cache函数后,就用上面的 addeq   pc, r12, r3直接跳转,例如执行下面这个处理器结构的cache函数

__armv7_mmu_cache_on:

         mov r12, lr //注意,这里需要手工保存返回地址!!这样做的原因是下面的bl指令会覆盖掉原来的lr,为保证程序正确返回,需要保存原来lr的值

              bl     __setup_mmu

              mov r0, #0

              mcr p15, 0, r0, c7, c10, 4     @ drain write buffer

              mcr p15, 0, r0, c8, c7, 0      @ flush I,D TLBs

              mrc p15, 0, r0, c1, c0, 0      @ read control reg

              orr   r0, r0, #0x5000             @ I-cache enable, RR cache replacement

              orr   r0, r0, #0x0030   

              bl     __common_mmu_cache_on

              mov r0, #0

              mcr p15, 0, r0, c8, c7, 0      @ flush I,D TLBs

              mov pc, r12  //返回到cache_on

这个函数首先执行__setup_mmu,然后清空write bufferI/DcacheTLB.接着打开i-cache,设置为Round-robin replacement。调用__common_mmu_cache_on,打开mmud-cache.把页表基地址和域访问控制写入协处理器寄存器c2c3. __common_mmu_cache_on函数数定义如下:

__common_mmu_cache_on:

#ifndef DEBUG

              orr   r0, r0, #0x000d             @ Write buffer, mmu

#endif

              mov r1, #-1 //-1的补码是ffff ffff,

              mcr p15, 0, r3, c2, c0, 0      @ 把页表地址存于协处理器寄存器中

              mcr p15, 0, r1, c3, c0, 0   @设置domain access control寄存

              b     1f                                

              .align       5                   @ cache line aligned

1:            mcr p15, 0, r0, c1, c0, 0      @ load control register

              mrc p15, 0, r0, c1, c0, 0      @ and read it back to

              sub  pc, lr, r0, lsr #32    @ properly flush pipeline

 

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

 

__setup_mmu:       sub  r3, r4, #16384        @ Page directory size

              bic   r3, r3, #0xff          @ Align the pointer

              bic   r3, r3, #0x3f00

这里r4中存放着内核执行地址,将16K的一级页表放在这个内核执行地址下面的16K空间里,上面通过 sub  r3, r4, #16384  获得16K空间后,又将页表的起始地址进行16K对齐放在r3中。即ttb的低14位清零。

 

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

              mov r0, r3

              mov r9, r0, lsr #18

              mov r9, r9, lsl #18         @ start of RAM

              add  r10, r9, #0x10000000    @ a reasonable RAM size

 

上面这几行把一级页表的起始地址保存在r0中,并通过r0获得一个ram起始地址(每个页面大小为1M)然后映射256M ram空间,并把对应的描述符的CB位均置”1   

              mov r1, #0x12 //一级描述符的bit[1:0]10,表示这是一个section描述符。也即分页方式为段式分页 

              orr   r1, r1, #3 << 10 //一级描述符的access permission bits bit[11:10]11.

 

              add  r2, r3, #16384  //一级描述符表的结束地址存放在r2中。

 

 

1:            cmp r1, r9                    @ if virt > start of RAM

              orrhs       r1, r1, #0x0c         @ set cacheable, bufferable

              cmp r1, r10                  @ if virt > end of RAM

              bichs       r1, r1, #0x0c         @ clear cacheable, bufferable

              str   r1, [r0], #4            @ 1:1 mapping

              add  r1, r1, #1048576//下个1M物理空间,每个页框1M

              teq   r0, r2

              bne  1b

 

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

上面这段就是对一级描述符表(页表)的初始化,首先比较这个描述符所描述的地址是否在那个256M的空间中,如果在则这个描述符对应的内存区域是cacheable ,bufferable。如果不在则noncacheable, nonbufferable.然后将描述符写入一个一级描述符表的入口,并将一级描述符表入口地址加4,而指向下一个1M section的基地址。如果页表入口未初始化完,则继续初始化。

 

页表大小为16K,每个描述符4字节,刚好可以容纳4096个描述符,每个描述符映射1M     ,那么4096*所以这里就映射了4096*1M = 4G的空间。因此16K的页完全可以把256M地址空间全部 映射

 

              mov r1, #0x1e

              orr   r1, r1, #3 << 10 //这两行将描述的bit[11:10] bit[4:1]置位,

//具体置位的原因,在ARM11的页表项描述符里有说明,由于没找到完整的文档,这里只给出图示:

 

 

              mov r2, pc, lsr #20

              orr   r1, r1, r2, lsl #20  //将当前地址进1M对齐,并与r1中的内容结合形成一个描述当前指令所在section的描述符。

 

              add  r0, r3, r2, lsl #2   //r3为刚才建立的一级描述符表的起始地址。通过将当前地

//(pc)的高12位左移两位(形成14位索引)r3中的地址

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

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

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

                          

              str   r1, [r0], #4

              add  r1, r1, #1048576

              str   r1, [r0]          //这里将上面形成的描述符及其连续的下一个section描述

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

//2位)

 

              mov pc, lr       //返回,调用此函数时,调用指令的下一语句mov   r0, #0的地 址保存在lr

                      

 

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

 

__common_mmu_cache_on最后执行mov pc, r12返回cache_on,为何返回到的是cache_on呢?这就是上面解释保存lr的原因,因为原来的lr保存了 执行

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

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

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

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

  栈的分配如下:

       .align

              .section ".stack", "w"

user_stack:    .space    4096//lc0SP进行了定义   .word     user_stack+4096  @ sp

由此可见sp是往下增长的

分配了解压缩用的缓冲区,那么接下来就判断这个数据区是否和我们目前运行的代码空间重叠,如果重叠则需调整

/*

 * Check to see if we will overwrite ourselves.

 *   r4 = final kernel address

 *   r5 = start of this image

 *   r2 = end of malloc space (and therefore this image)

 * We basically want:

 *   r4 >= r2 -> OK

 *   r4 + image length <= r5 -> OK

 */

              cmp r4, r2

              bhs  wont_overwrite

              sub  r3, sp, r5        @ > compressed kernel size

              add  r0, r4, r3, lsl #2      @ allow for 4x expansion

              cmp r0, r5

              bls   wont_overwrite

 

缓冲区空间的起始地址和结束地址分别存放在r1r2中。然后判断最终内核地址,也就是解压后内核的起始地址,是否大于malloc空间的结束地址,如果大于就跳到wont_overwrite执行,wont_overwrite函数后面会讲到。否则,检查最终内核地址加解压后内核大小,也就是解压后内核的结束地址,是否小于现在未解压内核映像的起始地址。小于也会跳到wont_owerwrite执行。如两这两个条件都不满足,则继续往下执行。

 

              mov r5, r2                    @ decompress after malloc space

              mov r0, r5

              mov r3, r7

              bl     decompress_kernel

 

这里将解压后内核的起始地址设为malloc空间的结束地址。然后后把处理器id(开始时保存在r7中)保存到r3中,调用decompress_kernel开始解压内核。这个函数的四个参数分别存放在r0-r3中,它在arch/arm/boot/compressed/misc.c中定义。 解压的过程为先把解压代码放到缓冲区,然后从缓冲区在拷贝到最终执行空间。

 

              add  r0, r0, #127

              bic   r0, r0, #127           @ align the kernel length

/*

 * r0     = decompressed kernel length

 * r1-r3  = unused

 * r4     = kernel execution address

 * r5     = decompressed kernel start

 * r6     = processor ID

 * r7     = architecture ID

 * r8     = atags pointer

 * r9-r14 = corrupted

 */

              add  r1, r5, r0        @ end of decompressed kernel

              adr   r2, reloc_start

              ldr   r3, LC1

              add  r3, r2, r3

1:            ldmia       r2!, {r9 - r14}              @ copy relocation code

              stmia       r1!, {r9 - r14}

              ldmia       r2!, {r9 - r14}

              stmia       r1!, {r9 - r14}

              cmp r2, r3

              blo   1b

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

 

              bl     cache_clean_flush

              add  pc, r5, r0        @ call relocation code

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

 

call_kernel:   

bl    cache_clean_flush

              bl    cache_off

              mov r0, #0                   @ must be zero

              mov r1, r7                    @ restore architecture number

              mov r2, r8                    @ restore atags pointer

              mov pc, r4                   @ call kernel

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

cache_clean_flush这个函数。这个函数的定义如下

 

cache_clean_flush:

              mov r3, #16

              b     call_cache_fn

其实这里又调用了call_cache_fn这个函数,注意,这里r3的值为16,上面对cache操作已经比较详细,不再讨论。

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

 

 

整个代码流程如下:

 

 

 

阅读更多
个人分类: ARM
想对作者说点什么? 我来说一句

linux启动oracle方法

2018年04月24日 590B 下载

详解嵌入式linux启动信息

2011年08月15日 6.49MB 下载

嵌入式linux启动信息完全注释

2009年03月17日 69KB 下载

嵌入式linux启动信息完全注释.doc

2010年04月19日 163KB 下载

arm linux启动资料,关于uboot等

2010年07月19日 1009KB 下载

ARM linux启动分析

2011年05月19日 98KB 下载

linux启动oracle方法及详细配置

2011年06月23日 19KB 下载

ARM Linux启动过程分析

2010年01月22日 46KB 下载

Linux启动更快速的十大秘诀

2011年01月26日 60KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭