1

目前我有一个在 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 应该“切换到保护模式”。此过程是否适用于长模式?我应该如何调试这个问题?

以下是一些调试信息,如果有帮助:

movl $0xc4610,(%rax)在 64 位长模式下发送 SIPI 指令 ( ) 之前的 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 ]

movl $0xc4610,(%eax)在 32 位保护模式下发送 SIPI 指令 ( ) 之前的 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 ]
4

1 回答 1

2

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

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

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

我会假设:

a) 存在 bug,本地 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,则像这样堆叠。

本质上; 固件使用(类似于)英特尔描述的示例,然后在“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 启动代码可以设置一个“我开始”标志,而不是delay_us(200);在发送第一个 SIPI 之后有一个“”,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 以不同的值启动,cs因为它来自SIP)。

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)。

于 2021-11-29T00:49:52.923 回答