操作系统的共享进程间的同步问题关乎到数据的一致性以及操作系统的可实践性,是十分重要的概念。本篇博客将从宏观上梳理操作系统进程同步的解决方案,同时引出当前广泛使用的一种有效措施。
进程同步机制基础概念
为何要引入进程同步机制
首先来明确一下,为什么要引入进程间的同步?由于操作系统中的进程是并发的,因此当协同进程对共享数据进行访问时,可能会造成数据的不一致性问题。为了保证数据的一致性,那么我们就需要一种有效地机制,这就是被我们称之为的进程同步机制。
进程间资源访问冲突的类型
进程间资源访问的冲突主要有两种类型,分别为
- 共享变量的访问冲突
- 访问顺序的冲突
对于共享变量的访问冲突,我们解决的方式就是进程的互斥,而对于进程间访问顺序的冲突,我们解决的方式就是进程的同步。
同步机制遵循的原则
进程间同步机制遵循一套规范化的原则,由于中文教材和英文教材存在一定的差异,以下的分析以英文教材为主:
- Mutual Exclusion.(互斥). 如果进程Pi在其临界区执行,那么其他进程如Pj将被排斥在临界区之外;
- Progress.(有空让进). 当没有进程进行临界区执行时,允许任何进程申请进入临界区;
- Bounded Waiting.(有限等待). 任何进程进入临界区的等待时间都是有限的。
看完了上述的分析,我们需要明确的一点是,什么是临界区:对于临界资源,系统必须互斥地对其进行访问。那么临界区就是进程中访问临界资源的一段代码。
进程间同步互斥机制
进程间实现互斥访问,大体上似乎可以分为两种方式:
- 轮流
- 申请
轮流也就是每个进程都有一段进入临界区的机会,但单纯出于轮流的考虑,可以满足互斥的条件,但是无法满足有空让进(某进程使用完后无法再在下一轮使用);同理,单纯通过申请的方式也不能满足有空让进(两进程彼此更改flag形成死锁)。
那么直观的现象是什么呢,这便是轮流加申请,实现的伪代码如下:
do {
flag[i] = true;
turn = j;
while (flag[j] && turn == j);
critical section
flag[i] = false;
remainder section
} while (true);
通过简单的分析,我们可以看到,使用轮流与申请这两种方式就可以实现进程间同步。
但是,我们不得不看到该方法存在着突出问题:一方面如果进程数量太多,那么控制逻辑相对过于复杂;另外一方面可能造成进程轮转一周但是没有进入临界区进程的情况,导致了资源的浪费。
那么有没有好的解决方法呢?当然有,就是Dijkstra提出的信号量进程同步机制。
信号量进程同步机制
信号量S表示的是可用资源实体的数量,其可以根据实际需求进行初始化。另外还有两个原子操作,分别为P操作和V操作。
P(S):
S--;
while S<0 do block;
V(S):
S++;
if S<=0 then wakeup;
P,V的两个原子操作是通过硬件来得以保证的。
通过多个进程的模拟实例,很容易证明该方式的有效性。
同时,我们还可以通过信号量来表示进程间的前驱关系,逻辑上用前驱图来进行表示。
参考文献
- Lihua Tian. Operating System.(PPT). Xi’an: Xi’an Jiaotong University.
- ABRAHAM SILBERSCHATZ et al. Operating System Concepts.