相信一些细心的读者有注意到, 到目前位置, 内核仍然在低地址段运行, 虽然我们已经启动了MMU, 但是仍然运行在等于物理地址的虚拟地址上, 下面我们就要正式的切换到内核态的高地址空间来运行.
1 2 3
| ldr x8, =__primary_switched adrp x0, __PHYS_OFFSET blr x8
|
这段code是上篇遗留的一点内容, 现在来进行分析. 第一条指令是arm的伪指令, 将__primary_switched
标签的链接地址放在x8中, 第二条将__PHYS_OFFSET
对应的运行时地址存在在x0中, __PHYS_OFFSET
虽然名字有PHYS, 但是实际他是指_text - TEXT_OFFSET
的链接地址. 它等于0xffff_0000_0000_0000 + 128M(module) + kaslr, 那现在x0存放的应该就是 DDR起始地址 + 2M align预留内存 + kaslr, 然后跳转到x8执行
这里有一点需要注意, 第一条指令加载的是链接地址, 我们现在放置的位置和链接地址是有kaslr的偏移的, 他是如何跳对的呢?
第一条指令实际上变汇编成一条指令加一个内存池, 如:
1 2 3
| ldr x8 __priary_switched_addr __priary_switched_addr: 0xffff_xxxx_xxxx_xxxx(也就是__primary_switched的链接地址)
|
这个内存池实际上就会导致上一篇内容讲的.rela
段增加3条内容. 因此在上篇的relocate运行时, 这个地址就已经被修正了. 自然就能跳转到正确的位置了.
来看看__primary_switched
的内容(特别注意, 从跳转发生开始, 内核就开始运行到高的虚拟地址上了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| __primary_switched:
adrp x4, init_thread_union add sp, x4, #THREAD_SIZE
adr_l x5, init_task msr sp_el0, x5
adr_l x8, vectors msr vbar_el1, x8 isb
stp xzr, x30, [sp, #-16]! mov x29, sp
str_l x21, __fdt_pointer, x5
ldr_l x4, kimage_vaddr sub x4, x4, x0 str_l x4, kimage_voffset, x5
adr_l x0, __bss_start mov x1, xzr adr_l x2, __bss_stop sub x2, x2, x0 bl __pi_memset dsb ishst
#ifdef CONFIG_KASAN bl kasan_early_init #endif #ifdef CONFIG_RANDOMIZE_BASE
tst x23, ~(MIN_KIMG_ALIGN - 1) b.ne 0f mov x0, x21 bl kaslr_early_init cbz x0, 0f orr x23, x23, x0 ldp x29, x30, [sp], #16 ret 0: #endif
add sp, sp, #16 mov x29, #0 mov x30, #0 b start_kernel ENDPROC(__primary_switched)
|
以上就是arm64 linux启动过程的汇编部分的分析.