可以从以长模式运行的 BSP 发送 SIPI 吗?

Posted

技术标签:

【中文标题】可以从以长模式运行的 BSP 发送 SIPI 吗?【英文标题】:Can SIPI be sent from a BSP running in long mode? 【发布时间】:2022-01-05 20:55:53 【问题描述】:

目前我有一个在 x86 保护模式下运行的多处理操作系统,我想让它在 x86_64 长模式下运行。它当前唤醒 AP 的逻辑是通过发送 SIPI-INIT-INIT:

// BSP already entered protected mode, set up page tables
uint32_t *icr = 0xfee00300;
*icr = 0x000c4500ul;        // send INIT
delay_us(10000);            // delay for 10 ms
while (*icr & 0x1000);      // wait until Send Pending bit is clear
for (int i = 0; i < 2; i++) 
    *icr = 0x000c4610ul;    // send SIPI
    delay_us(200);          // delay for 200 us
    while (*icr & 0x1000);  // wait until Send Pending bit is clear

此程序在 32 位保护模式下运行良好。

但是,在我将操作系统修改为以 64 位长模式运行后,发送 SIPI 时逻辑中断。在 QEMU 中,在执行 send SIPI 行之后,BSP 立即被重置(程序计数器变为 0xfff0)。

在英特尔的软件开发人员手册第 3 卷第 8.4.4.1 节(典型 BSP 初始化序列)中,它说 BSP 应该“切换到保护模式”。此过程是否适用于长模式?我应该如何调试这个问题?

如果有帮助,这里有一些调试信息:

在 64 位长模式下发送 SIPI 指令 (movl $0xc4610,(%rax)) 之前的 CPU 寄存器:

rax            0xfee00300          4276093696
rbx            0x40                64
rcx            0x0                 0
rdx            0x61                97
rsi            0x61                97
rdi            0x0                 0
rbp            0x1996ff78          0x1996ff78
rsp            0x1996ff38          0x1996ff38
r8             0x1996ff28          429326120
r9             0x2                 2
r10            0x0                 0
r11            0x0                 0
r12            0x0                 0
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
rip            0x1020d615          0x1020d615
eflags         0x97                [ IOPL=0 SF AF PF CF ]
cs             0x10                16
ss             0x18                24
ds             0x18                24
es             0x18                24
fs             0x18                24
gs             0x18                24
fs_base        0x0                 0
gs_base        0x0                 0
k_gs_base      0x0                 0
cr0            0x80000011          [ PG ET PE ]
cr2            0x0                 0
cr3            0x19948000          [ PDBR=12 PCID=0 ]
cr4            0x20                [ PAE ]
cr8            0x0                 0
efer           0x500               [ LMA LME ]
mxcsr          0x1f80              [ IM DM ZM OM UM PM ]

在 32 位保护模式下发送 SIPI 指令 (movl $0xc4610,(%eax)) 之前的 CPU 寄存器:

rax            0xfee00300          4276093696
rbx            0x40000             262144
rcx            0x0                 0
rdx            0x61                97
rsi            0x2                 2
rdi            0x102110eb          270602475
rbp            0x19968f4c          0x19968f4c
rsp            0x19968f04          0x19968f04
r8             0x0                 0
r9             0x0                 0
r10            0x0                 0
r11            0x0                 0
r12            0x0                 0
r13            0x0                 0
r14            0x0                 0
r15            0x0                 0
rip            0x1020d075          0x1020d075
eflags         0x97                [ IOPL=0 SF AF PF CF ]
cs             0x8                 8
ss             0x10                16
ds             0x10                16
es             0x10                16
fs             0x10                16
gs             0x10                16
fs_base        0x0                 0
gs_base        0x0                 0
k_gs_base      0x0                 0
cr0            0x80000015          [ PG ET EM PE ]
cr2            0x0                 0
cr3            0x19942000          [ PDBR=12 PCID=0 ]
cr4            0x30                [ PAE PSE ]
cr8            0x0                 0
efer           0x0                 [ ]
mxcsr          0x1f80              [ IM DM ZM OM UM PM ]

【问题讨论】:

【参考方案1】:

是否可以从以长模式运行的 BSP 发送 SIPI?

是的。唯一重要的是您将正确的值写入正确的本地 APIC 寄存器(具有正确的延迟,有点 - 见最后我的方法)。

但是,在我将操作系统修改为以 64 位长模式运行后,发送 SIPI 时逻辑中断。在 QEMU 中,执行发送 SIPI 行后,BSP 立即复位(程序计数器变为 0xfff0)。

我会假设:

a) 存在错误,本地 APIC 寄存器的地址不正确;当您尝试写入本地 APIC 的寄存器时会导致三重错误。不要忘记长模式必须使用分页,即使 0xFEE00300 可能是正确的物理地址,它也可能是错误的虚拟地址(除非您在将操作系统移植到长模式时通过标识映射该特定页面来处理这一点)。

b) 由于某种难以想象的原因,数据不正确,导致 SIPI 重新启动 BSP。

在英特尔的软件开发人员手册第 3 卷第 8.4.4.1 节(典型 BSP 初始化序列)中,它说 BSP 应该“切换到保护模式”。这个过程是否适用于长模式?

英特尔的“典型 BSP 初始化序列”只是仅适用于固件开发人员的一个可能示例。请注意,“面向固件开发人员”意味着它不应该被任何操作系统使用。

英特尔示例的主要问题是它将 INIT-SIPI-SIPI 序列广播到所有其他 CPU(可能包括固件因故障而禁用的 CPU,也可能包括固件因其他原因而禁用的 CPU -例如因为用户禁用了超线程);并且未能检测到“CPU 存在但由于某种原因无法启动”(操作系统应向用户报告)。

另一个问题是,通常操作系统会希望在启动之前为每个 AP 预先分配一个堆栈(并在启动 AP 之前将“您应该用于堆栈的地址”存储在某处),并且您不能给每个 AP如果您同时启动未知数量的 CPU,则像这样 AP 自己的堆栈。

基本上;固件使用(类似于)英特尔描述的示例,然后在“ACPI/MADT”ACPI 表(和/或非常旧的计算机的“多处理器规范表” - 现在已经过时)中构建信息供操作系统使用;并且操作系统使用固件表中的信息以正确(供应商和平台中立)的方式查找本地 APIC 的物理地址,并仅查找固件认为有效的 CPU 并确定这些 CPU/s 是否有效使用“本地 APIC”或“X2APIC”(支持超过 256 个 APIC ID,如果有大量 CPU,则这是必需的);然后在使用超时时一次只启动一个有效的 CPU,以便可以向用户报告和/或记录“我有证据的 CPU #123 无法启动”。

我还应该指出,英特尔的示例在英特尔的手册中已经存在了大约 25 年(自从引入长模式之前),几乎没有变化。

我的方法

Intel 算法的延迟很烦人,通常一个 CPU 会在第一个 SIPI 上启动,有时第二个 SIPI 会导致同一个 CPU 启动两次(如果你有任何类型的“started_CPUs++;”会导致问题在 AP 启动代码中)。

为了解决这些问题(并提高性能),AP 启动代码可以设置一个“我开始”标志,而不是在发送第一个 SIPI 之后有一个“delay_us(200);”,BSP 可以监控“我开始”标记超时,如果 AP 已经启动,则跳过第二个 SIPI(以及超时的剩余部分)。在这种情况下,SIPI 之间的超时时间可以更长(例如 500 us 就可以了),更重要的是不需要那么精确;并且在发送第二个 SIPI(如果需要发送第二个 SIPI)后,可以重复使用相同的“等待标志超时”代码,超时时间更长。

仅此一项并不能完全解决“CPU 启动两次”的问题;并且它没有解决“AP 在第二个 SIPI 之后启动,但在超时过期后启动,所以现在有 2 个 AP 在运行,操作系统只知道一个”。这些问题通过额外的同步得到解决 - 具体来说,AP 设置“我开始标志”,然后它可以等待 BSP 设置“如果您的 APIC ID 是......,您可以继续”要设置的值(并且如果AP 检测到 APIC ID 值错误,它可以执行“CLI 然后 HLT”循环以自行关闭)。

最后;如果您一次执行整个“INIT-SIPI-SIPI”序列一个 CPU,那么如果有很多 CPU,它可能会很慢(例如,由于发送 INIT 后的 10 毫秒延迟,100 个 CPU 至少需要一整秒)。这可以通过使用 2 种不同的方法来显着减少:

a) 并行启动 CPU。为了最好的情况; BSP 可以启动 1 个 AP,然后 BSP+AP 可以再启动 2 个 AP,然后 BSP+3 个 AP 可以再启动 4 个 AP,依此类推。这意味着 128 个 CPU 可以在略多于 70 毫秒的时间内启动(而不是超过一秒) .为了使这项工作(给每个 AP 不同的值用于堆栈等),最好使用多个 AP CPU 启动蹦床(例如,这样一个 AP 可以执行“mov esp,[cs:stackPointer]”,其中不同的 AP 以不同的值在 @ 987654324@,因为它来自 SIPI)。

b) 一次向多个 CPU 发送多个 INIT;然后有一个 10 毫秒的延迟;然后一次执行后面的“SIPI-SIPI”序列一个 CPU。这依赖于后来的“SIPI-SIPI”序列相对较快(与 INIT 之后的巨大 10 毫秒延迟相比),并且 CPU 对 10 毫秒延迟的确切长度不太挑剔。例如;如果您向 4 个 CPU 发送 4 个 INIT,并且您知道(在最坏的情况下)SIPI-SIPI 需要 1 ms 让操作系统决定 CPU 无法启动;那么在将 INIT 发送到第四个/最后一个 CPU 和将第一个 SIPI 发送到第四个/最后一个 CPU 之间会有 13 毫秒的延迟。

请注意,如果您足够勇敢,可以将这两种方法结合使用(例如,您可以在 50 毫秒多一点的时间内启动 128 个 CPU)。

【讨论】:

我能够找出我的操作系统中的问题。实际的问题是,在gdb中,当BSP执行send SIPI时,AP执行了很多代码。由于我的 AP 代码尚未准备好,AP 会产生三重故障,重置整个机器(即 BSP 和 AP)。在 gdb 中,BSP 似乎由于执行 send SIPI 而被重置。

以上是关于可以从以长模式运行的 BSP 发送 SIPI 吗?的主要内容,如果未能解决你的问题,请参考以下文章

怎么设置将LINUX的启动模式从以图形界面启动改为以文本模式启动

从以管理员身份运行的进程以用户身份启动进程

单片机是bsp驱动吗

从以 root 身份执行的脚本运行 awesome-client

XCTest 从以随机顺序运行的测试中获取执行顺序

trunk模式和access模式有啥区别吗?