各位晚上好,这里是第五位面壁者。

    上一期我们讲了Intel多处理器协议V1.4版本的前三章,主要的篇幅都花在了硬件Spec和中断的部分,在硬件Spec部分,我们蜻蜓点水的稍微提了SMP系统的对称性,BSP和AP,Cache一致性协议,原子操作,lock机制和memory order等概念。在中断部分,我们介绍了Spec规定的三种中断模式以及它们的各自特点,分别是PIC Mode,VirtualWire Mode和对称IO Mode, 以及它们之间是怎么进行切换的。

    刚才出现的每一个技术名词在x86体系结构中十分重要,如果通过我上期的视频大家可以在整体框架上能把这些名词串起来,那么我就达到目的的,大家如果想了解细节,可以参考Intel/AMD的手册,以及我后续的相关视频。视频播出后,有读者提出我的语速有些快以及屏幕对手机用户有些小,语速的问题我尽量调整,屏幕的问题我暂时想到的就是把PPT里的图标和文字尽量往大调整,但是今天的这期的PPT是之前就做好的,所以这期还保持之前的样子,我在下一期会注意这个问题。

    视频录播完整版请去B站:第五位面壁者Andrew,以下是视频中所使用的PPT与语音文字记录(语音文字记录同视频不完全一致)

processInstanceId 获取business process-specific_多处理器

    这一期呢,我们一起学习一下后面的几章,重点在由BIOS负责创建,传递给OS使用的MP Configuration Table。我们先来看一下他的大体结构,由两部分构成,一个是Floating Pointer Structure,一个MPConfiguration Table,二者是一个链式的结构,BIOS创建好后,OS要先找到Floating Pointer Structure 然后通过的PHYSICALADDRESS POINTER里面的地址再找到MP Configuration Table,下面我们先来看看Floating Pointer Structure 的具体定义以及OS要怎么找到它。

processInstanceId 获取business process-specific_ci_02

    可以看到上面这张是这个结构的示意图,下面是具体每个Field的定义,从低地址到高地址开始,第一个先是signature,可以看到在description里说它是一个searchkey。当BIOS创建完成后,OS想用的时候,就到大家都约定好的地址区域内去暴力搜索这个signature,Spec指定的如右边这三个,基本都是1M一下的某个位置。怎么个暴力搜索法?我们自己写程序也能做到,从0开始一直到1M,挨个比对是否由4个byte的ACSII码和_MP_一直,如果找到了也就找到了MP Floating Pointer Structure, 然后再根据表格里的offset就可能找到PHYSICAL ADDRESS POINTER。这种传递方法虽然古老粗暴但是生命力就很持久,目前的UEFI里把ACPI Table传递给ACPIOS也是用的这一招。

processInstanceId 获取business process-specific_初始化_03

    然后我们继续看Structure的剩余部分,是五个information byte,其中byte1用来标识MPConfiguration Table是否存在,如果全零就是存在,我们顺着上面提过的PYHSICAL ADDRESS跳过去就好,如果非全零就是不存在,表示这个平台使用的是Spec规定的某种default config,这个我们了解一下就好,还有值得提一下就是byte2里的bit7,标识IMCR是否存在以及目前的HW到底实现的是PIC Mode还是VirtualWire Mode。

processInstanceId 获取business process-specific_多处理器_04

    接下来我们顺着PHYSICAL ADDRESS跳到MPConfiguration Table来看看,先是一个Table Header, 介绍了一些基本信息,包含这个table由多少entry,OEM厂商是谁,以及Local APIC的地址在哪里。上期视频我们说过,Local APIC地址Spec建议落在FEE00000为base的这段区域里,但是我OS就是不想用这个地址怎么办,那就在这里面改。然后接下来的才是重头戏,后面的各种各样的Table Entry才能真正的描述整个MP系统的细节。

processInstanceId 获取business process-specific_多处理器_05

    有五种Entry,如table 4-3所示,先是ProcessorEntry, 一个processor一个,而且我们发现这个entry使用Local APIC ID做为区分Processor的标识,随后CPUFlags,只有两个bit,一个bit表征这颗Processor是否可用,一个bit表征这个Processor是不是BSP。理论上,如果BIOS不想让OS使用某个CPU,可以通过修改这个CPU Flag通知OS不要使用这份Entry对应的CPU;随后是BUSEntry,这个Entry用来通知OS,整个MP System里面存在多少个BUS,Spec里面列举了很多种,我们熟悉的也只有PCI和ISA了;接着是IO APIC Entry,表征系统里有多少个IO APIC,除了IOAPIC ID以外,Entry里还有表征这个IO APIC 是否可用的IOAPIC Flag。下一个就是IO Interrupt Assignment Entry,说白了就是描述中断线从设备端到IO APIC端是怎么连接的,每个Entry里都会包含中断源的BUS和IRQ以及目的地IO APIC的ID和INTIN;最后是Local Interrupt Assignment,顾名思义,就是用来描述Local APIC的中断引脚的连接情况的;多说无益,咱们还是通过业界良心RWEverying实际看看吧

    RW里很贴心的给我们把MP Table单独做了一个标签并且全部decode出来了,我们看一下咱们可以从这里面看到哪些系统信息。首先我们看MP FloatingPointer Structure, PHYSICAL Address里的地址和下面的MPConfiguration Table Header是对的上的,然后5个featurebyte都是0,说明IMCR不存在,那么目前这台电脑应该实现的是VirtualWire Mode;再看Processor Entry,有四个,说明有四颗CPU,;BUSEntry有6个,1个ISA,5个PCI;1个ISA好理解,我们只看到一个PCI转ISABridge,然后可以看到PCIBUSNumber一共有5个,刚好可以和5个PCIBUS Entry对应,但是要注意只是个数对应,BusNumber和Entry里的BusID不是同一套体系下的东西,所以对不上

IO APIC Entry只有一个,看起来只有一个IOAPIC;然后就是IO Interrupt Entry了,这里值得多说两句,大部分的内容大家应该都能理解,triggermode这些就不说了,destinationID和INTIN#也很好理解,难以理解的是SourceBUS IRQ这个概念。

processInstanceId 获取business process-specific_多处理器_06

    首先这个IRQ和我们通常讲的8259 IRQ有关联但是不是说完全一模一样的。如果SourceBUS是ISA,那么这里的IRQ和8259IRQ几乎等价,可以这样理解,某个IO Devcie,比如说KB,在PICModeor Virtual Wire Mode下连接的是8259的IRQ1,如果这里有一个IOAssignment Entry里包含一个ISABUS Source并且IRQ为1表明,KB的中断线会在对称IOMode下连接到IO APIC,目的地在Entry里的destinationID和INTIN指定,一般为了兼容也是连接到INTIN1上;对于PCIBUS来说情况就不一样了,要知道每个PCIBUS都有4根中断线INTA/B/C/D, 那么这里的IRQ的中间有5bit代表连接到IOAPIC的device ID,低两bit代表的是这个devcie里的四个中断线,是哪一根连接到了IOAPIC;我们使用RW实战看一下,先是Entry0,上来第一个就有点特殊,因为它的InterruptType是03,看起来这里是把8259的INTR连接到了IOAPIC的INTIN0上了,这是我们之前提过VirtualWire Mode的实现方式之一,然后是Entry1,和ISAIRQ1对应连接到了INTIN1,然后又是IRQ0,不过这次是正常INT了,相当于为了实现VirtualWire,INTR占用了INTIN0,那么总得给实际的IRQ0找好替代吧,但是为什么是INTIN2呢;那是因为,INTIN2的对位者IRQ2本来就被slave8259占用着,不会被连过来,所以把IRQ0放到INTIN2最合适不过了,如果这里有听不懂的同学下去以后恶补一下8259级联就好了。然后往下一直到Entry15都是ISAIRQ和INTIN一一对应的,那么ISA中断的连线就解决完了。接下了是PCI中断连线,我们看一个Entry16,来源于ID为0的PCIBUS,Device号是多少呢,我们算一下,然后我们再回到这个device的PCI ConfigSpace的3C和3D的位置看一下,3C是这个Device所使用的IRQNumber,3D是它使用的INTLine,看起来完全对的上。

然后我们来看一下LocalAssignment Entry,可以看到两个Entry的destinationID是全FF,表示连接到所有的LocalAPIC,两个Entry一个是NMI,另外一个是8259的INTR,看起来这是VirtualWire的另一种实现形式,但是为什么每个LocalAPIC都给连上了呢。回忆一下,INTR其实只需要和BSP连接上就行了,按理说连接一个就行了,在这里都给连上了,只能说明一个问题,那就是所有的processor都有可能做为BSP。我们可以去看一下P6family的MP协议初始化过程来验证一下我的想法。

processInstanceId 获取business process-specific_ci_07

    整个过程中P6的Local APIC需要发送三种IPI,分别是BIPI用于发起BSP选择的仲裁机制,FIPI用于发起BSP的BIOS初始化,和SIPI用于发起AP的初始化。然后我们按照时间顺序将一下整个MP协议初始化HW和BIOS需要做什么

    1.Reset引脚de-assert之后,根据拓扑结构,HW为每个localAPIC分配local APIC ID;

    2.每个processor分别同时做各自的BIST,当做完BIST,每个processor向包括自己在内的所有Processor广播一笔BIPI;

    3.APIC仲裁硬件将会导致所有的APIC每次只能回应一个BIPI;

    4.当第一次收到BIPI,每个APIC都会用自己的APICID和所收到BIPI vector的低4bit进行比较,如果相同,那么这个APIC通过将IA32_APIC_BASE MSR置位的方式将自己置为BSP,如果不同就将自己设定为AP,选为AP的processor将进入等待SIPI的状态;这种图里看起来P1被选为了BSP。说到这里大家是不就看出来了,这里谁做BSP完全取决于谁能先把BIPI发出来。有的同学肯定要琢磨了,搞这么麻烦干什么,干脆指定P0不就得了;我也同意,因为我们工程师习惯从0开始,但是大家有没有相过,如果P0的BIST没过的话,这颗chip是不是就彻底死了;相比之下,每次通过BIPI赛跑看起来更灵活一些;

    5.然后,BSP就会发起一笔FIPI开启自己初始化,但是硬件上回保证,当收到所有的BIPI后才会去回应这笔FIPI;

    6.当所有的BIPI都发出来,BSP也在T5开始处理这笔FIPI,会从resetvector,也就是FFFFFFF0H处取值执行boot-strapcode;

    7.做为boot-strapcode的一部分,BIOS要负责创建ACPI Table和MPTable, 并且把BSP自己的APIC ID房间Table里。这里插一句,这里创建这两个Table是为了兼容不同的OS,对于支持ACPI的OS,boot起来后使用ACPI table, 对于不支持ACPI的OS,比如说DOS,Boot起来后就使用MPTable.;

    8.在BSPboot-strap Code的最终解决,会让BSP广播一笔SIPI给AP,其实SIPI message里包含了指向AP初始化代码的vector;

    9.所有AP回应这笔SIPI的方式就是去竞争一个被BSP初始化过的信号量,竞争成功拿到信号量的AP就开始执行AP初始化代码,做为初始化的一部分,AP会将自己的APIC ID添加到ACPI/MP Table里面;在初始化代码的最后,会让AP执行一笔CLI关闭中断,随后AP就进入到了Halt状态;

    10.当所有的AP都执行完AP初始化代码之后,BSP为所有在system bus上的processor建立一个count,这是手册里直译过来的,我猜测应该还是去补充ACPI/MP Table里面的关于Count的条目,然后BSPBoot-Strap Code执行完毕,随后BSP开始执行OS的boot-strap code和start-upcode;

    11.当BSP在执行OS的boot-strapcode和start-up code过程中,AP一直保持Halt状态;在这种状态下,AP只会回应INIT#/SMI/NMI/Snoop/STPCLK#。