预计阅读本页时间:-
第8章 多处理机系统
从计算机诞生之日起,人们对更强计算能力的无休止的追求就一直驱使着计算机工业的发展。ENIAC可以完成每秒300次的运算,它一下子就比以往任何计算器都快1000多倍,但是人们并不满足。我们现在有了比ENIAC快数百万倍的机器,但是还有对更强大机器的需求。天文学家们正在了解宇宙,生物学家正在试图理解人类基因的含义,航空工程师们致力于建造更安全和速度更快的飞机,而所有这一切都需要更多的CPU周期。然而,即使有更多运算能力,仍然不能满足需求。
过去的解决方案是使时钟走得更快。但是,现在开始遇到对时钟速度的限制了。按照爱因斯坦的相对论,电子信号的速度不可能超过光速,这个速度在真空中大约是30cm/ns,而在铜线或光纤中约是20cm/ns。这在计算机中意味着10GHz的时钟,信号的传送距离总共不会超过2cm。对于100GHz的计算机,整个传送路径长度最多为2mm。而在一台1THz(1000GHz)的计算机中,传送距离就不足100µm了,这在一个时钟周期内正好让信号从一端到另一端并返回。
让计算机变得如此之小是可能的,但是这会遇到另一个基本问题:散热。计算机运行得越快,产生的热量就越多,而计算机越小就越难散热。在高端Pentium系统中,CPU的散热器已经比CPU自身还要大了。总而言之,从1MHz到1GHz需要的是更好的芯片制造工艺,而从1GHz到1THz则需要完全不同的方法。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
获得更高速度的一种处理方式是大规模使用并行计算机。这些机器有许多CPU,每一个都以“通常”的速度(在一个给定年份中的速度)运行,但是总体上会有比单个CPU强大得多的计算能力。具有1000个CPU的系统已经商业化了。在未来十年中,可能会建造出具有100万个CPU的系统。当然为了获得更高的速度,还有其他潜在的处理方式,如生物计算机,但在本章中,我们将专注于有多个普通CPU的系统。
在高强度的数据处理中经常采用高度并行计算机。如天气预测、围绕机翼的气流建模、世界经济模拟或理解大脑中药物-受体的相互作用等问题都是计算密集型的。解决这些问题需要多个CPU同时长时间运行。在本章中讨论的多处理机系统被广泛地用于解决这些问题以及在其他科学、工程领域中的类似问题。
另一个相关的进展是因特网不可思议地快速增长。因特网最初被设计为一个军用的容错控制系统的原型,然后在从事学术研究的计算机科学家中流行开来,并且在过去它已经获得了许多新用途。其中一种用途是,把全世界的数千台计算机连接起来,共同处理大型的科学问题。在某种意义上,一个包含有分布在全世界的1000台计算机的系统与在一个房间中有1000台计算机的系统之间没有差别,尽管这两个系统在延时和其他技术特征方面会有所不同。在本章中我们也将讨论这些系统。
假如有足够多的资金和足够大的房间,把一百万台无关的计算机放到一个房间中很容易做到。把一百万台无关的计算机放到全世界就更容易了,因为不存在第二个问题了。当要在一个房间中使这些计算机相互通信,以便共同处理一个问题时,问题就出现了。结果,人们在互连技术方面做了大量工作,而且不同的互连技术已经导致了不同性质的系统以及不同的软件组织。
在电子(或光学)部件之间的所有通信,归根结底是在它们之间发送消息——具有良好定义的位串(bit string)。其差别在于所涉及的时间范围、距离范围和逻辑组织。一个极端的例子是共享存储器多处理机,系统中有从2个到1000个的CPU通过一个共享存储器通信。在这个模型中,每个CPU可同样访问整个物理存储器,可使用指令LOAD和STORE读写单个的字。访问一个存储器字通常需要2~10ns。尽管这个模型,如图8-1a所示,看来很简单,但是实际上要实现它并不那么简单,而且通常涉及底层大量的消息传递,这一点我们会简要地加以说明。不过,该消息传递对于程序员来说是不可见的。
其次是图8-1b中的系统,许多CPU-存储器通过某种高速互连网络连接在一起。这种系统称为消息传递型多计算机。每个存储器局部对应一个CPU,且只能被该CPU访问。这些CPU通过互连网络发送多字消息通信。存在良好的连接时,一条短消息可在10~50µs之内发出,但是这仍然比图8-1a中系统的存储器访问时间长。在这种设计中没有全局共享的存储器。多计算机(消息传递系统)比(共享存储器)多处理机系统容易构建,但是编程比较困难。可见,每种类型各有其优点。

第三种模型参见图8-1c,所有的计算机系统都通过一个广域网连接起来,如因特网,构成了一个分布式系统(distributed system)。每台计算机有自己的存储器,当然,通过消息传递进行系统通信。图8-1b和图8-1c之间真正惟一的差别是,后者使用了完整的计算机而且消息传递时间通常需要10~100ms。如此长的延迟造成使用这类松散耦合系统的方式和图8-1b中的紧密耦合系统不同。三种类型的系统在通信延迟上各不相同,分别有三个数量级的差别。类似于一天和三年的差别。
本章有四个主要部分,分别对应于图8-1中的三个模型再加上虚拟化技术(一种通过软件创造出更多虚拟CPU的方法)。在每一部分中,我们先简要地介绍相关的硬件。然后,讨论软件,特别是与这种系统类型有关的操作系统问题。我们会发现,每种情况都面临着不同的问题并且需要不同的解决方法。
8.1 多处理机
共享存储器多处理机(或以后简称为多处理机,multiprocessor)是这样一种计算机系统,其两个或更多的CPU全部共享访问一个公用的RAM。运行在任何一个CPU上的程序都看到一个普通(通常是分页)的虚拟地址空间。这个系统惟一特别的性质是,CPU可对存储器字写入某个值,然后读回该字,并得到一个不同的值(因为另一个CPU改写了它)。在进行恰当组织时,这种性质构成了处理器间通信的基础:一个CPU向存储器写入某些数据而另一个读取这些数据。
至于最重要的部分,多处理机操作系统只是通常的操作系统。它们处理系统调用,进行存储器管理,提供文件系统并管理I/O设备。不过,在某些领域里它们还是有一些独特的性质。这包括进程同步、资源管理以及调度。下面首先概要地介绍多处理机的硬件,然后进入有关操作系统的问题。
8.1.1 多处理机硬件
所有的多处理机都具有每个CPU可访问全部存储器的性质,而有些多处理机仍有一些其他的特性,即读出每个存储器字的速度是一样快的。这些机器称为UMA(Uniform Memory Access,统一存储器访问)多处理机。相反,NUMA(Nonuniform Memory Access,非一致存储器访问)多处理机就没有这种特性。至于为何有这种差别,稍后会加以说明。我们将首先考察UMA多处理机,然后讨论NUMA多处理机。
1.基于总线的UMA多处理机体系结构
最简单的多处理机是基于单总线的,参见图8-2a。两个或更多的CPU以及一个或多个存储器模块都使用同一个总线进行通信。当一个CPU需要读一个存储器字(memory word)时,它首先检查总线忙否。如果总线空闲,该CPU把所需字的地址放到总线上,发出若干控制信号,然后等待存储器把所需的字放到总线上。
当某个CPU需要读写存储器时,如果总线忙,CPU只是等待,直到总线空闲。这种设计存在问题。在只有两三个CPU时,对总线的争夺还可以管理;若有32个或64个CPU时,就不可忍受了。这种系统完全受到总线带宽的限制,多数CPU在大部分时间里是空闲的。
这一问题的解决方案是为每个CPU添加一个高速缓存(cache),如图8-2b所示。这个高速缓存可以位于CPU芯片的内部、CPU附近、在处理器板上或所有这三种方式的组合。由于许多读操作可以从本地高速缓存上得到满足,总线流量就大大减少了,这样系统就能够支持更多的CPU。一般而言,高速缓存不以单个字为基础,而是以32字节或64字节块为基础。当引用一个字时,它所在的整个数据块(叫做一个cache行)被取到使用它的CPU的高速缓存当中。

每一个高速缓存块或者被标记为只读(在这种情况下,它可以同时存在于多个高速缓存中),或者标记为读写(在这种情况下,它不能在其他高速缓存中存在)。如果CPU试图在一个或多个远程高速缓存中写入一个字,总线硬件检测到写,并把一个信号放到总线上通知所有其他的高速缓存。如果其他高速缓存有个“干净”的副本,也就是同存储器内容完全一样的副本,那么它们可以丢弃该副本并让写者在修改之前从存储器取出高速缓存块。如果某些其他高速缓存有“脏”(被修改过)副本,它必须在处理写之前把数据写回存储器或者把它通过总线直接传送到写者上。高速缓存这一套规则被称为高速缓存一致性协议,它是诸多协议之一。
还有另一种可能性就是图8-2c中的设计,在这种设计中每个CPU不止有一个高速缓存,还有一个本地的私有存储器,它通过一条专门的(私有)总线访问。为了优化使用这一配置,编译器应该把所有程序的代码、字符串、常量以及其他只读数据、栈和局部变量放进私有存储器中。而共享存储器只用于可写的共享变量。在多数情况下,这种仔细的放置会极大地减少总线流量,但是这样做需要编译器的积极配合。
2.使用交叉开关的UMA多处理机
即使有最好的高速缓存,单个总线的使用还是把UMA多处理机的数量限制在16至32个CPU。要超过这个数量,需要不同类型的互连网络。连接n个CPU到k个存储器的最简单的电路是交叉开关,参见图8-3。交叉开关在电话交换系统中已经采用了几十年,用于把一组进线以任意方式连接到一组出线上。

水平线(进线)和垂直线(出线)的每个相交位置上是一个交叉点(crosspoint)。交叉点是一个以电子方式开关的小开关,具体取决于水平线和垂直线是否需要连接。在图8-3a中我们看到有三个交叉点同时闭合,允许(CPU,存储器)对(010,000)、(101,101)和(110,010)同时连接。其他的连接也是可能的。事实上,组合的数量等于象棋盘上8个棋子安全放置方式的数量(8皇后问题)。
交叉开关最好的一个特性是它是一个非阻塞网络,即不会因有些交叉点或连线已经被占据了而拒绝连接(假设存储器模块自身是可用的)。而且并不需要预先的规划。即使已经设置了7个任意的连接,还有可能把剩余的CPU连接到剩余的存储器上。
当然,当两个CPU同时试图访问同一个模块的时候,对内存的争夺还是可能的。不过,通过将内存分为n个单元,与图8-2的模型相比,这样的争夺概率可以降至1/n。
交叉开关最差的一个特性是,交叉点的数量以n2 方式增长。若有1000个CPU和1000个存储器我们就需要一百万个交叉点。这样大数量的交叉开关是不可行的。不过,无论如何对于中等规模的系统而言,交叉开关的设计是可用的。
3.使用多级交换的UMA多处理机
有一种完全不同的、基于简单2×2开关的多处理机设计,参见图8-4a。这个开关有两个输入和两个输出。到达任意一个输入线的消息可以被交换至任意一个输出线上。就我们的目标而言,消息可由四个部分组成,参见图8-4b。Module(模块)域指明使用哪个存储器。Address(地址)域指定在模块中的地址。Opcode(操作码)给定了操作,如READ或WRITE。最后,在可选的Value(值)域中可包含一个操作数,比如一个要被WRITE写入的32位字。该开关检查Module域并利用它确定消息是应该送给X还是发送给Y。

这个2×2开关可有多种使用方式,用以构建大型的多级交换网络(Adams等人,1987;Bhuyan等人,1989;Kuman和Reddy,1987)。有一种是简单经济的Omega网络,见图8-5。这里采用了12个开关,把8个CPU连接到8个存储器上。推而广之,对于n个CPU和n个存储器,我们将需要log2 n级,每级n/2个开关,总数为(n/2)log2 n个开关,比n2 个交叉点要好得多,特别是当n值很大时。
Omega网络的接线模式常被称作全混洗(perfect shuffle),因为每一级信号的混合就像把一副牌分成两半,然后再把牌一张张混合起来。接着看看Omega网络是如何工作的,假设CPU 011打算从存储器模块110读取一个字。CPU发送READ消息给开关1D,它在Module域包含110。1D开关取110的首位(最左位)并用它进行路由处理。0路由到上端输出,而1的路由到下端,由于该位为1,所以消息通过低端输出被路由到2D。
所有的第二级开关,包括2D,取用第二个比特位进行路由。这一位还是1,所以消息通过低端输出转发到3D。在这里对第三位进行测试,结果发现是0。于是,消息送往上端输出,并达到所期望的存储器110。该消息的路径在图8-5中由字母a标出。

在消息通过交换网络之后,模块号的左端的位就不再需要了。它们可以有很好的用途,可以用来记录入线编号,这样,应答消息可以找到返回路径。对于路径a,入线编号分别是0(向上输入到1D)、1(低输入到2D)和1(低输入到3D)。使用011作为应答路由,只要从右向左读出每位即可。
在上述这一切进行的同时,CPU 001需要往存储器001里写入一个字。这里发生的情况与上面的类似,消息分别通过上、上、下端输出路由,由字母b标出。当消息到达时,从Module域读出001,代表了对应的路径。由于这两个请求不使用任何相同的开关、连线或存储器模块,所以它们可以并行工作。
现在考虑如果CPU 000同时也请求访问存储器模块000会发生什么情况。这个请求会与CPU 001的请求在开关3A处发生冲突。它们中的一个就必须等待。和交叉开关不同,Omega网络是一种阻塞网络,并不是每组请求都可被同时处理。冲突可在一条连线或一个开关中发生,也可在对存储器的请求和来自存储器的应答中产生。
显然,很有必要在多个模块间均匀地分散对存储器的引用。一种常用的技术是把低位作为模块号。例如,考虑一台经常访问32位字的计算机中面向字节的地址空间,低位通常是00,但接下来的3位会均匀地分布。将这3位作为模块号,连续的字会放在连续的模块中。而连续字被放在不同模块里的存储器系统被称作交叉(interleaved)存储器系统。交叉存储器将并行运行的效率最大化了,这是因为多数对存储器的引用是连续编址的。设计非阻塞的交换网络也是有可能的,在这种网络中,提供了多条从每个CPU到每个存储器的路径,从而可以更好地分散流量。
4.NUMA多处理机
单总线UMA多处理机通常不超过几十个CPU,而交叉开关或交换网络多处理机需要许多(昂贵)的硬件,所以规模也不是那么大。要想超过100个CPU还必须做些让步。通常,一种让步就是所有的存储器模块都具有相同的访问时间。这种让步导致了前面所说的NUMA多处理机的出现。像UMA一样,这种机器为所有的CPU提供了一个统一的地址空间,但与UMA机器不同的是,访问本地存储器模块快于访问远程存储器模块。因此,在NUMA机器上运行的所有UMA程序无须做任何改变,但在相同的时钟速率下其性能不如UMA机器上的性能。
所有NUMA机器都具有以下三种关键特性,它们使得NUMA机器与其他多处理机相区别:
1)具有对所有CPU都可见的单个地址空间。
2)通过LOAD和STORE指令访问远程存储器。
3)访问远程存储器慢于访问本地存储器。
在对远程存储器的访问时间不被隐藏时(因为没有高速缓存),系统被称为NC-NUMA(No Cache NUMA,无高速缓存NUMA)。在有一致性高速缓存时,系统被称为CC-NUMA(Cache-Coherent NUMA,高速缓存一致NUMA)。
目前构造大型CC-NUMA多处理机最常见的方法是基于目录的多处理机(directory-based multiprocessor)。其基本思想是,维护一个数据库来记录高速缓存行的位置及其状态。当一个高速缓存行被引用时,就查询数据库找出高速缓存行的位置以及它是“干净”的还是“脏”(被修改过)的。由于每条访问存储器的指令都必须查询这个数据库,所以它必须配有极高速的专用硬件,从而可以在一个总线周期的几分之一内作出响应。
要使基于目录的多处理机的想法更具体,让我们考虑一个简单的(假想)例子,一个256个节点的系统,每个节点包括一个CPU和通过局部总线连接到CPU上的16MB的RAM。整个存储器有232 字节,被划分成226 个64字节大小的高速缓存行。存储器被静态地在节点间分配,节点0是0~16M,节点1是16~32M,以此类推。节点通过互连网络连接,参见图8-6a。每个节点还有用于构成其224 字节存储器的218 个64字节高速缓存行的目录项。此刻,我们假定一行最多被一个高速缓存使用。
为了了解目录是如何工作的,让我们跟踪引用了一个高速缓存行的发自CPU 20的LOAD指令。首先,发出该指令的CPU把它交给自己的MMU,被翻译成物理地址,比如说,0x24000108。MMU将这个地址拆分为三个部分,如图8-6b所示。这三个部分按十进制是节点36、第4行和偏移量8。MMU看到引用的存储器字来自节点36,而不是节点20,所以它把请求消息通过互连网络发送到该高速缓存行的的主节点(home node)36上,询问行4是否被高速缓存,如果是,高速缓存在何处。

当请求通过互连网络到达节点36时,它被路由至目录硬件。硬件检索其包含218 个表项的目录表(其中的每个表项代表一个高速缓存行)并解析到项4。从图8-6c中,我们可以看到该行没有被高速缓存,所以硬件从本地RAM中取出第4行,送回给节点20,更新目录项4,指出该行目前被高速缓存在节点20处。
现在来考虑第二个请求,这次访问节点36的第2行。在图8-6c中,我们可以看到这一行在节点82处被高速缓存。此刻硬件可以更新目录项2,指出该行现在在节点20上,然后送一条消息给节点82,指示把该行传给节点20并且使其自身的高速缓存无效。注意,即使一个所谓“共享存储器多处理机”,在下层仍然有大量的消息传递。
让我们顺便计算一下有多少存储器单元被目录占用。每个节点有16 MB的RAM,并且有218 个9位的目录项记录该RAM。这样目录上的开支大约是9×218 位除以16 MB,即约1.76%,一般而言这是可接受的(尽管这些都是高速存储器,会增加成本)。即使对于32字节的高速缓存行,开销也只有4%。至于128字节的高速缓存行,它的开销不到1%。
该设计有一个明显的限制,即一行只能被一个节点高速缓存。要想允许一行能够在多个节点上被高速缓存,我们需要某种对所有行定位的方法,例如,在写操作时使其无效或更新。要允许同时在若干节点上进行高速缓存,有几种选择方案,不过对它们的讨论已超出了本书的范围。
5.多核芯片
随着芯片制造技术的发展,晶体管的体积越来越小,从而有可能将越来越多的晶体管放入一个芯片中。这个基于经验的发现通常称为摩尔定律(Moore's Law),得名于首次发现该规律的Intel公司创始人之一Gordon Moore。Intel Core 2 Duo系列芯片已包含了3亿数量级的晶体管。
随之一个显而易见的问题是:“你怎么利用这些晶体管?”按照我们在第1.3.1小节的讨论,一个选择是给芯片添加数兆字节的高速缓存。这个选择是认真的,带有4兆字节片上高速缓存的芯片现在已经很常见,并且带有更多片上高速缓存的芯片也即将出现。但是到了某种程度,再增加高速缓存的大小只能将命中率从99%提高到99.5%,而这样的改进并不能显著提升应用的性能。
另一个选择是将两个或者多个完整的CPU,通常称为核(core),放到同一个芯片上(技术上来说是同一个小硅片)。双核和四核的芯片已经普及,八十核的芯片已经被制造出来,而带有上百个核的芯片也即将出现。
虽然CPU可能共享高速缓存或者不共享(如图1-8所示),但是它们都共享内存。考虑到每个内存字总是有惟一的值,这些内存是一致的。特殊的硬件电路可以确保在一个字同时出现在两个或者多个的高速缓存中的情况下,当其中某个CPU修改了该字,所有其他高速缓存中的该字都会被自动地并且原子性地删除来确保一致性。这个过程称为窥探(snooping)。
这样设计的结果是多核芯片就相当于小的多处理机。实际上,多核芯片时常被称为片级多处理机(Chip-level MultiProcessors,CMP)。从软件的角度来看,CMP与基于总线的多处理机和使用交换网络的多处理机并没有太大的差别。不过,它们还是存在着若干的差别。例如,对基于总线的多处理机,每个CPU拥有自己的高速缓存,如图8-2b以及图1-8b的AMD设计所示。在图1-8a所示的Intel使用的共享高速缓存的设计并没有出现在其他的多处理机中。共享二级高速缓存会影响性能。如果一个核需要很多高速缓存空间,而另一个核不需要,这样的设计允许它们各自使用所需的高速缓存。但另一方面,共享高速缓存也让一个贪婪的核损害其他核的性能成为了可能。
CMP与其他更大的多处理机之间的另一个差异是容错。因为CPU之间的连接非常紧密,一个共享模块的失效可能导致许多CPU同时出错。而这样的情况在传统的多处理机中是很少出现的。
除了所有核都是对等的对称多核芯片之外,还有一类多核芯片被称为片上系统(system on a chip)。这些芯片含有一个或者多个主CPU,但是同时还包含若干个专用核,例如视频与音频解码器、加密芯片、网络接口等。这些核共同构成了完整的片上计算机系统。
正如过去已经发生的,硬件的发展常常领先于软件。多核的时代已经来临,但是我们还不具备为它们编写应用程序的能力。现有的编程语言并不适合编写高度并行的代码,同时适用的编译器和调试工具还很匮乏。几乎没有几个程序员有编写并行程序的经验,而大部分程序员对于如何将工作划分为若干可以并行执行的块(package)知之甚少。同步、消除竞争、避免死锁成为了程序员的噩梦,同时也影响到了性能。信号量(semaphore)并不能解决问题。除了这些问题,什么样的应用真的需要使用数百个核尚不明确。自然语言语音识别可能需要大量的计算能力,但这里的问题并不是缺少时钟周期,而是缺少可行的算法。简而言之,或许硬件开发人员正在发布软件开发人员不知道如何使用而用户也并不需要的产品。