Cha*_*Kim 3 boot assembly linux-kernel arm64
这是来自 linux arm64 (arch/arm64/kernel/head.S) 的汇编代码。(内核源代码 5.4.21)
__primary_switched:
adrp x4, init_thread_union -- line 1
add sp, x4, #THREAD_SIZE -- line 2
adr_l x5, init_task -- line 3
msr sp_el0, x5 // Save thread_info -- line 4
adr_l x8, vectors // load VBAR_EL1 with virtual -- line5
msr vbar_el1, x8 // vector table address -- line 6
isb -- line7
stp xzr, x30, [sp, #-16]! -- line8
mov x29, sp -- line9
str_l x21, __fdt_pointer, x5 // Save FDT pointer -- line10
Run Code Online (Sandbox Code Playgroud)
我会尽力解释它,如果我错了,请有人给我启发并纠正我。
我无法确切理解这段代码在做什么。特别是第 4 行。这段代码在做什么?
好吧,arm64 上的堆栈管理速成课程:
在 EL1 中,您有两个堆栈指针寄存器,您可以使用mrs/ msr:sp_el1和 来访问它们sp_el0。您还有一个名为 的寄存器sp,您可以在大多数其他指令(如add、str等)中访问该寄存器。然后还有另一个名为 的系统寄存器,它由一个控制是否为或的别名的spsel位组成。为了显示:spsp_el1sp_el0
movz x1, 0x1000
movz x2, 0x2000
msr sp_el0, x1
msr sp_el1, x2
msr spsel, 0
add x3, sp, 0x10
msr spsel, 1
add x4, sp, 0x20
// AT this point, x3 == 0x1010 and x4 == 0x2020
Run Code Online (Sandbox Code Playgroud)
此外,当您在 EL1 运行并运行eret到 EL0 时,堆栈指针将始终为sp_el0。但这一切的原因是,当你再次对 EL1 抛出异常时,你的堆栈指针总是切换到sp_el1。这样做是因为每个通用寄存器都保存此时的用户空间值,并且您需要一种方法来保存它们而不破坏它们中的任何一个(或存储到用户空间内存中)。
因此,内核通常做的是设置一个异常堆栈,sp_el1在发生异常时可以将寄存器溢出到该异常堆栈中。当从 EL1 到 EL1(例如 IRQ)发生异常时,通常应该安全地存储异常发生之前正在使用的堆栈指针,因此可以完全在sp_el1.
然而,大多数操作系统不会这样做,而是添加另一个堆栈指针,如果愿意的话,可以是“正常内核堆栈”。那么异常流程将如下所示:
sp_el1并禁用中断。sp_el0为“正常内核堆栈”指针的地址。spsel, 0并启用中断。由于异常向量根据您是否来自在sp_el0或 上运行的上下文而有所不同sp_el1,因此这允许您将“预期异常”限制为sp_el0,并且如果您在 上运行时发生异常sp_el1,您会认为您在关键部分出现了故障并出现恐慌。
现在对于您显示的代码:它在前四个指令中所做的只是设置异常和“正常”堆栈指针。它似乎正在运行spsel, 1。
另请注意,这str_l不是实际的指令,而是 Linux 特定的宏:
/*
* @src: source register (32 or 64 bit wide)
* @sym: name of the symbol
* @tmp: mandatory 64-bit scratch register to calculate the address
* while <src> needs to be preserved.
*/
.macro str_l, src, sym, tmp
adrp \tmp, \sym
str \src, [\tmp, :lo12:\sym]
.endm
Run Code Online (Sandbox Code Playgroud)
所以它生成的代码是:
adrp x5, __fdt_pointer
str x21, [x5, :lo12:__fdt_pointer]
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,其中不使用 的旧值x5。