/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
关键有两点:
- 将模式位 M[4:0] 清 0
File: /arch/arm/include/asm/assembler.h
255| bic \reg , \reg , #MODE_MASK
- 通过设置低 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 的值为:
- 先是 pc 值和 0xf8000000 做与操作;
注:此处与 0xf8000000 做 and 操作的原因样是我们默认 zImage 被放置的位置一定在距离 PHYS_OFFSET 的 128MB 之内。
- 再加上 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 的值的原因如下图所示:
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
- 将 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 列表中的每一项的结构如下图所示:
- 获取处理器 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,
-
MIDR 的位分配如下图所示:
其中 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 .
- 遍历 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 为例)
- 读取 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,
-
ID_MMFR0(Memory Model Feature Register 0) 的位分配下图所示:
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, 参见上文。
要求达到的内存布局如下图所示:
常数 #16384 的由来:
32 位的 RAM 系统,寻址空间为 4GB,此处每一个页表项代表 1MB,则需要 4096 个页表项。同时,每一个页表项的大小为 4Byte,那么就需要 4096 * 4Byte = 16384Byte = 16KB 的空间。-
此时页表项的每项对应 1MB 的内存空间,其格式为 段(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] 看起来比较乱,实际上首次循环的流程如下图所示:
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 平台上将会有如下的 段页表项-物理内存 的映射关系:
注:由上图可知,在 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 到此结束
- 一些必需的使能 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:
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,可得下图:
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 下的内存布局如下图所示:
注:默认不配置 CONFIG_ARM_APPENDED_DTB,跳过 line 227 - line 322 的内容。
11. 检测两种不会发生覆盖的情形
- 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
此种情况如下图所示:
- 解压后的内核的结束地址 <= 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
此种情况如下图所示:
12. 若发生了覆盖则不会跳转到 wont_overwrite,向后执行进行代码的自搬移和重定位。
- 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 进行此番操作的话,下图所示情形便有可能造成覆写自身:
- 选定了复制范围,进行 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)
-
执行完上述代码后内存空间布局如下图所示:
- 通过循环进行复制 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
-
执行完循环后,内存布局如下所示:
- 计算 "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
-
如下图所示:
- 调用 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
- 完成了刷新缓存后跳转回来此处继续执行;获取 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 标签处开始执行。
- 若 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.
- 修正 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
- 重定位 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.
- 若配置了 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 分别代表的值如图所示:
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内核启动流程分析(一)