读书笔记-现代操作系统-8多处理机系统-8.1多处理机

来源:互联网 发布:azw3转mobi软件 编辑:程序博客网 时间:2024/05/17 13:11

多处理通常来说分为3类:

  1. 共享内存的多处理,多个cpu共享同一个储存器,单向通信时间2-10ns
  2. 消息传递多计算机,每个储存器局部对应一个cpu,且只能被该cpu访问,这些cpu通过互联网网络发送多字消息通信,典型通信时间10-50us
  3. 广域分布式系统,所有计算机都有自己的储存器,通过消息传递进系统通信,与2不同的是这里使用了完整的计算,典型的通信时间10-100ms
    这里写图片描述

8.1 多处理机

8.1.1 多处理机硬件

UMA(Uniform Memory Access)统一储存器访问
NUMA(Nonuniform Memory Access)非一致储存器访问。

1.基于总线的UMA多处理机体系结构

最简单模型是所有计算都使用同一个总线,这样的计算机出现的问题是如果某个cpu访问总线的时候,就必须阻塞,这样cpu需要消耗大量的时间在等待总线。

一个基本的改进就是为每个cpu添加高速缓存。但是如果这样需要保持高速缓存的一致性,所以需要有高速缓存一致性协议。

另一种改进是每个cpu不只有高速缓存还有一个本地的私有缓存。共享储存器只用于可写的共享变量。但是这样做需要特殊的编译器积极配合。

2.使用交叉开关的UMA多处理机

连接n个cpu到k个储存器的最简单的电路是交叉开关。
交叉开关最好的特性是一个非阻塞的网络。当然如果两个cpu同时争抢同一个模块的时候,还是需要对内存争夺的,但是概率可以降低至1/n。
最差的特性是交叉点的数量以n2方式增长。
这里写图片描述

3.使用多级交换的UMA多处理机
使用2*2的开关连接cpu和储存器,并且使用特殊的消息格式。

典型的模式:
消息格式分为四个部分:
1. Module(模块)指明使用哪个储存器。
2. Address(地址)指定在模块中的地址
3. Opcode(操作码)指定操作
4. Value(值)包含一个操作数

联系模式:
Omega网络:全混洗(perfect shuffle)模式
根据每一级的Module的位号确定出口的编号
需要注意的是Omega网络是一个阻塞网络。并不是每组请求都可被同时处理,冲突可以在一条联系或者一个开关中发生,也可以在对储存器的请求和来自储存器的应答中产生。
这里写图片描述

4.NUMA多处理机
有三个关键的特性:

  • 具有对所有CPU都可见的单个地址空间
  • 通过LOAD和STORE指令访问远程储存器
  • 访问远程储存器慢于访问本地储存器

对远程储存器的访问时间不被隐藏时(没有高速缓存)称为NC-NUMA(No Cache NUMA,无高速缓存NUMA
在有一致性高速缓存称为CC-NUMA Cache-Coherent NUMA,无高速缓存NUMA

构造大小CC-NUMA多处理机最常见的方法是基于目录的多处理机(directory-based multiprocessor),维护一个数据库来记录高速缓存行的位置及其状态。当一个高速缓存被引用的时候查询数据库。所有需要专门的配有极高速的硬件。

更具体的见P299。

5.多核芯片

将多个完整的cpu放到同一个芯片上,虽然cpu可能共享高速缓存或者不共享,但是他们都是共享内存的。
特殊的硬件电路可以确保在一个字(内存中的值)同时出现在两个或者多个高速缓存中的情况下,当其中某个cpu修改了该字,所有其他高速缓存中的该字都会被自动地并且原子性的删除来确保一致性。这个过程称为窥探(snooping)

实际上多核芯片市场被称为片级多处理机(Chip-level MultiProcessors,CMP)不过从软件的角度来看,CMP与基于总线的多处理机和使用交换网络的多处理并没有多大区别。

CMP和其他更大的多处理机之间的区别是容错。共享模块可能使多个CPU同时出错。
对于一类多核芯片被称为片上系统。(system on a chip)

8.1.2 多处理机操作系统类型

1. 每个CPU有自己的操作系统
优点:
运行所有的CPU共享操作系统的代码,而且只需要提供数据的私有副本。
这一机制比有n个分离的计算机要好,因为他允许所有的机器共享一套磁盘及其他的IO设备,它还允许灵活的共享储存器。
其他方面:

  1. 在一个进程进行系统调用时,该系统调用实在本机的cpu上被捕获并处理的,并使用操作系统表中的数据结构
  2. 因为每个操作系统都有自己的表,那么它也有自己的进程集合,通过自身调度这些进程。没有进程共享,会发生一个cpu进程满载,但是另一个cpu空转的情况
  3. 没有页面共享。CPU1不断的进行页面调度时,CPU2缺没有内存使用
  4. 可能会导致操作系统维护近期使用过的磁盘块的高速缓存,每个系统都有自己的高速缓存,每个系统独立工作,可能导致两个高速缓存冲突而导致不一致性。

2.主从多处理机
由CPU1运行主操作系统,并进行进程调配,主机操作系统可能成为性能的瓶颈。但是不会出现不一致的情形。

3.对此多处理机
Symmetric MultiProcessor,SMP
在储存器上有一个操作系统的副本,但任何CPU都可以运行它,再有系统调用时,进行系统调用的cpu陷入内核并处理系统调用。
这个模型动态的平衡进程和储存器,因为它只有一套操作系统数据表,它还消除了主cpu的瓶颈。
但是当两个cpu同时进行系统调用或者访问统一空闲储存器等情形的时候,所有需要使用信号锁等机制保障任何时候只有一个cpu可运行系统。

这样其实也存在问题就是对操作系统的占用会导致性能的瓶颈。一种方法是将操作系统分隔称相互不关联的多个临界区,这样就可以由多个cpu分别占用。现代多处理都采用这样的安排。这里操作系统编写起来比较复杂。

8.1.3多处理机同步

典型的做法对总线进行加锁。先使用通常的总线协议请求总线,并申明(设置一个逻辑1)已拥有某些特定的总线线路。直到两个周期全部完成。
这是所谓的自旋锁(spin lock),因为请求的cpu只是在原地尽可能块的对锁进行循环测试。这样做不仅完全浪费了提出请求的各个cpu的时间,而且还给总线或储存器增加了大量的负载,严重地降低了所有其他cpu从事正常的工作速度。

这里只有2种解决方案,而高速缓存并不能解决问题,因为高速缓存是以32或62自己的块中进行的:

  1. 如果能消除在请求一侧是所有由TSL引起的写操作,我们就可以明显的减少这种开销。使提出请求的cpu首先进行一个纯读操作来观察锁是否空闲,就可以实现这个目标。(只有锁是空闲的时候才尝试去获取它。)
  2. 使用以太网二进制指数补偿算法(binary exponential backoff algorithm),如果锁忙就延迟一个指令周期检测,如果还忙就在延迟两个指令周期再检测,如果还忙就延迟四个周期,以此类推。

另一种更好的解决方案是让每个打算获得互斥信号量的cpu都拥有各自用于测试的私有锁变量。给一个未能获得所的CPU分配一个锁变量并且把它附在等待该锁的cpu的链表的末端。类似于队列的思想。

自旋与切换

之前讨论的问题都是在进行阻塞的时候保持cpu等待,但是其实还有另一中选择就是将当前进程进行切换。但是切换需要的时间代价要大的多因此也需要一定的算法和经验才能判断。
一种方法是对所有有关的活动进行跟踪,再次基础上决定是切换还是自旋。这种称为事后算法(hindsight algorithm)称为对可行算法进行测试的基准评测标准。

8.1.4 多处理机调度

线程是内核线程还是用户线程至关重要。如果线程是由用户空间库区维护的,而对内核不可见,那么调度就是基于单进程的。
而对于内核线程来说,所有线程都是可见的。通常来说内核级线程是可用的

另一个复杂的问题是,有些线程是相关的。典型的比如说make。

1.分时
对线程分优先级,并且使用使用链表保存同一个优先级的线程。对多个cpu进行分时管理。如果有cpu空闲就按照优先级一次分配cpu。
但是有些问题需要额外的考量:

第一个是如果一个线程的时间片用完了,但是这个线程可能占有自旋锁,如果立刻切换会导致死锁或者其他进程被阻塞。所以可能会多给这类线程多一些时间片

另一个问题是当某个线程在某个cpu上运行过一段时间,那么再次切换到这个cpu时候,可能会运行更快,因为缓存tlb等可能还保有未失效的东西。因此调度时会考虑到亲和调度(affinity scheduling)。采用所谓的两级调度算法(two-level scheduling algorithm)在一个线程创建时,它被分配给一个cpu。

两级调度算法:
在一个线程创建时,它被分配给一个cpu。这种把线程分配给cpu的算法在顶层工作,其结果是每个cpu获得自己的线程集。
线程的实际调度进程在算法底层进行。它由每个cpu使用优先级或其他手段分别进行。
优点:
它把负载大致平均分配在可用的cpu上。
尽可能发挥高速缓存亲和力的优势
通过为每个cpu提供一个私有的就绪线程链表使得就绪线程列表的的竞争减到最小。

2.空间共享
在多个cpu上同时调度多个线程称为空间共享(space sharing)
对cpu进行分组,然后根据线程需要同时运行多个cpu,资源不够的话需要线程推迟。

3.群调度(Gang Scheduling)
空间共享的一个明显优点是消除了多道程序设计,从而消除了上下文切换的开销。
缺点是当cpu被阻塞或者根本无事可做的时间就被浪费了,只有等到再次就绪。
于是需要寻找可以调度时间又可以调度空间的算法。

群调度,是协同调度(co-scheduling)的发展产物。有三个部分组成:

  1. 把一组相关线程作为一个单位,即一个群,一起调度
  2. 一个群中的所有成员在不同的分时cpu上同时运行
  3. 群中的所有成员共同开始和结束其时间片

思想是让一个进程的所有线程一起运行,这样如果其中一个线程想另一个线程发送请求,接收方集合会立刻得到消息并立刻应答。

1 0