Mic*_*tch 7 boot x86 multicore intel osdev
在之前的Stackoverflow 回答中,玛格丽特·布鲁姆 (Margaret Bloom) 说道:
\n\n\n\n\n唤醒 AP
\n\n这是通过向所有 AP 发出 INIT-SIPI-SIPI (ISS) 序列来实现的。
\n\n将发送 ISS 序列的 BSP 使用“全部排除自身”简写作为目的地,从而针对所有 AP。
\n\nSIPI(启动处理器间中断)会被所有在收到 SIPI 时被唤醒的 CPU 忽略,因此,如果第一个 SIPI 足以唤醒目标处理器,则第二个 SIPI 将被忽略。出于兼容性原因,英特尔建议这样做。
\n
我多年来一直在编写多处理代码,我对硬件的观察是,在某些处理器上,它似乎与所述不同。我很确定我已经观察到应用处理器 (AP) 在收到启动 IPI 后修改了其指令指针,即使它处于活动状态(不在等待启动 IPI 中)。
\n\n是否有任何英特尔文档说明 AP在不处于等待启动 IPI 状态时收到启动 IPI 后将执行的操作,或将行为记录为未定义?我似乎无法在英特尔软件文档手册或补充英特尔文档Minimal Boot Loader for Intel\xc2\xae Architecture中找到明确的答案。
\n\n通常,我编写初始化代码来初始化并启动 AP,假设 AP 可以获取 SIPI 并在活动状态(而不是等待启动 IPI 状态)下重置其指令指针。
\n\n我正在尝试确定 Margaret Bloom 声明的准确性,即先前已唤醒的 AP 将忽略第二个启动 IPI。
\n我并不是说应该忽略有缺陷的硬件,但必须首先评估它们的影响。
\n我想提醒读者,虽然我对此事持固执己见的立场,但我希望这个答案尽可能中立。
\n为了充分实现这一目的,我试图为我的陈述提供来源。
虽然我确实相信其他用户的体验,但我不能仅将我的信念建立在记忆上(因为它们无法验证)1,并且我期待有人用证据纠正我引用的陈述。
\n\n我知道这是一个不受欢迎的观点,我希望它不会被认为是完全错误的。
\n\n首先,与计算机一样,一切都归结为标准。虽然英特尔在手册中记录了其 CPU 的 MP 行为,但更进一步制定了适当的多处理器规范。
\n这个规范的重要性在于它在行业中的作用,这不是Intel的CPU的工作原理,据我所知,这是唯一的x86 SMP行业参考。
\nAMD 和 Cyrix 推动了OpenPIC 规范,但引用了维基百科:
\n\n\n然而,没有发布带有 OpenPIC 的 x86 主板。[3] OpenPIC 在 x86 市场失败后,AMD 为其 AMD Athlon 及后续处理器授权了 Intel APIC 架构。
\n
MP 规范的附录 B4 中存在以下行
\n\n\n\n\n如果目标处理器在RESET 或 INIT 之后立即处于暂停状态,则 STARTUP IPI 会使其离开该状态并开始执行。效果是将CS:IP设置为
\nVV00:0000h。
正如评论中所述,我已将if解析为更强的 *iif。
\n\n不幸的是,如上所述,所引用的句子只是一个充分条件。因此它不能用于推断正在运行的 CPU 上的 SIPI 行为。
\n\n然而我个人认为这是一个错误,该规范作者的意图是使用 SIPI 来唤醒处于等待 SIPI状态的 CPU。
\n\nSIPI 是随着集成 APIC 的出现而专门引入的,同时还对 INIT IPI 进行了修订,以管理 AP 的启动。
\nSIPI 对 BSP 没有影响(根据 Intel 手册,BSP永远不会进入等待 SIPI状态),并且很明显,它应该对正在运行的 CPU 没有影响。
\nSIPI 的用处除了不可屏蔽且不需要启用 LAPIC 之外,还在于避免从复位向量运行以及不需要 AP 的热启动标志。
从设计角度来看,让 SIPI 作用于正在运行的 CPU 是没有意义的。CPU 始终以 INIT IPI 作为第一个 IPI 重新启动。
\n\n所以,我有信心将引用的语句解析为口语英语,默认这也是一个必要条件。
\n\n我相信这设置了SIPI 在唤醒 CPU 上的官方行为,即忽略它们。
\n\n事实 1:有一个所有主要 x86 制造商都遵循的行业标准 MP 规范,尽管含糊不清,但其目的是设置 SIPI 的行为。
\n\nPentium 规格更新第 98 页似乎证实了这一点,至少对于 Pentium(可能是 Intel 的后续几代产品,其中可能包括 AMD,因为他们已经从 Intel 购买了 LAPIC 的许可证):
\n\n\n\n\n如果随后将 INIT IPI 发送到已停止的升级组件,则它将被锁定并保持挂起状态,直到收到 STARTUP IPI。从收到 STARTUP IPI 起,CPU 将响应进一步的 INIT IPls\n 但会忽略任何 STARTUP IPls。它不会响应未来的 STARTUP IPls,直到再次发生 RESET 断言或 INIT 断言(INIT Pin 或 INIT IPI)。
\n\n75 MHz、90 MHz 和 100 MHz Pentium 处理器在用作主处理器时,任何时候都不会响应 STARTUP IPI。它将忽略 STARTUP IPI,不会产生任何影响。
\n\n要关闭处理器,操作系统应仅使用 INIT IPI,一旦处理器正在运行,切勿使用 STARTUP IPls。
\n
如果存在不忽略后续 IPI 的 CPU,这并不能解决问题。
\n虽然这个问题仍有待解决,但到目前为止,我们已经将其转变为“是否存在有问题的CPU ......?”。
\n这是一个巨大的飞跃,因为我们现在可以看到现有操作系统如何处理它。
我不会讨论 Windows,虽然我认识到这是一个很大的缺席,但我现在没有心情深入研究 Windows 二进制文件。
\n我可以稍后再做。
Linux
\n\nLinux 发送了两个 SIPI,但我在这个循环中没有看到任何反馈。 代码位于smpboot.c我们清楚地看到num_starts设置为 的位置2。
\n我不会讨论 LAPIC 和 82489DX APIC 之间的区别,特别是后者没有 SIPI 2。
然而我们可以看到Linux是如何遵循Intel的算法的并且它并没有被第二个SIPI所担心。
\n在循环执行num_starts时,SIPI被发送到目标AP。
在评论中已经指出trampoline是幂等的并且Linux作为一种同步机制。
\n这与我的经验不符,当然,Linux 会在 CPU 之间同步代码,但这是在AP 运行后的启动稍后完成的。
\n事实上,AP 执行的第一个 C 代码是蹦床 start_secondary,而且它似乎不是幂等的(set_cpu_online稍后在主体中调用,如果这算的话)。
最后,如果程序员想要防止双重 SIPI,他们会尽早放置同步逻辑,以避免稍后处理复杂的情况。
\n蹦床已经处理了 SME 和漏洞修复,为什么要在处理 SIPI-SIPI 问题 之前这样做呢?
这么晚了进行如此严格的检查对我来说毫无意义。
\n\n免费 BSD
\n我想包含一个 BSD 操作系统,因为 BSD 代码非常干净和健壮。
\n我能够找到一个包含 Free BSD 源代码的 GitHub(非官方)存储库,虽然我对该代码不太有信心,但我已经找到了启动 AP 的例程mp_x86.c.
Free BSD 也使用 Intel 的算法。\n令我感到有趣的是,消息来源还解释了为什么需要第二个 SIPI:P5 处理器(P54C Pentium 系列?)由于错误而忽略了第一个 SIPI:
\n\n\n\n\n/*
\n
\n * 接下来我们执行一个 STARTUP IPI:之前的 INIT IPI 可能仍然被
锁住,(P5 bug)第一个 STARTUP 将
立即终止 \n * ,并且之前启动的 INIT IPI 将继续。或者
\n * 之前的 INIT IPI 已经运行。并且此 STARTUP IPI 将
运行。或者先前的 INIT IPI 被忽略。并且此 STARTUP IPI
\n * 将运行。
\n */
我无法找到此声明的来源,我唯一的线索是在旧的 Android(即 Linux)内核上找到的Pentium 规范更新的勘误表 AP11。
\n今天Linux 似乎已经放弃了对那些旧的有缺陷的 LAPIC 的支持。
考虑到详细的评论,我认为不需要检查代码的幂等性直至假设检查。
\nBSD 代码显然是根据注释的假设编写的。
事实 2:两个主流操作系统认为 SIPI bug 的发生频率不足以值得处理。
\n\n在搜索互联网时,我在gem5模拟器中发现了一个标题为X86: Only recognize the firststartup IPI after INIT or Reset 的提交。
\n显然,他们一开始就弄错了,然后又改正了。
下一步是尝试找到一些在线文档。
\n我首先在 Google Patents 中进行搜索,虽然弹出了很多有趣的结果(包括如何分配 APIC ID),但关于 SIPI,我只在专利Method and Equipment for initiatingexecution of an applicationprocessor in a clustered multiprocessor 中找到了这段文字系统:
\n\n\nSTARTUP IPI 不会导致目标处理器中的任何状态更改(指令指针的更改除外),并且只能在 RESET 后或 INIT IPI 接收或引脚置位后发出一次。
\n
维基百科将威盛列为唯一仍然存在的其他 x86 制造商。。
\n我尝试寻找VIA手册,但似乎它们不是公开的?
关于过去的制造商,我根本找不到是否生产过MP CPU。例如,Cyrix 6x86MX根本没有 APIC,因此它们可能仅通过外部 APIC(无法支持 SIPI)放入 MP 系统中。
\n\n下一步是查看所有AMD 和 Intel 勘误表,看看是否有关于 SIPI 的内容。
\n然而,勘误表是错误,因此问题变成了寻找不存在的证据(即存在错误的 LAPIC 吗?),这是很难找到的(仅仅是因为错误很难找到,而且有很多微架构) 。
我的理解是,第一个集成 APIC(今天已知的 LAPIC)随P54C一起提供,我查阅了勘误表,但没有发现任何有关 SIPI 处理的信息。
\n然而,理解勘误表的全部后果并非易事。
然后我转向Pentium Pro Errata(这是下一个 uarch,P6),发现 SIPI 的处理不正确,尽管这并不完全是我们正在寻找的:
\n\n\n\n\n3AP。INIT_IPI 在 STARTUP_IPI-STARTUP_IPI 序列之后可能会导致
\n\nAP 在 0h** 执行
\n\n
\n 问题:MP 规范规定,要唤醒应用程序处理器 (AP),应将处理器间中断序列 INIT_IPI、STARTUP_IPI、STARTUP_IPI 发送到该处理器。在 Pentium\n Pro 处理器上,INIT_IPI、STARTUP_IPI 序列也将起作用。但是,如果将 INIT_IPI、STARTUP_IPI、STARTUP_IPI 序列发送到 AP,则 APIC 逻辑中可能会出现内部竞争条件,从而使处理器处于不正确的状态。在此状态下操作将是正确的,但如果另一个 INIT_IPI 发送到处理器,处理器将不会按预期停止执行,而是从线性地址 0h 开始执行。为了使竞争条件导致这种不正确的状态,系统\xe2\x80\x99s 内核与总线时钟的比率必须为 5:2\n 或更大。含义:如果系统使用的内核与总线时钟比为 5:2 或更大,并且在 APIC 总线上生成序列 INIT_IPI、\n STARTUP_IPI、STARTUP_IPI 以唤醒 AP,然后在稍后的某个时间\n另一个 INIT_IPI 被发送到处理器,该处理器可能会尝试在线性地址 0h 处执行,并将执行随机操作码。某些操作系统在尝试关闭系统时确实会生成此序列,并且在多处理器系统中,可能会在处理器脱机后挂起。所看到的效果是,如果在退出操作系统时选择\xe2\x80\x98shutdown and restart\xe2\x80\x99 或等效选项,则操作系统可能无法重新启动系统。如果操作系统允许用户使用 INIT_IPI\n 使 AP 脱机\n(英特尔尚未发现任何操作系统当前具有此功能),则不应\n 使用此选项。
\n\n解决方法:BIOS 代码应执行单个 STARTUP_IPI 来唤醒应用程序处理器。\n 然而,操作系统将按照 MP 规范中的建议\n 发出 INIT_IPI、STARTUP_IPI、STARTUP_IPI 序列。在具有 C0 或后续 Pentium Pro 处理器芯片的系统中,BIOS 代码可能包含针对此错误的解决方法。对于 Pentium Pro 处理器的 B0 步进,没有可用的解决方法。
\n
状态:有关受影响的步骤,请参阅本节开头的变更摘要表。
\n\n这个 AP3 勘误很有趣,因为:
\n\n有趣的是,在同一个勘误表中甚至有一个错误导致“相反的行为”:8AP。在低功耗模式下,AP 在 INIT# 或 INIT_IPI\n 后不响应 STARTUP_IPI
\n\n我还检查了 Pentium II、Pentium II Xeon、Pentium III、Pentium 4 勘误表,没有发现任何有关 SIPI 的新内容。
\n\n据我了解,第一个支持 SMP 的 AMD 处理器是基于 Palomino uarch 的 Athlon MP。
\n我检查了 Athlon MP 的修订指南,但没有发现任何内容,检查了此列表中的修订版本,也没有发现任何内容。
不幸的是,我对非 AMD 非 Intel x86 CPU 的经验很少。我无法找到哪些二级制造商包含 LAPIC。
\n\n事实 3:非 AMD/Intel 制造商的官方文档很难找到,勘误表也不容易搜索。勘误表中没有包含与正在运行的处理器上接受 SIPI 相关的错误,但存在大量 LAPIC 错误,使得此类错误的存在合理。
\n\n最后一步是硬件测试。
\n虽然此测试不能排除其他行为的存在,但至少记录了(蹩脚)代码。
\n记录的代码很好,因为它可以用来重复其他研究人员的实验,可以检查它是否存在错误并构成证明。
\n总之是科学的。
我从未见过后续 SIPI 重新启动它的 CPU,但这并不重要,因为只要有一个有错误的 CPU 就足以确认错误的存在。
\n我太年轻、太穷、太人性化,无法对所有 MP CPU 进行广泛的、无错误的分析。
\n所以,我做了一个测试并运行它。
事实 4:Whiskey Lake、Haswell、Kaby Lake 和 Ivy Bridge 都忽略后续的 SIPI。
\n欢迎其他人在 AMD 和较旧的 CPU 上进行测试。
\n这并不构成证据,但正确地描述事情的状态很重要。
\n我们掌握的数据越多,对错误的了解就越准确。
测试包括引导 AP 并使它们递增计数器并进入无限循环(无论是使用jmp $还是使用hlt,结果都是相同的)。
\n同时,BSP 将每n秒发送一个 SIPI,其中n至少为 2(但由于计时机制非常不精确,可能会更多),并打印计数器。
如果计数器保持在k -1(其中k是 AP 数量),则忽略辅助 SIPI。
\n\n有一些技术细节需要解决。
\n\n首先,引导加载程序是遗留的(不是 UEFI),我不想读取另一个扇区,因此我希望它适合 512 字节,因此我在 BSP 和 AP 之间共享引导序列。
\n\n其次,一些代码必须仅由 BSP 执行,但在进入保护模式(例如视频模式设置)之前,因此我使用标志 ( init) 而不是检查BSP寄存器中的标志IA32_APIC_BASE_MSR(稍后完成,以便将 AP 与 BSP 分开) )。
第三,我走了一些捷径。SIPI 启动 CPU 的频率为 ,8000h因此我将远跳至0000h:7c00h。计时是通过端口80h技巧完成的,虽然非常不精确,但应该足够了。GDT 使用空条目。计数器打印在顶部下方几行,以避免被某些监视器裁剪。
如果修改循环以包含 INIT IPI,则计数器会定期递增。
\n\n请注意,此代码不受支持。
\n\nBITS 16\nORG 7c00h\n\n%define IA32_APIC_BASE_MSR 1bh\n%define SVR_REG 0f0h\n%define ICRL_REG 0300h\n%define ICRH_REG 0310h\n\nxor ax, ax\nmov ds, ax\nmov ss, ax\nxor sp, sp ;This stack ought be enough\n\ncmp BYTE [init], 0\nje _get_pm\n\n;Make the trampoline at 8000h\nmov BYTE [8000h], 0eah\nmov WORD [8001h], 7c00h\nmov WORD [8003h], 0000h\n\nmov ax, 0b800h\nmov es, ax\nmov ax, 0003h\nint 10h\nmov WORD [es:0000], 0941h\n\nmov BYTE [init], 0\n\n_get_pm:\n;Mask interrupts\nmov al, 0ffh\nout 21h, al\nout 0a1h, al\n\n;THIS PART TO BE TESTED\n;\n;CAN BE REPLACED WITH A cli, SIPIs ARE NOT MASKEABLE\n;THE cli REMOVES THE NEED FOR MASKING THE INTERRUPTS AND\n;CAN BE PLACED ANYWHERE BEFORE ENTERING PM (BUT LEAVE xor ax, ax\n;AS THE FIRST INSTRUCTION)\n\n;Flush pending ones (See Michael Petch\'s comments)\nsti\nmov cx, 15\nloop $ \n\nlgdt [GDT]\nmov eax, cr0\nor al, 1\nmov cr0, eax\nsti\n\nmov ax, 10h\nmov es, ax\nmov ds, ax\nmov ss, ax\njmp 08h:DWORD __START32__\n\n__START32__: \n BITS 32\n\n mov ecx, IA32_APIC_BASE_MSR\n rdmsr\n or ax, (1<<11) ;ENABLE LAPIC\n mov ecx, IA32_APIC_BASE_MSR\n wrmsr\n\n mov ebx, eax\n and ebx, 0ffff_f000h ;APIC BASE\n\n or DWORD [ebx+SVR_REG], 100h\n\n test ax, 100h\n jnz __BSP__\n\n__AP__: \n lock inc BYTE [counter]\n\n jmp $ ;Don\'t use HLT just in case\n\n__BSP__:\n xor edx, edx \n mov DWORD [ebx+ICRH_REG], edx \n mov DWORD [ebx+ICRL_REG], 000c4500h ;INIT\n\n mov ecx, 10_000\n.wait1:\n in al, 80h\n dec ecx\njnz .wait1 \n\n.SIPI_loop:\n movzx eax, BYTE [counter]\n mov ecx, 100\n div ecx \n add ax, 0930h\n mov WORD [0b8000h + 80*2*5], ax\n\n mov eax, edx \n xor edx, edx\n mov ecx, 10\n div ecx\n add ax, 0930h\n mov WORD [0b8000h + 80*2*5 + 2], ax\n\n mov eax, edx\n xor edx, edx\n add ax, 0930h\n mov WORD [0b8000h + 80*2*5 + 4], ax\n\n xor edx, edx \n mov DWORD [ebx+ICRH_REG], edx \n mov DWORD [ebx+ICRL_REG], 000c4608h ;SIPI at 8000h\n\n mov ecx, 2_000_000\n.wait2:\n in al, 80h\n dec ecx\njnz .wait2\n\njmp .SIPI_loop\n\n\nGDT dw 17h\n dd GDT\n dw 0\n\n dd 0000ffffh, 00cf9a00h\n dd 0000ffffh, 00cf9200h\n\ncounter db 0\ninit db 1\n\nTIMES 510-($-$$) db 0\ndw 0aa55h\nRun Code Online (Sandbox Code Playgroud)\n\n目前还不能得出明确的结论,此事仍悬而未决。
\n读者已看到一系列事实。
预期行为是忽略后续 SIPI,需要两个 SIPI 是由于“P5 错误”。
\nLinux 和 Free BSD 似乎并不介意有缺陷的 SIPI 处理。
\n其他制造商似乎没有提供有关其 LAPIC 的文档(如果他们自己生产的话)。
\n最近的 Intel 硬件会忽略后续的 SIPI。
1对所有相关人员给予应有的尊重,并且不攻击任何人的可信度。我确实相信存在有缺陷的 CPU,但也存在有缺陷的软件和有缺陷的内存。由于我不相信自己的旧记忆,我认为我仍然在尊重的对话范围内要求其他人不要相信他们模糊的记忆。
\n\n2可能是因为当时的 MP 是通过将常规 CPU 封装在一起来完成的,并且INIT#使用外部芯片 (APIC) 断言它们是启动它们的唯一方法(以及设置热复位向量)。然而那些年我还太小,还没有电脑。
根据我的测试,当不处于等待 SIPI状态时,SIPI 会被忽略。\n我已经测试了Whiskey-lake 8565U,当然真实的硬件测试并不能构成证明。
\n我确信自 Pentium 4 以来的所有 Intel 处理器也具有相同的行为,但这只是我的观点。
\n在这个答案中我只想展示测试的结果。每个人都会得出自己的结论。
| 归档时间: |
|
| 查看次数: |
3014 次 |
| 最近记录: |