10.4 进程同步
在多处理机系统中,进程间的同步变得尤为重要和复杂。这主要是因为,在这样的系统中,不同处理器上运行的进程需要协调工作,以保持数据的一致性和避免潜在的冲突。本节将探讨进程同步的不同机制,特别是在紧密耦合和松散耦合多处理机系统中的应用。
紧密耦合与松散耦合的同步
在紧密耦合多处理机系统中,所有处理器共享同一个存储系统。这种架构简化了进程同步的实现,因为所有进程都可以直接通过共享存储来协调彼此的行为。相比之下,松散耦合系统中的进程同步更加复杂,因为进程分布在不同的处理器上,这些处理器可能没有共享存储,或者共享存储的使用受到限制。
集中式与分布式同步方式
10.4.1 集中式同步
1. 中心同步实体
集中式同步依赖于一个或多个中心同步实体,如硬件锁、信号量或进程,来协调进程间的同步。一个有效的中心同步实体应具备唯一性和可访问性。为了提高可靠性,系统可能采用容错技术,如出现故障时能够迅速切换到备选的中心同步实体。
2. 集中式同步机构
在集中式同步机构中,所有的同步操作都通过中心同步实体来协调。这种方式在单处理器系统中较为常见,但在多处理器系统中也有应用,尤其是当需要同步对共享数据的访问时。集中式同步机构能够简化同步逻辑,但也可能成为系统的性能瓶颈。
10.4.2 分布式同步
分布式同步不依赖单一的中心同步实体,而是采用多个协同工作的实体来实现进程间的同步。这种方式的优点在于增强了系统的可靠性和可扩展性,因为它避免了单点故障和性能瓶颈的问题。然而,设计和实现分布式同步算法通常比集中式同步更加复杂。
1. 同步算法
无论是集中式还是分布式同步,都需要相应的同步算法来支持。集中式同步算法依赖中心控制节点来决策,而分布式同步算法则要求每个节点基于本地信息进行决策,这要求算法能够在缺乏全局视图的情况下工作。
10.4.3 中心进程方式
中心进程方式是一种特殊的集中式同步机制,通过一个中心进程来协调所有对共享资源的访问请求。这种方式简化了进程同步的管理,但也需要有效地处理死锁和资源分配问题,以保证系统的高效运行。
总结
进程同步是多处理机操作系统设计中的一个核心问题。无论是采用集中式还是分布式同步方式,目标都是确保系统中的多个进程可以协调地执行,同时保持数据的一致性和系统的稳定性。在设计同步机制时,需要综合考虑系统的结构、可靠性需求以及性能目标,以实现高效且可靠的同步解决方案。
10.4.2 自旋锁(Spin Lock)
1. 自旋锁的引入
在多处理器系统中,由于多个CPU可能同时访问共享资源,传统的读-修改-写操作无法保证操作的原子性。这是因为,这些操作可能涉及多个指令,需要在共享的总线上执行多次操作,而在多处理器系统中,总线是共享资源,处理器之间通过竞争来获得总线使用权。因此,需要引入机制来实现对总线的互斥访问,自旋锁就是这样一种机制。
2. 实现对总线互斥访问的方法
自旋锁通过在总线上设置一个锁来实现互斥访问。当一个内核进程需要对某个存储单元进行读写操作时,它首先尝试获取自旋锁。如果锁已被其他进程占用,该进程将不断循环(即“自旋”),检查锁的状态,直到锁变为可用。这种机制可以有效防止多个内核进程同时访问共享资源,从而避免资源竞争。
3. 自旋锁与信号量的主要差别
自旋锁和信号量的主要区别在于自旋锁避免了进程的阻塞。使用自旋锁的进程在等待锁释放时,通过忙等(即自旋)而不是睡眠,从而避免了进程切换的开销和可能导致的缓存失效问题。这使得自旋锁在需要短时间访问共享资源的场景下,尤其是在多处理器环境中,效率较高。然而,如果临界区较大或锁的持有时间较长,则使用自旋锁可能会导致CPU资源的浪费。
4. 自旋锁的类型
在实际应用中,自旋锁的使用形式为:
spin_lock(&lock);
// 临界区代码
spin_unlock(&lock);
常见的自旋锁类型包括:
- 普通自旋锁:这是最基本的自旋锁类型,它不会影响当前处理器的中断状态,通常用于临界区代码不能被中断处理程序执行的场景。
- 读写自旋锁:允许多个读操作同时进行,但在写操作进行时,不允许其他读操作或写操作。这种锁提供了比普通自旋锁更高的并发性。
- 大读者自旋锁:在获取读锁时,只需要对本地读锁加锁,而在获取写锁时,则需要锁住所有CPU上的读锁,因此获取写锁的代价较高。
自旋锁适用于临界区小、锁持有时间短的场景。在单CPU系统中,自旋锁的操作可以是空操作,因为不需要担心并发访问的问题。在多CPU系统或内核可抢占的环境下,自旋锁是实现互斥的重要机制。
10.4.3 读—拷贝—修改锁和二进制指数补偿算法
1. 读—拷贝—修改锁 (RCU) 的引入
在处理并发读写问题时,传统的读写锁允许多个读操作同时进行,但只要有一个写操作在执行,就会阻止所有读操作,这在写操作耗时长时会严重影响读操作的效率。为改善这种情况,引入了读—拷贝—修改 (RCU) 锁机制。该机制允许写操作通过创建数据的副本并对其进行修改来进行,而读操作可以继续访问原数据,不受写操作的影响。
2. RCU (Read-Copy-Update) 锁
RCU 锁通过允许读操作无需获取锁即可访问被保护的共享资源,而写操作则通过复制一个资源副本并对此副本进行修改来实现。修改完成后,采用回调机制将修改应用回原数据结构,从而不阻塞读操作。这种机制大大提高了并发读操作的效率。
3. 写回时机
在 RCU 机制中,确定修改后内容写回的时机至关重要。理想情况下,所有读操作完成后再进行写回,以确保不干扰正在进行的读操作。每个读操作完成后需向写操作发送信号,表明其不再使用该数据结构。当所有读操作均已结束,即所有CPU都不再访问该共享数据时,写操作可以将修改后的数据写回,这段时间被称为写延迟期 (grace period)。
4. RCU 锁的优点
RCU 锁的引入带来了显著的性能提升,尤其是在读操作频繁而写操作较少的场景中:
- 读操作不被阻塞:RCU 机制确保了读操作可以无阻塞地访问数据,极大提高了读操作的效率并减少了CPU上下文切换的开销。
- 无需为共享数据设置同步机制:RCU 允许多个读操作和一个或多个写操作同时进行,无需为共享数据设置额外的同步机制,从而降低了同步开销并避免了死锁问题。
尽管 RCU 提供了对读操作的显著优化,但它并不总是替代传统读写锁的最佳选择。在写操作频繁的场景中,写操作所需的同步开销和对副本的修改可能会抵消对读操作的性能提升,此时可能更适合使用传统的读写锁。
二进制指数补偿算法
虽然原文没有直接提及二进制指数补偿算法,但这种算法通常用于处理冲突或重试场景,如网络通信中的退避算法,或在处理并发操作中的冲突重试。它通过动态调整等待时间,以指数方式增长,从而减少重试或冲突的可能性。在并发控制和同步机制设计中,这种算法有助于平衡系统的性能与资源利用率,尤其是在高并发环境下。
10.4.4 二进制指数补偿算法和待锁CPU等待队列机构
1. 二进制指数补偿算法
二进制指数补偿算法主要用于减少多个CPU在互斥访问共享数据结构时对总线流量的影响。当一个CPU尝试获取已被占用的锁时,它会根据二进制指数补偿算法延迟再次尝试的时间,这个延迟时间随每次尝试失败而指数增加。例如,如果第一次尝试失败,则下一次尝试将在 2121 个指令执行周期后进行;如果这次也失败,下次尝试将在 2222 个周期后进行,以此类推,直到达到设定的最大延迟时间。这种方法有效地降低了锁请求的冲突率和总线的数据流量,但可能导致锁的释放不被及时发现,从而浪费CPU资源。
2. 待锁CPU等待队列机构
为了及时发现锁空闲的状态并减少总线流量,引入了锁等待队列机构。这一机构通过为每个CPU分配一个私有的锁变量,并将这些CPU按请求锁的顺序组织成一个等待队列来实现。当共享数据结构被占用时,后续请求锁的CPU将其私有锁变量加入到当前持有锁的CPU的等待队列中。一旦持锁CPU释放锁,它会直接通知队列中的下一个CPU,允许其进入临界区,从而避免了不必要的总线访问和延迟。
这种机制不仅减少了总线流量,而且保证了一旦锁被释放,等待的CPU能够迅速得到响应,有效避免了资源的浪费。每个等待的CPU在自己的高速缓存中测试私有的锁变量,不影响总线流量。通过这种方式,系统能够更高效地管理锁竞争,提高了多处理器环境下的性能和资源利用率。
10.4.5 定序机构
在多处理机系统和分布式系统中,为了保证各处理机上的进程能够协调运行,需要对系统中的所有特定事件进行排序。这就引入了定序机构的概念,其目的是通过全局一致的方法来排序系统中的事件,以支持进程同步和协调操作。
1. 时间邮戳定序机构 (Timestamp Ordering Mechanism)
时间邮戳定序机构基于一个简单而强大的原则:为系统中的每一个特定事件分配一个唯一的时间标签(或时间邮戳),这些时间邮戳由一个全系统共享的、同步的物理时钟产生。这种机构要求:
- 系统内所有特殊事件,如资源请求、通信等,都被加上时间邮戳。
- 每一种特殊事件都使用唯一的时间邮戳。
- 事件上的时间邮戳用于定义所有事件的全序关系。
通过这种方式,可以实现不同处理机之间的进程同步,无论是在集中式系统还是分布式系统中。
2. 事件计数 (Event Counts) 同步机构
事件计数同步机构利用了一个叫作定序器 (Sequencers) 的整型量来为所有特定事件排序。定序器从0开始,且值只增不减,通过执行ticket(S)操作进行递增。当一个事件发生时,系统为其分配一个序号V,然后自动将ticket加1,形成一个递增的整数序列。事件被标号后,送至等待服务的队列中排队。
在这个机构中,维护了一个事件计数E,它是一个栈,保存了已发生的某特定类型事件的编号计数。对于事件计数,主要有三种操作:
- await(E,V): 在进程尝试进入临界区之前执行。如果E小于V,则将进程插入到等待队列EQ中,并进行重新调度;否则进程继续执行。
- advance(E): 在进程退出临界区时执行,使E值增加1。如果等待队列EQ不为空,则进一步检查队首进程的V值;如果E等于V,则唤醒该进程。
- read(E): 返回E的当前值,提供给进程参考,以决定是否处理其他事件。
这三个操作允许在同一事件上并发执行,但对定序器的使用必须是互斥的。
总结
定序机构在多处理机和分布式系统中扮演着关键角色,它通过为系统中的事件赋予全局一致的顺序来支持进程间的同步和协调。无论是基于时间邮戳的定序机构还是基于事件计数的同步机构,它们都为系统中事件的有序进行和进程同步提供了有效的方法。
10.4.6 面包房算法
面包房算法是一个经典的分布式进程同步算法,它使用事件排序的方法来处理对临界资源的访问请求,确保这些请求按照先来先服务(FCFS)的顺序得到处理。这种算法通过一个简单的比喻——顾客在面包店按顺序购买面包,来形象地描述其工作原理。
基本假设
- 系统结构:系统由N个结点组成,每个结点运行一个进程,负责控制一个临界资源,并处理到达的请求。
- 进程队列:每个进程维护一个队列,记录收到的消息和自产生的消息,初始为空。
- 消息类型:消息分为请求(request)、应答(reply)和撤销(release)三种类型,请求消息在队列中按事件时序排序。
算法描述
- 资源请求:当进程Pi需要访问资源时,它将请求消息
request(Ti, i)
放入自己的队列,并将此消息发送给系统中的其他进程。这里的Ti
是发送消息时的逻辑时钟值,i
是进程标识符。 - 消息接收与回应:进程Pj在接收到来自Pi的请求消息
request(Ti, i)
后,会发送回答消息reply(Tj, j)
回Pi,并将request(Ti, i)
消息加入自己的队列。如果Pj在接收到request(Ti, i)
之前已经提出了对相同资源的访问请求,则它的时间戳应该小于(Ti, i)
。 - 资源访问条件:进程Pi被允许访问资源(即进入临界区)的条件是:
- Pi自己的资源访问请求消息在请求队列中处于最前面;
- Pi已经收到了来自所有其他进程的回答消息,这些消息的时间戳都晚于
(Ti, i)
。
- 资源释放:为了释放资源,Pi从自己的队列中撤销请求消息,并向其他进程发送带时间戳的释放消息
release
。 - 处理释放消息:当进程Pj接收到Pi的释放消息后,它会从自己的队列中撤销Pi的请求消息
request(Ti, i)
。
特点与应用
面包房算法的核心优势在于其简单和有效性,它能够保证在分布式环境中对临界资源的访问是有序的,并遵循先来先服务的原则。这种算法不仅适用于分布式系统中的进程同步,也可以应用于多线程环境下的资源管理和控制。其主要特点包括无需使用物理时钟同步机制和能够有效处理并发请求,确保了系统的公平性和效率。
10.4.7 令牌环算法
令牌环算法是一种经典的分布式同步算法,主要用于管理和协调分布式系统中对共享资源的访问。通过将所有进程组织成一个逻辑环形结构,并利用一个特定的控制数据单元——令牌(Token)来控制访问权限,该算法能有效地实现对共享资源的互斥访问。
工作原理
- 逻辑环结构:系统中的进程被组织成一个逻辑环,进程间通过点对点的通信方式,按照环形结构的固定方向和顺序进行消息传递。
- 令牌传递:令牌是一种具有特定格式的报文,它象征着访问共享资源的权限。令牌在逻辑环中不断循环传递,只有持有令牌的进程才能进入临界区访问共享资源。
- 访问共享资源:获得令牌的进程如果需要访问共享资源,则检查资源是否空闲。如果空闲,进程进入临界区并进行访问;访问结束后,退出临界区并将令牌传递给逻辑环中的下一个进程。
保障机制
- 令牌循环传递:为保证环中进程能实现对共享资源的访问,必须确保令牌能够持续循环传递且不丢失。
- 故障处理:如果因为通信链路故障、进程崩溃等原因导致令牌丢失或破坏,系统必须有机制及时进行修复,如重新颁发令牌或重构逻辑环。
不足之处
- 令牌丢失或破坏:令牌环算法的一个潜在弱点是令牌的丢失或破坏,这可能会导致整个系统的访问控制机制瘫痪。
- 检测和恢复机制:令牌丢失或破坏的检测和恢复可能比较复杂,需要额外的机制来确保系统的稳定性和可靠性。
应用场景
令牌环算法特别适合于对资源访问顺序要求严格的分布式系统,如分布式数据库系统、分布式文件系统等。它通过简单有效的令牌传递机制,保证了系统的公平性和资源访问的有序性,尽管在处理令牌丢失或破坏问题上可能需要额外的注意和设计。