Bre*_*dan 12
警告:我在这里假设80x86.如果它不是80x86那么我不知道:-)
首先,您需要了解存在多少其他CPU以及它们的APIC ID,并确定本地APIC的物理地址.为此,您需要解析ACPI表(请参阅ACPI规范中的MADT/APIC).如果找不到有效的ACPI表(例如计算机太旧),则会有一个较旧的"多处理器规范",它定义了自己的表,其中包含相同的信息.请注意,现在不推荐使用"多处理器规范"(并且有一些计算机具有虚拟多处理器表),这就是您需要首先检查ACPI表的原因.
下一步是确定您拥有的本地APIC类型.有3种情况 - 旧的外部"82489DX"本地APIC(未内置于CPU本身),xAPIC和x2APIC.
首先检查CPUID以确定本地APIC是否为x2APIC.如果您有2个选择 - 您可以使用x2APIC,或者您可以使用"xAPIC兼容模式".对于"xAPIC兼容模式",您只能使用8位APIC ID,并且无法支持具有大量CPU的计算机(例如,255个或更多CPU).我建议使用x2APIC(即使你不关心拥有大量CPU的计算机),因为它更快.如果您使用x2APIC模式,则需要将本地APIC切换到此模式.
否则,如果不是x2APIC,则读取本地APIC的版本寄存器.如果本地APIC的版本是0x10或更高,那么它的xAPIC,如果它是0x0F或更低,那么它是一个外部"82489DX"本地APIC.
旧的外部"82489DX"本地APIC用于80486和更旧的计算机,这些非常罕见(它们在20年前非常罕见,然后大部分死亡和/或被替换并丢弃).因为使用不同的序列来启动其他CPU,并且因为具有这些本地APIC的计算机非常罕见(例如,您可能永远无法测试您的代码),所以不打扰支持这些计算机是很有意义的.如果你支持这些旧电脑; 我建议将它们视为"仅单CPU",如果本地APIC为"82489DX",则根本不启动任何其他CPU.出于这个原因,我不会在这里描述用于启动它们的方法(如果你很好奇,它将在英特尔的"多工艺规范"中描述).
对于xAPIC和x2APIC,启动另一个CPU的顺序基本相同(只是访问本地APIC的不同方式 - MSR或内存映射).我建议使用(例如)函数指针隐藏这些差异; 以便后面的代码可以调用"发送IPI"功能.如果本地APIC是x2APIC或xAPIC,则无需关心的函数指针.
要实际启动另一个CPU,您需要向其发送一系列IPI(处理器间中断).英特尔的方法是这样的:
Send an INIT IPI to the CPU you're starting
Wait for 10 ms
Send a STARTUP IPI to the CPU you're starting
Wait for 200 us
Send another STARTUP IPI to the CPU you're starting
Wait for 200 us
Wait for started CPU to set a flag (so you know it started)
If flag was set by other CPU, other CPU was started successfully
Else if time-out, other CPU failed to start
Run Code Online (Sandbox Code Playgroud)
英特尔方法存在两个问题.通常其他CPU将由第一个STARTUP IPI启动,并且在某些情况下这会导致问题(例如,如果其他CPU的启动代码执行类似于total_CPUs++;
每个CPU可能执行两次的事情.为了避免此问题,您可以添加额外的同步(例如,其他CPU等待"我知道你开始"标志由第一个CPU在继续之前设置).英特尔方法的第二个问题是测量这些延迟.通常操作系统启动其他CPU,然后计算出哪些功能CPU支持以及之后存在的硬件,并且没有精确的定时器设置来准确地测量这200个延迟.
为了避免这些问题; 我使用另一种方法,如下所示:
Send an INIT IPI to the CPU you're starting
Wait for 10 ms
Send a STARTUP IPI to the CPU you're starting
Wait for started CPU to set a flag (so you know it started) with a short timeout (e.g. 1 ms)
If flag was set by other CPU, other CPU was started successfully
Else if time-out
Send another STARTUP IPI to the CPU you're starting
Wait for started CPU to set a flag with a long timeout (e.g. 200 ms)
If flag was set by other CPU, other CPU was started successfully
Else if time-out, other CPU failed to start
If CPU started successfully
Set flag to tell other CPU it can continue
Run Code Online (Sandbox Code Playgroud)
另请注意,您需要单独启动CPU.我已经看到人们使用"广播IPI到除了自己以外的所有"功能同时启动所有CPU - 这是错误的,破坏和狡猾(除非你正在编写固件,否则不要这样做).这样的问题是某些CPU可能有故障(例如,他们的BIST /内置自测失败)并且某些CPU可能被禁用(例如,在固件中禁用超线程时的超线程); 并且"广播IPI到除了自己之外的所有方法"可以启动永远不应该启动的CPU.
最后,对于具有大量CPU的计算机,如果您一次启动它们,则可能需要相对长的时间才能启动它们.例如,如果启动每个CPU需要11毫秒,并且有128个CPU,则需要1.4秒.如果你想快速启动,有办法避免这种情况.例如,第一个CPU可以启动第二个CPU,然后第一个和第二个CPU可以启动第三个和第四个CPU,那么这四个CPU可以启动接下来的四个CPU等.这样你就可以在77毫秒内启动128个CPU而不是1.4秒.
注意:我建议您一次启动一个CPU,并确保在您尝试任何类型的"并行启动"之前有效(在您知道其余工作后,您可以担心这些事情).
其他CPU将开始执行的地址在STARTUP IPI的"向量"字段中编码.该CPU将开始执行代码(在实模式)CS = vector * 256
和IP = 0
.向量字段为8位,因此可以使用的最高起始地址是0x000FF000(实模式下为0xFF00:0x0000).但是,这是传统的ROM区域(实际上起始地址必须更低).通常,您将一小段启动代码复制到合适的地址中; 启动代码处理同步的位置(例如,设置另一个CPU可以看到的"我已启动"标志并等待被告知可以继续)然后执行诸如启用受保护/长模式并在跳转到条目之前设置堆栈之类的操作指向操作系统的普通代码.这一小段启动代码称为"AP CPU启动trampoline".这也是"并行启动"有点复杂的原因; 因为每个启动的CPU都需要自己/独立的同步标志和堆栈; 并且因为这些东西通常用蹦床中的变量来实现(例如mov esp,[cs:stackTop]
),这意味着最终会有多个蹦床.
归档时间: |
|
查看次数: |
3188 次 |
最近记录: |