zoukankan      html  css  js  c++  java
  • 三、内核启动(一)

      内核的实际起始函数为 start_kernel() 函数,然后再调用其他函数来执行启动。再调用此函数之前,需要先将通过编译内核获得的 zImage 进行解压,请按成页目录构建等基本任务。

      调用 start_kernel 的过程分为以下三个阶段:

    1. 解压内核映像 zImage 前的准备阶段,通过与处理器版本相符的处理器类型列表,执行打开/关闭/清除缓存等任务,为MMU构建16KB的页目录;
    2. 对 zImage 执行解压缩
    3. 检查处理器及机器信息、通过启动加载项获得 atag 信息的有效性,然后激活 MMU 调用内核的起始函数 start_kernel()。

    3.1 内核解压

    3.1.1 准备阶段

      解压缩准备阶段将执行中断禁用、分配动态内存、初始化BBS区域、初始化页目录、打开缓存等任务。

      在该阶段,zImage 解压位置的下级 16KB 构建用于保存页目录的空间,在CP15的c2寄存器中保存页目录的位置。

      ARM中,页目录将 4GB 的内存以 1MB 节区为单位进行管理。因此,为了管理 4GB 的内存,需要有 4096 个以 1MB为单位的项。由于以32位的字符为单位管理各项,所以共需要 16KB (4字节 X 4096各项 = 16KB)。之后,向相当于页目录位置的项设置 cacheable 和 bufferable,使页目录得到缓冲并能快速访问。

      从start 标签到解压缩准备阶段的流程图

      

    • 启动加载项必须提供5种功能
      • RAM初始化
      • 串行端口初始化
      • 查找机器类别
      • 构建  tagged list 内核
      • 将控制移交到内核镜像    

    3.1.1.1 进入启动加载后结束首个启动--start 标签

      通过加载项完成对软硬件的默认初始化后,最先执行的是 head.S (archarmootcompressed) 下的 start 标签中的代码。 完成的主要功能如下:

    • 从启动加载项接收结构ID和atags信息
    • 禁用中断
    • 初始化寄存器,跳转到 not_relocated 标签
    1. 从 start 标签开始执行,共执行了 8 (rept 7 + 1) 次 "mov r0, r0" 指令(等同于 nop 指令),空出了 32 字节的用来存放 ARM 的中断向量表的位置,然后跳转到 "1" 标签处。
    1 start:
    2         .type    start,#function
    3         .rept    7
    4         __nop
    5         .endr
    6 
    7         mov    r0, r0
    8         W(b)    1f

      使用.type标号来指明start的符号类型是函数类型,然后重复执行.rept到.endr之间的指令7次,这里一共执行了7次mov r0, r0指令,共占用了4*7 = 28个字节,这是用来存放ARM的异常向量表的。向前跳转到标号为1处执行

      2. 保存 cpsr 的值到 r9 中,保存架构 ID 和 atags 指针分别到 r7 和 r8 中。

    1 1:
    2  ARM_BE8(    setend    be        )    @ go BE8 if compiled for BE8
    3  AR_CLASS(    mrs    r9, cpsr    )
    4         /* 将启动加载项传递的结构ID和 atags 信息分别保存到寄存器 r7 r8 中 */
    5         mov    r7, r1            @ 保存结构ID
    6         mov    r8, r2            @ 保存 atags 指针

      当中还有未贴出来的代码,不相关的

      这里将CPU的工作模式保存到r9寄存器中,将uboot通过r1传入的机器码保存到r7寄存器中,将启动参数tags的地址保存到r8寄存器中。

    • CONFIG_ARM_VIRT_EXT 表明启用了 ARM 虚拟化扩展。
    • 从 bootloader 中接收了 3 个参数,分别为
      • R0 = 0
      • R1 = 架构 ID
      • R2 = atags 指针

      3.继续在标签“1”中运行,判断当前 CPU 的工作模式,若不是在用户模式下,则跳转到 "not_angel" 标签处,否则通过 swi 指令产生软中断异常的方式来进入 SVC 模式。

     1  2         /*
     3          * Booting from Angel - need to enter SVC mode and disable
     4          * FIQs/IRQs (numeric definitions from angel arm.h source).
     5          * We only do this if we were in user mode on entry.
     6          */
     7         mrs    r2, cpsr        @ 将CPSR状态寄存器读取,保存到R1中,即获取当前CPU模式
     8         tst    r2, #3            @ 判断CPU是否为用户模式
     9         bne    not_angel
    10         mov    r0, #0x17        @ angel_SWIreason_EnterSVC
    11  ARM(        swi    0x123456    )    @ angel_SWI_ARM
    12  THUMB(        svc    0xab        )    @ angel_SWI_THUMB

      这里将CPU的工作模式保存到r2寄存器中,然后判断是否是SVC模式,如果是USER模式就会通过swi指令产生软中断异常的方式来自动进入SVC模式。由于我这里在uboot中已经将CPU的模式设置为SVC模式了,所以就直接跳到not_angel符号处执行。

      4.借助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ中断,切换到 SVC 模式;将 r9 中保存的原来的 CPSR 的值保存到 SPSR 中。

    1  /* 设置CPU为SVC模式的具体操作 */
    2 not_angel:
    3         /* .macro safe_svcmode_maskall reg:req 在 Assembler.h (archarmincludeasm)中定义*/
    4         safe_svcmode_maskall r0
    5         msr    spsr_cxsf, r9        @ Save the CPU boot mode in
    6                         @ SPSR

      (1)借助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ中断,切换到 SVC 模式;将 r9 中保存的原来的 CPSR 的值保存到 SPSR 中

        arch/arm/include/asm/assembler.h

        这里的注释已经说明了,这里是强制将CPU的工作模式切换到SVC模式,并且关闭IRQ和FIQ中断。然后将r9中保存的原始CPU配置保存到SPSR中。

     1 /* 此处出现的 MODE_MASK、PSR_I_BIT 等常量被宏定义在 arch/arm/include/uapi/asm/ptrace.h */
     2 .macro safe_svcmode_maskall reg:req
     3 #if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
     4     mrs    
    eg , cpsr
     5     eor    
    eg, 
    eg, #HYP_MODE
     6     tst    
    eg, #MODE_MASK
     7     bic    
    eg , 
    eg , #MODE_MASK                            @ 将模式位M[4:0]清0
     8     /* 通过设置低 8 位为 110 10011,达到了关闭 IRQ、FIQ、设置 CPU 工作模式为 SVC 模式的目标 */
     9     orr    
    eg , 
    eg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
    10 THUMB(    orr    
    eg , 
    eg , #PSR_T_BIT    )
    11     bne    1f
    12     orr    
    eg, 
    eg, #PSR_A_BIT
    13     badr    lr, 2f
    14     msr    spsr_cxsf, 
    eg
    15     __MSR_ELR_HYP(14)
    16     __ERET
    17 1:    msr    cpsr_c, 
    eg
    18 2:
    19 #else
    20 /*
    21  * workaround for possibly broken pre-v6 hardware
    22  * (akita, Sharp Zaurus C-1000, PXA270-based)
    23  */
    24     setmode    PSR_F_BIT | PSR_I_BIT | SVC_MODE, 
    eg
    25 #endif
    26 .endm

      5.将内核解压地址(ZRELADDR)保存到 R4 中,依然在 "not_angel" 标签中运行

     1 /* 字符段开始区域 */
     2         .text
     3 
     4 #ifdef CONFIG_AUTO_ZRELADDR
     5         mov    r4, pc
     6         and    r4, r4, #0xf8000000
     7         /* Determine final kernel image address. */
     8         add    r4, r4, #TEXT_OFFSET
     9 #else
    10         ldr    r4, =zreladdr
    11 #endif

      内核配置项AUTO_ZRELDDR表示自动计算内核解压地址(Auto calculation of the decompressed kernelimage address),这里没有选择这个配置项,所以保存到r4中的内核解压地址就是zreladdr

      (1)定义了 CONFIG_AUTO_ZRELADDR, 将在运行时计算确定 ZRELADDR 

      ZRELADDR 的值为:

      1. 先是 pc 值和 0xf8000000 做与操作;

          注:此处与 0xf8000000 做 and 操作的原因样是我们默认 zImage 被放置的位置一定在距离 PHYS_OFFSET 的 128MB 之内。

      1. 再加上 TEXT_OFFSET(内核最终存放的物理地址与内存起始处之间的偏移)

          TEXT_OFFSET 定义如下所示:

          File: /arch/arm/Makefile

          

          此处的 textofs-y 定义如下所示:

           

          即 TEXT_OFFSET 的值为 0x00008000 = 32KB

          此处之所以加上 TEXT_OFFSET 这个 32KB 的值的原因如下图所示:

          

          PHY_OFFSET的值不一定为  0x60000000,根据硬件来确定。

      (2)未定义 CONFIG_AUTO_ZRELADDR 时,直接加载 zreladdr 到 R4 中

        zreladdr 的定义如下所示:

        File: /arch/arm/boot/compressed/Makefile
        

        ZERLADDR定义如下:

        File: /arch/arm/boot/Makefile

          

        看一下params_phys和initrd_phys的值,他们最终由arch/arm/mach-$(SOC)/Makefile.boot决定,我这里使用的soc是bcm2807(bcm2835),他的Makefile.boot内容如下:

        zreladdr-y            := 0x00008000

        params_phys-y         := 0x00000100

        initrd_phys-y        :=0x00800000

        params_phys-y和initrd_phys-y是内核参数的物理地址和initrd文件系统的物理地址。其实除了zreladdr外这些地址uboot都会传入的。

        这里的 zreladdr-y 定义在 /arch/arm/mach-xxx/Makefile.boot 中。

        比如所用的 2440

        

      这些地址都是通过uboot 传入进来的

      6.缓存和MMU初始化cache_on的执行流程

       这里将比较当前PC地址和内核解压地址,只有在不会自覆盖的情况下才会创建一个页表,如果当前运行地址PC < 解压地址 r4,则读取 LC0+32 地址处的内容加载到 r0 中,否则跳转到 cache_on 处执行缓存初始化和MMU初始化。
      代码如下,此处代码依然在 "not_angel" 标签中运行
    1         mov    r0, pc
    2         cmp    r0, r4
    3         ldrcc    r0, LC0+32
    4         addcc    r0, r0, pc
    5         cmpcc    r4, r0
    6         orrcc    r4, r4, #1        @ remember we skipped cache_on
    7         blcs    cache_on

      LC0的定义如下:

      

      LC0+32地址处的内容为:_end -restart + 16384 + 1024*1024,所指的就是程序长度+16k的页表长+1M的DTB空间。

      继续比较解压地址r4(0x00008000)和当前运行程序的(结束地址+16384 + 1024*1024),如果小于则不进行缓存初始化并置位r4最低位进行标识。 

      分情况总结一下:

      (1)      PC >= r4:直接进行缓存初始化

      (2)      PC < r4 && _end + 16384+ 1024*1024 > r4:不进行缓存初始化

      (3)      PC < r4 && _end + 16384+ 1024*1024 <= r4:执行缓存初始化

      cache on 开始执行:

      

     1 /*
     2  * Turn on the cache.  We need to setup some page tables so that we
     3  * can have both the I and D caches on.
     4  *
     5  * We place the page tables 16k down from the kernel execution address,
     6  * and we hope that nothing else is using it.  If we're using it, we
     7  * will go pop!
     8  *
     9  * On entry,
    10  *  r4 = kernel execution address
    11  *  r7 = architecture number
    12  *  r8 = atags pointer
    13  * On exit,
    14  *  r0, r1, r2, r3, r9, r10, r12 corrupted
    15  * This routine must preserve:
    16  *  r4, r7, r8
    17  */
    18         .align    5
    19 cache_on:    mov    r3, #8            @ cache_on function
    20         b    call_cache_fn

      注释中说明了,为了开启I Cache和D Cache,需要建立页表(开启MMU),而页表使用的就是内核运行地址以下的16KB空间(对于我的环境来说地址就等于0x00004000~0x00008000)。同时在运行的过程中r0~r3以及r9、r10和r12寄存器会被使用。

     这里首先在r3中保存打开缓存函数表项在cache操作表中的地址偏移(这里为8,cache操作表见后文),然后跳转到call_cache_fn中。 
     1 /*
     2  * Here follow the relocatable cache support functions for the
     3  * various processors.  This is a generic hook for locating an
     4  * entry and jumping to an instruction at the specified offset
     5  * from the start of the block.  Please note this is all position
     6  * independent code.
     7  *
     8  *  r1  = corrupted
     9  *  r2  = corrupted
    10  *  r3  = block offset
    11  *  r9  = corrupted
    12  *  r12 = corrupted
    13  */
    14 
    15 call_cache_fn:    adr    r12, proc_types
    16 #ifdef CONFIG_CPU_CP15
    17         mrc    p15, 0, r9, c0, c0    @ get processor ID
    18 #elif defined(CONFIG_CPU_V7M)
    19         /*
    20          * On v7-M the processor id is located in the V7M_SCB_CPUID
    21          * register, but as cache handling is IMPLEMENTATION DEFINED on
    22          * v7-M (if existant at all) we just return early here.
    23          * If V7M_SCB_CPUID were used the cpu ID functions (i.e.
    24          * __armv7_mmu_cache_{on,off,flush}) would be selected which
    25          * use cp15 registers that are not implemented on v7-M.
    26          */
    27         bx    lr
    28 #else
    29         ldr    r9, =CONFIG_PROCESSOR_ID
    30 #endif
    31 1:        ldr    r1, [r12, #0]        @ get value
    32         ldr    r2, [r12, #4]        @ get mask
    33         eor    r1, r1, r9        @ (real ^ match)
    34         tst    r1, r2            @       & mask
    35  ARM(        addeq    pc, r12, r3        ) @ call cache function
    36  THUMB(        addeq    r12, r3            )
    37  THUMB(        moveq    pc, r12            ) @ call cache function
    38         add    r12, r12, #PROC_ENTRY_SIZE
    39         b    1b

      首先保存cache操作表的运行地址到r12寄存器中,proc_types定义在head.s中:

     1 /*
     2  * Table for cache operations.  This is basically:
     3  *   - CPU ID match
     4  *   - CPU ID mask
     5  *   - 'cache on' method instruction
     6  *   - 'cache off' method instruction
     7  *   - 'cache flush' method instruction
     8  *
     9  * We match an entry using: ((real_id ^ match) & mask) == 0
    10  *
    11  * Writethrough caches generally only need 'on' and 'off'
    12  * methods.  Writeback caches _must_ have the flush method
    13  * defined.
    14  */
    15         .align    2
    16         .type    proc_types,#object

      表中的每一类处理器都包含以下5项(如果不存在缓存操作函数则使用“mov  pc, lr”占位):

      (1)      CPU ID

      (2)      CPU ID 位掩码(用于匹配CPU类型用)

      (3)      打开缓存“cache on”函数入口

      (4)      关闭缓存“cache off”函数入口

      (5)      刷新缓存“cache flush”函数入口

      我所用的CPU为ARM920T的 S3C2440,为ARMV4T架构,一般架构如下图:

      

      对应的代码为:

      

      若配置了CPU_CP15条件编译项,所以这里将从CP15中获取CPU型号而不是从内核配置项中获取。

      然后逐条对cache操作表中的CPU类型进行匹配,如果匹配上了就跳转到相应的函数入口执行。

         遍历 proc_types 列表,查找想对应的处理器类型,找到之后 pc = r12 + r3,r3 中存储的是常数 8,即 pc 指向了相对应的 cache on 子例程。执行如下

     1 call_cache_fn:    adr    r12, proc_types
     2 #ifdef CONFIG_CPU_CP15
     3         mrc    p15, 0, r9, c0, c0    @ get processor ID
     4 #elif defined(CONFIG_CPU_V7M)
     5         /*
     6          * On v7-M the processor id is located in the V7M_SCB_CPUID
     7          * register, but as cache handling is IMPLEMENTATION DEFINED on
     8          * v7-M (if existant at all) we just return early here.
     9          * If V7M_SCB_CPUID were used the cpu ID functions (i.e.
    10          * __armv7_mmu_cache_{on,off,flush}) would be selected which
    11          * use cp15 registers that are not implemented on v7-M.
    12          */
    13         bx    lr
    14 #else
    15         ldr    r9, =CONFIG_PROCESSOR_ID
    16 #endif
    17 1:        ldr    r1, [r12, #0]        @ get value
    18         ldr    r2, [r12, #4]        @ get mask
    19         eor    r1, r1, r9        @ (real ^ match)
    20         tst    r1, r2            @       & mask
    21  ARM(        addeq    pc, r12, r3        ) @ call cache function
    22  THUMB(        addeq    r12, r3            )
    23  THUMB(        moveq    pc, r12            ) @ call cache function
    24         add    r12, r12, #PROC_ENTRY_SIZE
    25         b    1b

      代码注释已经很清楚,之后调用 cache  函数,对应 2440 则调用函数:__armv4_mmu_cache_on

  • 相关阅读:
    IDEA解决Cannot download sources的问题
    Swagger在Springboot项目中的使用
    ElasticSearch(10)—SpringBoot集成ES
    ElasticSearch(9)---Rest风格
    ElasticSearch(8)---IK分词器
    js显示原型和隐示原型
    通俗易懂讲解为什么设计稿都是750px
    关于rem和px全局设置问题
    PHP RSA密文过长加密解密 越过1024的解决代码
    使用https,$_SERVER['HTTPS']却不等于on?
  • 原文地址:https://www.cnblogs.com/kele-dad/p/8546603.html
Copyright © 2011-2022 走看看