内核启动

Posted 游戏进行中

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核启动相关的知识,希望对你有一定的参考价值。

  内核的实际起始函数为 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 (arch\\arm\\boot\\compressed) 下的 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 (arch\\arm\\include\\asm)中定义*/
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    \\reg , cpsr
 5     eor    \\reg, \\reg, #HYP_MODE
 6     tst    \\reg, #MODE_MASK
 7     bic    \\reg , \\reg , #MODE_MASK                            @ 将模式位M[4:0]清0
 8     /* 通过设置低 8 位为 110 10011,达到了关闭 IRQ、FIQ、设置 CPU 工作模式为 SVC 模式的目标 */
 9     orr    \\reg , \\reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
10 THUMB(    orr    \\reg , \\reg , #PSR_T_BIT    )
11     bne    1f
12     orr    \\reg, \\reg, #PSR_A_BIT
13     badr    lr, 2f
14     msr    spsr_cxsf, \\reg
15     __MSR_ELR_HYP(14)
16     __ERET
17 1:    msr    cpsr_c, \\reg
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, \\reg
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

以上是关于内核启动的主要内容,如果未能解决你的问题,请参考以下文章

Android小部件,启动一个片段?

scrapy按顺序启动多个爬虫代码片段(python3)

linux打开终端如何启动scala,如何在终端下运行Scala代码片段?

十个html5代码片段,超实用,一定要收藏

如何从片段适配器启动活动

在ViewPager上,在onPageSelected上的片段上启动动画