多处理器结构分类:以CPU之间的关系不同又可以分为对称和非对称多处理器结构。
SMP:在对称结构下,多个CPU的角色功能平等,没有主从之分,这种多CPU结构称为对称多处理器结构(Symmetric Multi-Processor Architecture,SMP)。
AMP:在非对称多处理器结构中,则不同CPU的角色地位不同,有主从CPU之分。这种多CPU结构称为非对称多处理器结构(Asymmetric Multi-ProcessorArchitecture,AMP)。
超线程技术:超线程技术是在一个CPU上同时执行多个程序而共享这个CPU内的资源,理论上要像两个CPU一样在同一时间执行两个线程,超线程技术可在同一时间里让应用程序使用芯片的不同部分。而为了支持这种技术,需要在处理器上多加入一个逻辑处理单元指针(Logical CPU Pointer)。
UMA:最简单的内存共享方式就是将内存作为与执行核独立的单元构建在核之外,所有的核通过同一总线对内存进行访问。由于每个核使用相同的方式访问内存,其到内存的延迟也相同,这种访问模式称为均匀内存访问(Uniform Memory Ac-cess,UMA)。
UMA的优缺点:在这种模式下,最重要的是所有核的地位在内存面前平等。其优点是设计简单,实现容易。缺点是大锅饭,难以针对个体的程序进行访问优化,以及扩展困难。随着执行核数量的增加,对共享内存的竞争将变得白热化,从而造成系统效率急剧下降
NUMA:使用多个分开的独立共享内存。每个执行核或CPU到达不同共享内存的距离不同,访问延迟也不一样。这种访问延迟不一致的内存共享模式称为非均匀内存访问(Non-Uniform MemoryAccess,NUMA)。在这种模式下,最重要的特点是执行核在不同的内存单元面前地位并不平等:到近的内存具有优势地位,到远的内存则处于劣势。
NUMA的优缺点:NUMA结构的优点是灵活性高、扩展容易。在执行核的数量增加的时候,其访问内存的效率可以保持不下降。不过,这种不下降的前提是优良的调度策略,即在调度时能够将程序就近执行。否则,有可能因内存访问距离远而造成效率下降。因此,NUMA对调度的要求很高。但因为扩展容易,所以NUMA得到了非常广泛的应用。
COMA:每个执行核里面配置缓存,其执行需要的数据均缓存在该缓存里面。所有访问由缓存得到满足。这样,不论数据原来是处于哪个内存单元,其对效率的影响均将不复存在。这种完全由缓存满足数据访问的模式称为全缓存内存访问(Cache Only Memory Access,COMA)。在这种模式下,每个执行核配备的缓存共同组成全局地址空间。
NORMA:如果内存单元为每个执行核所私有,且每个执行核只能访问自己的私有内存,对其他内存单元的访问通过消息传递进行,则就是非远程内存访问模式(Non-Re-mote MemoryAccess,NORMA)。这种模式的优点是设计比NUMA还要简单,但执行核之间的通信成本高昂。这已经有一点像网络了。因为效率问题,这种模式在多核体现结构下使用甚少。
对称多处理器计算机的启动顺序:这个顺序是固定的。所有的CPU里面有一个被定为启动处理器(Bootstrap Processor,BSP),而其他的处理器则作为应用处理器(ApplicationProcessor,AP)。到底哪个CPU是BSP则由某一特定寄存器的值来决定。
cpu之间通信机制:在多CPU之间通信,需要发送的是中断。用来协调这些CPU之间中断的机制就是高级可编程中断控制器(APIC)。CPU通过彼此发送中断(IPI,即处理器间中断)来完成它们之间的通信。通过给中断附加动作,不同的CPU可以在某种程度上彼此进行控制。
I/O APIC:系统中另外一个重要的部分为I/O APIC。系统中最多可拥有8个I/O APIC。它们会收集来自I/O装置的中断信号且在当那些装置需要中断时传送信息至本机APIC。除了处理处理器间及输入输出的中断外,APIC也负责处理本地中断源发出的中断,如本地连接的I/O设备、时序中断、性能监视计数器中断、高温中断、内部错误中断等。
SMP缓存一致性问题:于在对称多处理器结构下,每个处理器都有自己的缓存,因此在一个系统里面存在多个缓存的情况下就有可能出现两个缓存的数据不一致的情况。即两个CPU缓存同样的数据,其中一个或两个CPU对数据进行了修改从而造成两个CPU缓存数据的不同。而这有可能造成严重的后果。因此确保SMP里面的缓存一致性十分重要。(实现策略本书没有讨论)
多处理器、超线程和多核共享资源的区别:同时执行的两个线程之间共享物理资源,多处理器的共享物理资源最少,每个线程有自己单独的处理器;超线程共享最多,ALU、FPU、MSR、缓存等均为共享物理资源;而多核则介于两者之间,共享处理器,但不共享ALU、FPU等。
多核进程同步:多核之间为了保证数据的一致性,避免脏数据,主要通过硬件的原子操作达成。
总线锁:在多核环境下,还有一种硬件原子操作称为总线锁。总线锁就是将总线锁住,只有持有该锁的CPU才能使用总线。这样,由于所有CPU均需要使用共享总线来访问共享内存,而总线的锁住将使得其他CPU没有办法执行任何与共享内存有关的指令,从而使得数据的访问是排他的。
xchg:硬件提供的另外一种同步原语是交换指令,即xchg(exchange)。该指令可以以原子操作完成在寄存器和内存单元之间的内容置换。
Linux内核提供的原子操作包括如下几种:
- 总线锁:置换、比较与置换、原子递增操作。
- 原子算术操作:原子读、设置、加、减、递增、递减、递减与测试。
- 原子位操作:位设置、位清除、位测试与设置、位测试与清除、位测试与改变。
Windows内核提供的原子操作包括如下几种:
- 互锁操作(interlocked operation)
- 执行体互锁操作(executive interlocked operations)
旋锁:旋锁(spin lock)是几乎所有多核操作系统均提供的一种CPU互斥机制,是操作系统内核用于多处理器互斥的机制,即用户程序不能使用旋锁进行互斥。旋锁通常用于保护某个全局的数据结构,如Windows里面的DPC(延迟过程调用)队列。这里的互斥指的是多个处理器或执行核之间的互斥,即两个处理器或核不能(物理上)同时访问同一个数据结构;而不是第8章中讲过的多线程之间的互斥。
旋锁与普通锁的区别:旋锁的拥有者是CPU,而不是线程。因此,如果一个CPU获得一个旋锁,那么运行在该CPU上的所有的线程都可以访问该旋锁所保护的寄存器和数据结构。旋锁的作用与Win-dows API里面的mutex作用非常类似。
旋锁机制:旋锁是一个特定的内存单元。这个特定的内存单位必须位于整个系统的共享内存里面。这是旋锁的物理载体。如果一个处理器要使用旋锁,就必须检查这个特定内存单元的值。如果为0,则将其设置为1,表示获得该旋锁。如果为1,则表示该旋锁被其他处理器所占有,则在该旋锁上进行繁忙等待,即不停地循环,这也是叫旋锁的缘故。
旋锁的缺点:旋锁的问题是对总线的竞争,每个CPU在检查旋锁的状态时均需要使用系统总线来访问旋锁所在的共享全局内存单元。如果它要测试并设置这个全局内存单元,就需要使用内存总线。那么它不停地发信号到总线上,会造成对内存总线的竞争。这是效率非常低下的一种机制。这就是旋锁的缺点。
队列旋锁:队列旋锁的中心思想就是,需要旋锁的CPU不要到全局内存去SPIN,而是到自己的局部内存去SPIN。这样就可以排除对总线的竞争。在旋锁上排一个队,就表示哪些CPU要这个旋锁。释放的时候就去检查这个队列,交给队列里的第一个CPU。
调度域:调度域指的是一组CPU。而负载平衡就是在一个调度域里面的CPU之间进行平衡,使得它们执行的进程数相同或类似,或者它们的繁忙程度类似。
负载平衡的目标:负载平衡的目标是将进程均匀分配到每个CPU的就绪队列里面。在负载平衡时,我们需要找出最繁忙的调度域,在每个调度域里面找出最繁忙的队列,然后将任务从一个队列移动到另一个队列,或者另一个调度域。
关联线程的调度:如果一个应用被分解为多个线程,由于多个线程需要共享许多资源,这时需要将这些线程尽量分配到同一个处理器核上执行,以提升缓存命中率。