Assembly 为什么在arm64引导代码中将init_任务结构地址保存到sp_el0?
这是来自linux arm64(arch/arm64/kernel/head.S)的汇编代码。(内核源代码5.4.21) 我会尽力解释的,如果我错了,请有人告诉我并纠正我Assembly 为什么在arm64引导代码中将init_任务结构地址保存到sp_el0?,assembly,linux-kernel,boot,arm64,Assembly,Linux Kernel,Boot,Arm64,这是来自linux arm64(arch/arm64/kernel/head.S)的汇编代码。(内核源代码5.4.21) 我会尽力解释的,如果我错了,请有人告诉我并纠正我 第1行:x4=(初始化线程联合的页面地址)。我发现init_thread_union是内核链接脚本(arch/arm64/kernel/vmlinux.lds)中的一个变量。这个vmlinux.lds是在内核构建期间从vmlinux.lds.S生成的 第2行:sp=(x4+#螺纹尺寸)。看起来像是为此线程设置堆栈指针。(看起
- 第1行:x4=(初始化线程联合的页面地址)。我发现init_thread_union是内核链接脚本(arch/arm64/kernel/vmlinux.lds)中的一个变量。这个vmlinux.lds是在内核构建期间从vmlinux.lds.S生成的
- 第2行:sp=(x4+#螺纹尺寸)。看起来像是为此线程设置堆栈指针。(看起来此线程正在使用init_thread_union内存区域)(使用init_thread_union位置的4K字节作为此线程的堆栈)
- 第3行:x5=(init_task结构的地址),我发现init_task是init task的task结构(在init/init_task.c中)。所以这个结构包含线程信息
- 第4行:sp_el0=x5。为什么要用线程信息设置异常级别为0的堆栈指针?该sp_el0与第2行中的sp不同吗?(我想我们现在是el 1,所以第2行中的sp表示sp_el1)。x5随后用于第10行
我不能确切地理解这段代码在做什么。尤其是第四行。这段代码在做什么?好的,关于arm64上堆栈管理的速成课程: 在EL1,您有两个堆栈指针寄存器,可以使用
mrs
/msr
访问它们:sp_EL1
和sp_el0
。您还有一个名为sp
的寄存器,您可以在大多数其他指令(如add
、str
)中访问该寄存器。然后还有另一个名为spsel
的系统寄存器,它由一个位组成,控制sp
是sp_el1
还是sp_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
此外,当您在EL1上运行并且eret
到EL0时,堆栈指针将始终是sp_EL0
。但这一切的原因是,当您再次对EL1执行异常时,堆栈指针总是切换到sp_EL1
。之所以这样做,是因为每个通用寄存器在该点上都保存用户空间值,并且您需要一种在不破坏任何值(或存储到userland内存)的情况下保存它们的方法。因此,内核通常要做的是在
sp_el1
中设置一个异常堆栈,当发生异常时,可以将寄存器溢出到该堆栈上。当从EL1到EL1发生异常(例如IRQ)时,通常应该安全地存储在发生异常之前正在使用的堆栈指针,因此可以完全在sp_EL1
上运行内核本身然而,大多数操作系统不这样做,而是添加另一个堆栈指针,如果您愿意的话,添加一个“普通内核堆栈”。然后异常流将如下所示:
sp_EL1
并禁用中断sp_el0
中的值sp_el0
还是sp_el1
上运行的上下文而有所不同,这允许您将“预期异常”限制在sp_el0
,如果您在sp_el1
上运行时发生异常,则假定您在关键部分出现故障并陷入恐慌
现在来看您所展示的代码:它在前四条指令中所做的只是设置异常和“正常”堆栈指针。它似乎是用spsel,1
运行的
还要注意的是,stru\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
正如您所看到的,它没有使用与您的非常相似的旧值
x5
,但我看到您缩小了它的范围!您也可以简单地编辑上一个问题。@fuz我的问题太多,无法包含在一个问题中。所以我打算把它分成两三个问题。我不知道我的前一个问题仍然存在(我将删除它并用于下一个问题)。可能是个好主意。不幸的是,我无法帮助您回答您的问题,因为我没有足够的经验。init\u thread\u union
属于thread\u union
类型,显然。因此,前两行设置了每个线程的唯一堆栈。在EL0堆栈上保存init_task
,可能是为了让EL0代码知道在哪里可以找到它(我假设整个例程都在EL1调用)。@MargaretBloom是的,读了你的评论后,看起来是这样的。因此,init_thread_union似乎被用作当前线程堆栈,可以选择将task_struct或thread_info保留在最低地址(堆栈向下增长)。顺便说一下,我在内核源代码中找不到正在声明的init_线程联合,可能是由脚本生成的。。?谢谢你的评论。好吧,看完你的答案后,我还是有点困惑。首先是一些想法。每个进程和每个线程(进程中)都有自己的堆栈。例如,EL0可用于用户应用程序,EL1可用于操作系统。所以当有这么多堆栈时,你的意思是只有sp_el0和sp_el1覆盖所有堆栈,对吗?我想是的。当有函数调用时,堆栈帧递减,堆栈指针从堆栈帧的顶部开始(堆栈向下增长)。堆栈中保存了函数调用参数、自动变量(可能也返回值?)。考虑到所有这些,现在我知道sp只是sp_el0或sp_el1的别名。(我猜到了)。一个需要澄清的问题:当EL0期间发生异常时,寄存器是否溢出到sp_EL0堆栈或sp_el1堆栈?(我猜sp_el1堆栈…),另一个问题:没有专门用于异常处理的堆栈指针,对吗?(对于
/*
* @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
adrp x5, __fdt_pointer
str x21, [x5, :lo12:__fdt_pointer]