ARM Linux 3.10.61 的启动 —— (一)解压缩阶段

/arch/arm/boot/compressed/head.S 的分析

1. 从 start 标签开始执行,共执行了 8 (rept 7 + 1) 次 "mov r0, r0" 指令(等同于 nop 指令),空出了 32 字节的用来存放 ARM 的中断向量表的位置,然后跳转到 "1" 标签处。

File: /arch/arm/boot/compressed/head.S
123|start:
124|        .type    start,#function
125|        .rept    7
126|        mov    r0, r0
127|        .endr
128|   ARM(        mov    r0, r0        )
129|   ARM(        b    1f        )
130| THUMB(        adr    r12, BSYM(1f)    )
131| THUMB(        bx    r12        )

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

File: /arch/arm/boot/compressed/head.S
137|1:
138|        mrs    r9, cpsr
139|#ifdef CONFIG_ARM_VIRT_EXT
140|        bl    __hyp_stub_install    @ get into SVC mode, reversibly
141|#endif
142|        mov    r7, r1            @ save architecture ID
143|        mov    r8, r2            @ save atags pointer

注:

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

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

File: /arch/arm/boot/compressed/head.S
151|        mrs    r2, cpsr        @ get current mode
152|        tst    r2, #3            @ not user?
153|        bne    not_angel
154|        mov    r0, #0x17        @ angel_SWIreason_EnterSVC
155| ARM(        swi    0x123456    )    @ angel_SWI_ARM
156| THUMB(        svc    0xab        )    @ angel_SWI_THUMB

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

File: /arch/arm/boot/compressed/head.S
157|not_angel:
158|        safe_svcmode_maskall r0
159|        msr    spsr_cxsf, r9        @ Save the CPU boot mode in
160|                        @ SPSR

4.1 safe_svcmode_maskall 宏,具体完成 CPU 工作模式切换为 SVC 模式,并且屏蔽 IRQ 和 FIQ 中断的工作。

File: /arch/arm/include/asm/assembler.h
250|.macro safe_svcmode_maskall reg:req
251|#if __LINUX_ARM_ARCH__ >= 6
252|    mrs    \reg , cpsr
253|    eor    \reg, \reg, #HYP_MODE
254|    tst    \reg, #MODE_MASK
255|    bic    \reg , \reg , #MODE_MASK
256|    orr    \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
257|THUMB(    orr    \reg , \reg , #PSR_T_BIT    )
258|    bne    1f
259|    orr    \reg, \reg, #PSR_A_BIT
260|    adr    lr, BSYM(2f)
261|    msr    spsr_cxsf, \reg
262|    __MSR_ELR_HYP(14)
263|    __ERET
264|1:    msr    cpsr_c, \reg
265|2:
266|#else
267|/*
268| * workaround for possibly broken pre-v6 hardware
269| * (akita, Sharp Zaurus C-1000, PXA270-based)
270| */
271|    setmode    PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg
272|#endif
273|.endm

关键有两点:

  1. 将模式位 M[4:0] 清 0
File: /arch/arm/include/asm/assembler.h
255|    bic    \reg , \reg , #MODE_MASK
  1. 通过设置低 8 位为 110 10011,达到了关闭 IRQ、FIQ、设置 CPU 工作模式为 SVC 模式的目标:
File: /arch/arm/include/asm/assembler.h
256|    orr    \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE

注:此处出现的 MODE_MASK、PSR_I_BIT 等常量被宏定义在 /arch/arm/include/uapi/asm/ptrace.h 中。

TODO 在这个宏中涉及到 Hyper 态(HYP_MODE),暂且略过,可参考 http://blog.csdn.net/crosskernel/article/details/21091819

5. 将内核解压地址(ZRELADDR)保存到 R4 中

File: /arch/arm/boot/compressed/head.S
177|#ifdef CONFIG_AUTO_ZRELADDR
178|        @ determine final kernel image address
179|        mov    r4, pc
180|        and    r4, r4, #0xf8000000
181|        add    r4, r4, #TEXT_OFFSET
182|#else
183|        ldr    r4, =zreladdr
184|#endif

可以看到这取决于是否定义了 CONFIG_AUTO_ZRELADDR

5.1 定义了 CONFIG_AUTO_ZRELADDR, 将在运行时计算确定 ZRELADDR

ZRELADDR 的值为:

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

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

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

TEXT_OFFSET 定义如下所示:

File: /arch/arm/Makefile
216|# The byte offset of the kernel image in RAM from the start of RAM.
217|TEXT_OFFSET := $(textofs-y)

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

File: /arch/arm/Makefile
126|textofs-y    := 0x00008000

TEXT_OFFSET 的值为 0x00008000 = 32KB

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


ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第1张图片
Vexpress 下的内核地址空间布局

5.2 未定义 CONFIG_AUTO_ZRELADDR 时,直接加载 zreladdr 到 R4 中

zreladdr 的定义如下所示:

File: /arch/arm/boot/compressed/Makefile
136|LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)

此处的 ZRELADDR 定义如下所示:

File: /arch/arm/boot/Makefile
22|ZRELADDR    := $(zreladdr-y)

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

注:linux 3.10.61,在 mach-vexpress 文件夹下无 Makefile.boot 文件,zreladdr-y := 0x60008000 已被移除,参考 ARM: removed unused zreladdr specifications in all Makefile.boot files

6. 开启缓存(以及 MMU)

File: /arch/arm/boot/compressed/head.S
186|        bl    cache_on

6.1 cache_on

File: /arch/arm/boot/compressed/head.S
543|        .align    5
544|cache_on:    mov    r3, #8            @ cache_on function
545|        b    call_cache_fn

此处将常数 8 保存到 R3 中(该常数将被用作偏移量,指向了 proc_types 列表各项的 "cache on subroutine" 的起始地址),之后跳转到 call_cache_fn 标签。

6.2 call_cache_fn

  1. 将 proc_types 标签的起始地址保存到 R12 中
File: /arch/arm/boot/compressed/head.S
776|call_cache_fn:    adr    r12, proc_types

摘取部分 proc_types 的代码如下所示:

File: /arch/arm/boot/compressed/head.S
806|        .align    2
807|        .type    proc_types,#object
808|proc_types:
809|        .word    0x41000000        @ old ARM ID
810|        .word    0xff00f000
811|        mov    pc, lr
812| THUMB(        nop                )
813|        mov    pc, lr
814| THUMB(        nop                )
815|        mov    pc, lr
816| THUMB(        nop                )
    ……
934|        .word    0x000f0000        @ new CPU Id
935|        .word    0x000f0000
936|        W(b)    __armv7_mmu_cache_on
937|        W(b)    __armv7_mmu_cache_off
938|        W(b)    __armv7_mmu_cache_flush
939|
940|        .word    0            @ unrecognised type
941|        .word    0
942|        mov    pc, lr
943| THUMB(        nop                )
944|        mov    pc, lr
945| THUMB(        nop                )
946|        mov    pc, lr
947| THUMB(        nop                )
948|
949|        .size    proc_types, . - proc_types

可见,proc_types 列表中的每一项的结构如下图所示:


ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第2张图片
proc_types 列表
  1. 获取处理器 ID
File: /arch/arm/boot/compressed/head.S
777|#ifdef CONFIG_CPU_CP15
778|        mrc    p15, 0, r9, c0, c0    @ get processor ID
779|#else
780|        ldr    r9, =CONFIG_PROCESSOR_ID
781|#endif

如果定义了 CONFIG_CPU_CP15,则表明具有协处理器 CP15,那么将从 CP15 中获取处理器 ID,否则从 CONFIG_PROCESSOR_ID 中获取。

重点关注一下这条指令:

File: /arch/arm/boot/compressed/head.S
778|        mrc    p15, 0, r9, c0, c0    @ get processor ID
  • MRC 语法:mrc{cond} coproc, #opcode1, Rd, CRn, CRm{, #opcode2}

    • cond: 可选的条件码;
    • coproc: 要运行指令的协处理器的名称,标准名称为 pn,其中 n 为 0 到 15 范围内的整数;
    • opcode1: 特定于协处理器的 3 位操作码;
    • Rd: 作为目标 ARM 寄存器,不能是 R15;
    • CRn: 存放第1个操作数的协处理器寄存器;
    • CRm: 存放第2个操作数的协处理器寄存器;
    • opcode2: 特定于协处理器的可选 3 位操作码。
      注: 若可选的 opcode2 不出现,则默认当作 0 处理。
  • 查阅 ARM 架构手册(B4-1649),可知上述指令是将 MIDR(Main ID Register) 的值复制到 R9 中

MRC p15, 0, , c0, c0, 0 ; Read MIDR into Rt
  • MIDR 的位分配如下图所示:


    MIDR bit assignment
  • 其中 Architecture, bits[19:16] 可取值如下表所示:

Bits[19:16] Architecture
0x1 ARMv4
0x2 ARMv4T
0x3 ARMv5(obsolete)
0x4 ARMv5T
0x5 ARMv5TE
0x6 ARMv5TEJ
0x7 ARMv6
0xF Defined by CPUID scheme
others reserved

特别的,对于 ARMv7,手册中有如下说明:

For an ARMv7 implementation by ARM, the MIDR is interpreted as:
……
Bits[19:16] Architecture code, must be 0xF .

  1. 遍历 proc_types 列表,查找想对应的处理器类型,找到之后 pc = r12 + r3,r3 中存储的是常数 8,即 pc 指向了相对应的 cache on 子例程。
File: /arch/arm/boot/compressed/head.S
782|1:        ldr    r1, [r12, #0]        @ get value
783|        ldr    r2, [r12, #4]        @ get mask
784|        eor    r1, r1, r9        @ (real ^ match)
785|        tst    r1, r2            @       & mask
786| ARM(        addeq    pc, r12, r3        ) @ call cache function
787| THUMB(        addeq    r12, r3            )
788| THUMB(        moveq    pc, r12            ) @ call cache function
789|        add    r12, r12, #PROC_ENTRY_SIZE
790|        b    1b

6.3 __armv7_mmu_cache_on (以 ARMv7 为例)

  1. 读取 ID_MMFR0 的内容到 R11 中,检测是否支持 VMSA,若支持则先将 #CB_BITS | 0x02 的值保存到 R6 中,再跳转到 __setup_mmu 标签处
File: /arch/arm/boot/compressed/head.S
691| __armv7_mmu_cache_on:
692|         mov    r12, lr
693| #ifdef CONFIG_MMU
694|         mrc    p15, 0, r11, c0, c1, 4    @ read ID_MMFR0
695|         tst    r11, #0xf        @ VMSA
696|         movne    r6, #CB_BITS | 0x02    @ !XN
697|         blne    __setup_mmu

查阅 ARM 手册(B4-1621) 可知上述 MRC 指令的作用:

MRC p15, 0, , c0, c1, 4 ; Read ID_MMFR0 into Rt
  • ID_MMFR0(Memory Model Feature Register 0) 的位分配下图所示:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第3张图片
    ID_MMFR0 bit assignment
  • VMSA support, bits[3:0] 可取值如下表所示:

bits[3:0] VMSA support
0b0000 Not supported
0b0001 Support for IMPLEMENTATION DEFINED VMSA
0b0010 Support for VMSAv6, with Cache and TLB Type Registers implemented
0b0011 Support for VMSAv7, with support for remapping and the Access flag. ARMv7-A profile.
0b0100 As for 0b0011 , and adds support for the PXN bit in the Short-descriptor translation table format descriptors
0b0101 As for 0b0100 , and adds support for the Long-descriptor translation table format

Note: When the VMSA support field is set to a value other than 0b0000 the PMSA support field must be set to 0b0000 .

  • 可知,支持 VMSA 时, bits[3:0] 不是 0000,故执行完 tst 指令后,将不会设置 Z 标记,接下来将执行 movne 修改 R6 的值,然后执行 blne 指令跳转到 __setup_mmu 标签处。

  • CB_BITS 的定义:
    可知,若配置了 CPU D-cache 为直写方式,则 CB_BITS 的值是 0x08;否则(回写方式)是 0x0c.

File: /arch/arm/boot/compressed/head.S
609| #ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
610| #define CB_BITS 0x08
611| #else
612| #define CB_BITS 0x0c
613| #endif

则 R6 = 0xA (CPU_DCACHE_WRITETHROUGH) 或 R6 = 0xE (CPU_DCACHE_WRITEBACK)

2) 详解 __setup_mmu 例程

1] 给页表留出空间,将页表的起始地址保存到 R3 中

File: /arch/arm/boot/compressed/head.S
615| __setup_mmu:    sub    r3, r4, #16384        @ Page directory size

注: R4 中保存的内容是 ZRELADDR, 参见上文。

要求达到的内存布局如下图所示:


ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第4张图片
ZRELADDR page_base
  • 常数 #16384 的由来:
    32 位的 RAM 系统,寻址空间为 4GB,此处每一个页表项代表 1MB,则需要 4096 个页表项。同时,每一个页表项的大小为 4Byte,那么就需要 4096 * 4Byte = 16384Byte = 16KB 的空间。

  • 此时页表项的每项对应 1MB 的内存空间,其格式为 段(section)页表项 ,如下图所示:

    section 页表项格式

    图中 bit4 XN 为不可执行位, bit3 C 为 cacheable,bit2 B 为 bufferable

注: L1页表项的格式有 4 种,分别为 Fault 页表项、Section 页表项、Page Table 页表项和 Supersection 页表项。详细内容参考 ARM 手册,B3-1326 Figure B3-4.

2] 对齐页表的起始地址

File: /arch/arm/boot/compressed/head.S
616|         bic    r3, r3, #0xff        @ Align the pointer
617|         bic    r3, r3, #0x3f00

经过两次 bit 指令, R3 中的值的低 14 位均为0,实际上是对齐到了 16KB 边界。

3] R0 = R3; R9 保存的实际是 R3 的高 14 位的内容,低 18 位全为0,对齐到了 256MB 的边界; R10 = R9 + 256MB.

File: /arch/arm/boot/compressed/head.S
622|         mov    r0, r3
623|         mov    r9, r0, lsr #18
624|         mov    r9, r9, lsl #18        @ start of RAM
625|         add    r10, r9, #0x10000000    @ a reasonable RAM size

执行完 lsr #18 和 lsl #18 之后,R9 中保存的内容是 PHYS_OFFSET.

4] R1 = 0xC12, R2 = ZRELADDR

File: /arch/arm/boot/compressed/head.S
626|         mov    r1, #0x12        @ XN|U + section mapping
627|         orr    r1, r1, #3 << 10    @ AP=11
628|         add    r2, r3, #16384

5] 比较 R1 和 R9,若 R1 >= R9,则继续比较 R10 和 R1,之后 R1 = 0xC02

File: /arch/arm/boot/compressed/head.S
629| 1:        cmp    r1, r9            @ if virt > start of RAM
630|         cmphs    r10, r1            @   && end of RAM > virt
631|         bic    r1, r1, #0x1c        @ clear XN|U + C + B

6] 若 line 630 的 cmphs 后的结果是 R10 < R1,则执行 orrlo,R1 = 0xC12; 否则执行 orrhs,R1 = 0xC0A (CPU_DCACHE_WRITETHROUGH) 或 R1 = 0xC0E (CPU_DCACHE_WRITEBACK).

File: /arch/arm/boot/compressed/head.S
632|         orrlo    r1, r1, #0x10        @ Set XN|U for non-RAM
633|         orrhs    r1, r1, r6        @ set RAM section settings

这里 5] 和 6] 看起来比较乱,实际上首次循环的流程如下图所示:


ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第5张图片
__setup_mmu_first_loop

7] 通过循环逐一映射 4096 项的段页表项,对其中的 R9-R10 之间的这 256MB 设置 Cacheable,若是配置了 “回写” 还需再设置 Bufferable.

File: /arch/arm/boot/compressed/head.S
634|         str    r1, [r0], #4        @ 1:1 mapping
635|         add    r1, r1, #1048576
636|         teq    r0, r2
637|         bne    1b

注:

  • str r1, [r0], #4 是先执行 [R0] = R1,再执行 R0 += 4.
  • 执行完此处的循环后,R1 = 0xC12,R0 = ZRELADDR

执行完上述循环后,在 vexpress 平台上将会有如下的 段页表项-物理内存 的映射关系:


ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第6张图片
Vexpress page-phyaddr

注:由上图可知,在 vexpress 下 R9-R10 之间的 256MB 对应 0x6000,0000 - 0x6FFF,FFFF.

8] R1 = (当前 PC 值进行 1MB 对齐后的值 | 0xC0E) = 0x???0,0C0E; R2 = 0x0000,0???

File: /arch/arm/boot/compressed/head.S
644|         orr    r1, r6, #0x04        @ ensure B is set for this
645|         orr    r1, r1, #3 << 10
646|         mov    r2, pc
647|         mov    r2, r2, lsr #20
648|         orr    r1, r1, r2, lsl #20

9] 实际上是重新映射了当前 PC 值向下对齐到 1MB 边界处向上 2MB 的内存范围设置属性为 0xC0E.

File: /arch/arm/boot/compressed/head.S
649|         add    r0, r3, r2, lsl #2
650|         str    r1, [r0], #4
651|         add    r1, r1, #1048576
652|         str    r1, [r0]
653|         mov    pc, lr
654| ENDPROC(__setup_mmu)

__setup_mmu 到此结束


  1. 一些必需的使能 MMU 之前的准备工作;
    1] 清除(Clean and Invalidate)数据缓存行寄存器;验证若支持 VMSA,则刷新 I,D TLB.
File: /arch/arm/boot/compressed/head.S
698|         mov    r0, #0
699|         mcr    p15, 0, r0, c7, c10, 4    @ drain write buffer
700|         tst    r11, #0xf        @ VMSA
701|         mcrne    p15, 0, r0, c8, c7, 0    @ flush I,D TLBs
702| #endif

2] 读取 SCTLR(System Control Register) 到 R0 中,处理后重新加载到 SCTLR 中。这里处理完成之后,通过加载 R12 到 PC 中,返回到 "bl cache_on" 的下一行指令。

703|         mrc    p15, 0, r0, c1, c0, 0    @ read control reg
704|         bic    r0, r0, #1 << 28    @ SCTLR.TRE = 0, TEX remap disabled.
705|         orr    r0, r0, #0x5000        @ I-cache enable, RR cache replacement
706|         orr    r0, r0, #0x003c        @ write buffer
707|         bic    r0, r0, #2        @ A (no unaligned access fault)
708|         orr    r0, r0, #1 << 22    @ U (v6 unaligned access model)
709|                         @ (needed for ARM1176)
710| #ifdef CONFIG_MMU
711| #ifdef CONFIG_CPU_ENDIAN_BE8
712|         orr    r0, r0, #1 << 25    @ big-endian page tables
713| #endif
714|         mrcne   p15, 0, r6, c2, c0, 2   @ read ttb control reg
715|         orrne    r0, r0, #1        @ MMU enabled
716|         movne    r1, #0xfffffffd        @ domain 0 = client
717|         bic     r6, r6, #1 << 31        @ 32-bit translation system
718|         bic     r6, r6, #3 << 0         @ use only ttbr0
719|         mcrne    p15, 0, r3, c2, c0, 0    @ load page table pointer
720|         mcrne    p15, 0, r1, c3, c0, 0    @ load domain access control
721|         mcrne   p15, 0, r6, c2, c0, 2   @ load ttb control
722| #endif
723|         mcr    p15, 0, r0, c7, c5, 4    @ ISB
724|         mcr    p15, 0, r0, c1, c0, 0    @ load control register
725|         mrc    p15, 0, r0, c1, c0, 0    @ and read it back
726|         mov    r0, #0
727|         mcr    p15, 0, r0, c7, c5, 4    @ ISB
728|         mov    pc, r12
  • SCTLR 的位分配如下图所示,各个 bit 具体的信息参阅 ARM 手册 B4-1705:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第7张图片
    SCTLR_bit_assignment

7. 加载 LC0 表的内容到各个指定的寄存器中

File: /arch/arm/boot/compressed/head.S
188| restart:    adr    r0, LC0
189|         ldmia    r0, {r1, r2, r3, r6, r10, r11, r12}
190|         ldr    sp, [r0, #28]

LC0 的定义如下:

File: /arch/arm/boot/compressed/head.S
506|         .align    2
507|         .type    LC0, #object
508| LC0:        .word    LC0         @ r1, LC0 的链接地址
509|         .word    __bss_start     @ r2, bss 段的起始地址
510|         .word    _end            @ r3, bss 段的结束地址
511|         .word    _edata          @ r6, zImage 结束的位置,其值与 _end 相同
512|         .word    input_data_end - 4   @ r10, 保存“解压后的内核的大小”这个值的位置
513|         .word    _got_start      @ r11, GOT(全局偏移表)的起始地址
514|         .word    _got_end        @ ip,  GOT 的结束地址
515|         .word    .L_user_stack_end    @ sp,临时分配的 4KB 的栈的栈底
516|         .size    LC0, . - LC0
  • __bss_start、_end、_edata、_got_start、_got_end 均是在 /arch/arm/boot/compressed/vmlinux.lds.in 中被定义。

  • input_data_end 定义如下所示:

File: /arch/arm/boot/compressed/piggy.gzip.S
1|     .section .piggydata,#alloc
2|     .globl    input_data
3| input_data:
4|     .incbin    "arch/arm/boot/compressed/piggy.gzip"
5|     .globl    input_data_end
6| input_data_end:
  • L_user_stack_end 标签定义如下,是长度为 4KB 的栈的结束地址:
File: /arch/arm/boot/compressed/head.S
1271|         .align
1272|         .section ".stack", "aw", %nobits
1273| .L_user_stack:    .space    4096
1274| .L_user_stack_end:
  • 根据 compressed/vmlinux.lds,可得下图:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第8张图片
    zImage中各个段的位置

8. 计算出链接时的地址和运行时的地址之间的偏移量保存在 R0 中,据此修正 R6 和 R10 的值。

File: /arch/arm/boot/compressed/head.S
196|         sub    r0, r0, r1      @ calculate the delta offset
197|         add    r6, r6, r0      @ _edata
198|         add    r10, r10, r0    @ inflated kernel size location

注: 此处计算出来被保存在 R0 中的值实际上便就是 zImage 被放置的地方的起始地址。

9. 逐字节读取”解压后的内核的大小“保存到 R9 中(这是为了兼容使用大端编译的 ARM)。

File: /arch/arm/boot/compressed/head.S
205|         ldrb    r9, [r10, #0]
206|         ldrb    lr, [r10, #1]
207|         orr    r9, r9, lr, lsl #8
208|         ldrb    lr, [r10, #2]
209|         ldrb    r10, [r10, #3]
210|         orr    r9, r9, lr, lsl #16
211|         orr    r9, r9, r10, lsl #24
  • 被压缩的内核的后面有以小端序保存的解压后的内核的大小(参见如下在代码中的注释所述):
File: /arch/arm/boot/compressed/head.S
200|         /*
201|          * The kernel build system appends the size of the
202|          * decompressed kernel at the end of the compressed data
203|          * in little-endian form.
204|          */

10. 是在 RAM 中运行的,不会跳入 #else 分支;这里首先根据 R0 中保存的偏移量来修正 sp 的值;之后在 sp 的基础上再向上开辟了 64KB 的堆栈空间用于解压内核时的临时缓冲区;再令 R5 = 0.

File: /arch/arm/boot/compressed/head.S
213| #ifndef CONFIG_ZBOOT_ROM
214|         /* malloc space is above the relocated stack (64k max) */
215|         add    sp, sp, r0
216|         add    r10, sp, #0x10000
217| #else
    ……
223|         mov    r10, r6
224| #endif
225| 
226|         mov    r5, #0            @ init dtb size to 0
  • 此时在 vexpress 下的内存布局如下图所示:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第9张图片
    Vexpress下RAM_zImage

注:默认不配置 CONFIG_ARM_APPENDED_DTB,跳过 line 227 - line 322 的内容。

11. 检测两种不会发生覆盖的情形

  1. ZREADDR >= zImage + 16KB.(这里通过 R10 += 16KB,确保了 16KB 的页表不会被覆盖,即:页表的起始地址 >= zImage 的结束地址)
File: /arch/arm/boot/compressed/head.S
333|         add    r10, r10, #16384   @ r10 += 16KB
334|         cmp    r4, r10
335|         bhs    wont_overwrite

此种情况如下图所示:


ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第10张图片
first_wont_overwrite2
  1. 解压后的内核的结束地址 <= wont_overwrite 标签的地址。(此时 R10 = 解压内核的结束地址)
File: /arch/arm/boot/compressed/head.S
336|         add    r10, r4, r9
337|         adr    r9, wont_overwrite
338|         cmp    r10, r9
339|         bls    wont_overwrite

此种情况如下图所示:


ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第11张图片
second_wont_overwrite

12. 若发生了覆盖则不会跳转到 wont_overwrite,向后执行进行代码的自搬移和重定位。

  1. R10 = 解压后的内核的结束地址 + [ (reloc_code_end - restart) 向上 256 字节对齐],再进行 256 字节对齐。
File: /arch/arm/boot/compressed/head.S
353|         add    r10, r10, #((reloc_code_end - restart + 256) & ~255)
354|         bic    r10, r10, #255
  • 这里是把 R10 增加了一部分,即解压后的内核的结束地址增大;这是为了避免当 偏移(这里的偏移指 zImage 的起始地址 和 解压后的内核的结束位置) 过小时造成覆写自身。此处若不对 R10 进行此番操作的话,下图所示情形便有可能造成覆写自身:
    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第12张图片
    first_up_r10
  1. 选定了复制范围,进行 32byte 对齐,保存到了 R9 中;之后再根据 R10,更新 R9 的值。
File: /arch/arm/boot/compressed/head.S
356|         /* Get start of code we want to copy and align it down. */
357|         adr    r5, restart
358|         bic    r5, r5, #31
    ……
374|        sub    r9, r6, r5        @ size to copy
375|        add    r9, r9, #31        @ rounded up to a multiple
376|        bic    r9, r9, #31        @ ... of 32 bytes
377|        add    r6, r9, r5
378|        add    r9, r9, r10

注: 此处暂且跳过 line-361 到 line-372 的配置 CONFIG_ARM_VIRT_EXT 后才执行的代码。

  • 之所以这里进行 32byte 对齐是因为在复制时的每次循环复制 32byte(使用 8 个寄存器, 8 * 4byte = 32byte)

  • 执行完上述代码后内存空间布局如下图所示:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第13张图片
    before_relocate
  1. 通过循环进行复制 R6-R5 范围内的内容到 R9-R10 之间;
File: /arch/arm/boot/compressed/head.S
379| 
380|1:      ldmdb    r6!, {r0 - r3, r10 - r12, lr}
381|        cmp    r6, r5
382|        stmdb    r9!, {r0 - r3, r10 - r12, lr}
383|        bhi    1b
  • 执行完循环后,内存布局如下所示:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第14张图片
    after_relocate
  1. 计算 "relocate_from 的起始地址"(R6) 到 "relocate_to 的起始地址"(R9) 之间的偏移量保存到 R6 中;并据此修改 SP 的值,从而完成了对栈的重定位。
File: /arch/arm/boot/compressed/head.S
385|        /* Preserve offset to relocated code. */
386|        sub    r6, r9, r6
387| 
388| #ifndef CONFIG_ZBOOT_ROM
389|         /* cache_clean_flush may use the stack, so relocate it */
390|         add    sp, sp, r6
391| #endif
  • 如下图所示:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第15张图片
    correct_sp
  1. 调用 cache_clean_flush 刷新缓存。
File: /arch/arm/boot/compressed/head.S
393|         bl    cache_clean_flush

1] 根据上文中提到的 proc_types 列表,可知此处在 R3 中所需保存的偏移量确实是 16,之后跳转到 call_cache_fn 标签,之后跳转到 __armv7_mmu_cache_flush.

File: /arch/arm/boot/compressed/head.S
1031| cache_clean_flush:
1032|         mov    r3, #16
1033|         b    call_cache_fn

2] __armv7_mmu_cache_flush

File: /arch/arm/boot/compressed/head.S
1067| __armv7_mmu_cache_flush:
1068|         mrc    p15, 0, r10, c0, c1, 5    @ read ID_MMFR1
1069|         tst    r10, #0xf << 16        @ hierarchical cache (ARMv7)
1070|         mov    r10, #0
1071|         beq    hierarchical
1072|         mcr    p15, 0, r10, c7, c14, 0    @ clean+invalidate D
1073|         b    iflush
1074| hierarchical:
1075|         mcr    p15, 0, r10, c7, c10, 5    @ DMB
1076|         stmfd    sp!, {r0-r7, r9-r11}
1077|         mrc    p15, 1, r0, c0, c0, 1    @ read clidr
1078|         ands    r3, r0, #0x7000000    @ extract loc from clidr
1079|         mov    r3, r3, lsr #23        @ left align loc bit field
1080|         beq    finished        @ if loc is 0, then no need to clean
1081|         mov    r10, #0            @ start clean at cache level 0
1082| loop1:
1083|         add    r2, r10, r10, lsr #1    @ work out 3x current cache level
1084|         mov    r1, r0, lsr r2        @ extract cache type bits from clidr
1085|         and    r1, r1, #7        @ mask of the bits for current cache only
1086|         cmp    r1, #2            @ see what cache we have at this level
1087|         blt    skip            @ skip if no cache, or just i-cache
1088|         mcr    p15, 2, r10, c0, c0, 0    @ select current cache level in cssr
1089|         mcr    p15, 0, r10, c7, c5, 4    @ isb to sych the new cssr&csidr
1090|         mrc    p15, 1, r1, c0, c0, 0    @ read the new csidr
1091|         and    r2, r1, #7        @ extract the length of the cache lines
1092|         add    r2, r2, #4        @ add 4 (line length offset)
1093|         ldr    r4, =0x3ff
1094|         ands    r4, r4, r1, lsr #3    @ find maximum number on the way size
1095|         clz    r5, r4            @ find bit position of way size increment
1096|         ldr    r7, =0x7fff
1097|         ands    r7, r7, r1, lsr #13    @ extract max number of the index size
1098| loop2:
1099|         mov    r9, r4            @ create working copy of max way size
1100| loop3:
1101|  ARM(        orr    r11, r10, r9, lsl r5    ) @ factor way and cache number into r11
1102|  ARM(        orr    r11, r11, r7, lsl r2    ) @ factor index number into r11
1103|  THUMB(        lsl    r6, r9, r5        )
1104|  THUMB(        orr    r11, r10, r6        ) @ factor way and cache number into r11
1105|  THUMB(        lsl    r6, r7, r2        )
1106|  THUMB(        orr    r11, r11, r6        ) @ factor index number into r11
1107|         mcr    p15, 0, r11, c7, c14, 2    @ clean & invalidate by set/way
1108|         subs    r9, r9, #1        @ decrement the way
1109|         bge    loop3
1110|         subs    r7, r7, #1        @ decrement the index
1111|         bge    loop2
1112| skip:
1113|         add    r10, r10, #2        @ increment cache number
1114|         cmp    r3, r10
1115|         bgt    loop1
1116| finished:
1117|         ldmfd    sp!, {r0-r7, r9-r11}
1118|         mov    r10, #0            @ swith back to cache level 0
1119|         mcr    p15, 2, r10, c0, c0, 0    @ select current cache level in cssr
1120| iflush:
1121|         mcr    p15, 0, r10, c7, c10, 4    @ DSB
1122|         mcr    p15, 0, r10, c7, c5, 0    @ invalidate I+BTB
1123|         mcr    p15, 0, r10, c7, c10, 4    @ DSB
1124|         mcr    p15, 0, r10, c7, c5, 4    @ ISB
1125|         mov    pc, lr
  1. 完成了刷新缓存后跳转回来此处继续执行;获取 restart 标签的地址,然后根据 R6 进行修正,再将修正后的值传递给 PC,完成了重新跳转到 restart 标签处。
File: /arch/arm/boot/compressed/head.S
395|         adr    r0, BSYM(restart)
396|         add    r0, r0, r6
397|         mov    pc, r0

13. 重定位后,从 restart 处开始执行,执行到 line-338 进行判断是否会发生覆盖,此时肯定满足不会发生覆盖的第二种情况,跳转到 wont_overwrite 标签处开始执行。

  1. 若 R5 = 0 (不配置 CONFIG_ARM_APPENDED_DTB 时成立) 并且 R0 为 0,则直接跳转到 not_relocated 处(因为 R0 = 0 说明链接地址等于运行地址,即肯定没有发生重定位,所以无需对 GOT 表进行重定位);否则继续执行,对 GOT 表进行重定位。
File: /arch/arm/boot/compressed/head.S
399| wont_overwrite:
    ……
413|         orrs    r1, r0, r5
414|         beq    not_relocated

注: 此处当配置了 CONFIG_ARM_APPENDED_DTB (默认不配置) 时, R5 为 紧跟在 zImage 后的 dtb 的大小;否则 R5 = 0.

  1. 修正 R11、R12、R2 和 R3 的值。(这四个寄存器分别代表的值参看本文中 “10” 中的插图)
File: /arch/arm/boot/compressed/head.S
416|         add    r11, r11, r0
417|         add    r12, r12, r0
418| 
419| #ifndef CONFIG_ZBOOT_ROM
    ……
425|         add    r2, r2, r0
426|         add    r3, r3, r0
  1. 重定位 GOT 表中的所有项。
File: /arch/arm/boot/compressed/head.S
432| 1:        ldr    r1, [r11, #0]        @ relocate entries in the GOT
433|         add    r1, r1, r0        @ This fixes up C references
434|         cmp    r1, r2            @ if entry >= bss_start &&
435|         cmphs    r3, r1            @       bss_end > entry
436|         addhi    r1, r1, r5        @    entry += dtb size
437|         str    r1, [r11], #4        @ next entry
438|         cmp    r11, r12
439|         blo    1b
    ……
442|         add    r2, r2, r5
443|         add    r3, r3, r5

注: 若配置了 CONFIG_ARM_APPENDED_DTB,则 dtb 是被插入在 zImage 的 got 段之后,bss 段之前;所以此处若 GOT 中的该项满足 bss_start =< entry < bss_end 时,则需进行 entry += dtb_size 以修正,同时通过 R5 来修正 R2 和 R3.

  1. 若配置了 CONFIG_ZBOOT_ROM,则只重定位 BSS 段之外的项。
File: /arch/arm/boot/compressed/head.S
445| #else
    ……
451| 1:        ldr    r1, [r11, #0]        @ relocate entries in the GOT
452|         cmp    r1, r2            @ entry < bss_start ||
453|         cmphs    r3, r1            @ _end < entry
454|         addlo    r1, r1, r0        @ table.  This fixes up the
455|         str    r1, [r11], #4        @ C references.
456|         cmp    r11, r12
457|         blo    1b
458| #endif

14. 清除 BSS 段

File: /arch/arm/boot/compressed/head.S
460| not_relocated:    mov    r0, #0
461| 1:        str    r0, [r2], #4        @ clear bss
462|         str    r0, [r2], #4
463|         str    r0, [r2], #4
464|         str    r0, [r2], #4
465|         cmp    r2, r3
466|         blo    1b

15. 为调用 decompress_kernel 函数准备参数,调用 decompress_kernel 进行解压内核。

File: /arch/arm/boot/compressed/head.S
475|         mov    r0, r4          @ R4 = ZRELADDR, 解压起始地址
476|         mov    r1, sp            @ malloc space above stack
477|         add    r2, sp, #0x10000    @ 64k max
478|         mov    r3, r7          @ r7  = architecture ID
479|         bl    decompress_kernel
  • 此时 R0、R1、R2 分别代表的值如图所示:


    ARM Linux 3.10.61 的启动 —— (一)解压缩阶段_第16张图片
    before_decompress

16. 解压内核后完成后,依次进行刷新缓存、关闭缓存,恢复 R1、R2 的值;然后跳转到 __enter_kernel 标签处(这里暂时不考虑配置了 CONFIG_ARM_VIRT_EXT 后执行的代码)。

File: /arch/arm/boot/compressed/head.S
480|         bl    cache_clean_flush
481|         bl    cache_off
482|         mov    r1, r7            @ restore architecture number
483|         mov    r2, r8            @ restore atags pointer
484| 
485| #ifdef CONFIG_ARM_VIRT_EXT
    ……
502| #else
503|         b    __enter_kernel
504| #endif

17. 跳转到解压好的内核处。

File: /arch/arm/boot/compressed/head.S
1264| __enter_kernel:
1265|         mov    r0, #0            @ must be 0
1266|  ARM(        mov    pc, r4    )        @ call kernel
1267|  THUMB(        bx    r4    )        @ entry point is always ARM

注: R4 = ZRELADDR,即解压后的内核的入口。


全文完

Reference

1] ARM linux解析之压缩内核zImage的启动过程
2] ARM Linux启动流程分析——内核自解压阶段
3] /arch/arm/boot/compressed/head.S 分析
4] Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7)
5] ARM协处理器CP15寄存器详解
6] * ARM处理器的协处理器CP15/CP14
7] 随笔之GoldFish Kernel启动过程中arm汇编分析
8] ARM® Architecture Reference Manual ARMv7-A and ARMv7-R edition
9] Linux内核启动流程分析(一)

你可能感兴趣的:(ARM Linux 3.10.61 的启动 —— (一)解压缩阶段)