Assembly 为什么在arm64引导代码中将init_任务结构地址保存到sp_el0?

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+#螺纹尺寸)。看起来像是为此线程设置堆栈指针。(看起

这是来自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+#螺纹尺寸)。看起来像是为此线程设置堆栈指针。(看起来此线程正在使用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
上运行内核本身
然而,大多数操作系统不这样做,而是添加另一个堆栈指针,如果您愿意的话,添加一个“普通内核堆栈”。然后异常流将如下所示:

  • 以EL1为例,硬件隐式切换到
    sp_EL1
    并禁用中断
  • 将所有通用寄存器溢出到异常堆栈
  • 用“普通内核堆栈”指针的地址替换
    sp_el0
    中的值
  • 切换到spsel,0并启用中断
  • 由于异常向量根据您是来自
    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]