linux 内核解压缩一

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

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

1.保存boot下传递给内核的参数,并且关闭中断进入svc 模式

2.缓存和mmu的初始化;

3.内核的搬移;

4.内核的解压缩;

1.1 准备阶段 

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

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

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

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

  • 从启动加载项接收结构ID和atags信息
  • 禁用中断
  • 初始化寄存器,跳转到 not_relocated 标签
  1. 从 start 标签开始执行,共执行了 8 (rept 7 + 1) 次 "mov r0, r0" 指令(等同于 nop 指令),空出了 32 字节的用来存放 ARM 的中断向量表的位置,然后向前跳转到 "1" 标签处。使用.type标号来指明start的符号类型是函数类型,然后重复执行.rept到.endr之间的指令7次,这里一共执行了7次mov r0, r0指令,共占用了4*7 = 28个字节,这是用来存放ARM的异常向量表的。向前跳转到标号为1处执行

    start:
            .type    start,#function
            .rept    7
            __nop
            .endr

            mov    r0, r0
            W(b)    1f

  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 指针

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

1          /*
 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符号处执行。

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

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

arch/arm/include/asm/assembler.h

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

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

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

/* 字符段开始区域 */
        .text

#ifdef CONFIG_AUTO_ZRELADDR
        mov    r4, pc
        and    r4, r4, #0xf8000000
        /* Determine final kernel image address. */
        add    r4, r4, #TEXT_OFFSET
#else
        ldr    r4, =zreladdr
#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

 即 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

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值