在开机后的环境(无OS),怎么会使用BSP(第一核/处理器)创建的IPI为APS(所有其他内核/处理器)? 从本质上讲,一个人如何唤醒,并为其他核心的指令指针从一开始的时候?
Answer 1:
警告:我假设的80x86在这里。 如果不是80x86的话,我不知道:-)
首先,你需要找出多少其它的CPU存在,以及他们的APIC ID为,并确定本地APICS的物理地址。 要做到这一点,你解析ACPI表(见ACPI规范MADT / APIC)。 如果你不能找到有效的ACPI表(例如电脑太旧)有一个旧的“多处理器规范”,定义与它相同的信息它自己的表。 请注意,“多处理器规范”现在已经过时(并有与虚拟多处理器表有些电脑),这就是为什么你需要首先检查ACPI表。
下一个步骤是确定你有什么类型的本地APIC的。 有3种情况 - 老外“82489DX”本地APICS(没有内置在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”本地APICS是在80486台旧电脑使用,而这些都是极为罕见的(他们是非常罕见的20年前,当时大多数死亡和/或因为得到了更换,并扔掉了)。 因为不同的序列用于启动其它的CPU,并且由于具有这些地方APICS电脑是非常罕见的(例如,你可能永远不能对代码进行测试),这让很多的意义不打扰支持这些计算机。 如果你支持所有这些旧电脑; 我建议你把他们当作“唯一的单CPU”,只是没有开始任何其他CPU / s如果本地APIC是“82489DX”。 出于这个原因,我就不一一介绍了使用从这里开始他们(它英特尔的“多进程规范”中描述了如果你好奇)的方法。
对于xAPIC和x2APIC,用于开始另一个CPU的序列是基本上相同的(访问本地APIC的只是不同的方式 - 的MSR或存储器映射)。 我推荐使用(例如)函数指针来隐藏这些差异; 这样以后代码可以调用通过“发送IPI”功能。 如果没有本地APIC贴心的函数指针是x2APIC或xAPIC。
要真正开始,你需要的IPI的序列(处理器间中断)发送给它的另一个CPU。 英特尔的方法是这样的:
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
有2个问题,英特尔的方法。 通常情况下,其他的CPU将首次启动IPI开始,并在某些情况下,这可能会导致问题(例如,如果其它CPU的启动代码不会像total_CPUs++;
然后每个CPU可能执行两次为了避免这个问题,你可以添加。额外的同步(例如,其它CPU等待一个“我知道你开始”标志由它继续之前的第一个CPU设置)。与英特尔的方法的第二个问题是测量这些延迟。通常情况下操作系统启动其它CPU,然后计算出哪些功能的CPU支持,以后的硬件是否存在,并没有精确的计时器/ s的设置来衡量这些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
另外请注意,您需要单独启动的CPU。 我见过的人开始使用“广播IPI所有,但自”功能在同一时间所有的CPU - 这是错误的,破碎和狡猾的(除非你写固件不这样做)。 这里的问题是,某些CPU可能出现故障(如失败的BIST /内置自测试)和某些CPU可能会被禁用时(超线程固件被禁用如超线程); 和“广播IPI所有,但自”方法可以启动不应该已经启动的CPU。
最后,对于大量的CPU的计算机可能需要相当长的时间来启动所有这些,如果你开始逐一时间。 例如,如果需要11毫秒启动每个CPU和有128个CPU,那么它会采取1.4秒。 如果你想快速启动有办法避免这种情况。 例如,第一个CPU可以开始第二个CPU,那么第一和第二CPU可以开始第3和第4的CPU,那么这四个CPU可以开始下个CPU,等等。这样,你可以在77毫秒启动128个CPU而不是1.4秒。
注:我建议刚开始的CPU一次一个,并确保您尝试任何一种“平行启动”之前的作品(它的东西,你可以不用担心事后你都知道了工作之后)。
与其它CPU /秒将开始执行的地址在STARTUP IPI的“载体”字段进行编码。 CPU将开始执行代码(在实模式) CS = vector * 256
和IP = 0
。 该矢量场是8位,所以你可以使用的最高起始地址为0x000FF000(为0xFF00:0×0000在实模式下)。 然而,这是传统的ROM区域(在实践中的起始地址必须是低)。 通常你一小片的启动代码复制到一个合适的地址; 其中,启动代码处理同步(例如,设置了“我开始”标志,另一个CPU可以看到,等待被告知它的行继续),然后确实喜欢启用保护/长模式和设置堆栈跳入一进入前的事点在OS的正常码。 这小片的启动代码被称为“AP CPU启动蹦床”。 这也是什么使“平行启动”有点复杂; 作为被启动每个CPU都需要自己的/单独的同步标记和栈; 并且因为这些东西通常用在蹦床变量实现(例如mov esp,[cs:stackTop]
这意味着与多个蹦床结束。