【现代操作系统】之进程间通信

什么是进程间通信

进程间通信就是进程间的信息传递,简单的说,有三个主要问题,第一,一个进程如何把信息传递给另一个进程,第二,两个或更多进程的活动不会出现交叉。第三,进程要按顺序来通信。

竞争条件

两个进程同时竞争使用一块存储区,这样会造成一些问题,比如有两个进程同时想向一块存储区中写数据,那么前一个写的数据可能就会被后一个进程写的数据所覆盖,这样就会造成第一个程序的异常运行,这就需要竞争条件来约束两个程序的行为使它们正常运行。

临界区

如上所示,当两个进程同时操作一块共享内存、共享文件以及任何共享资源的情况,都会引发竞争条件的问题,为了解决这个问题,我们需要实现一种互斥机制让这两个进程无法同时操作一块共享区域。
一个进程的一部分时间做内部计算或者另外一些不会引发竞争条件的操作,在某些时候会做一些共享内存等引发竞争的操作,我们把对共享内存进行访问的程序片段称为临界区,如果我们能够通过适当的安排使得两个进程不可能同时处于临界区,就可以避免竞争条件。
这种机制可以避免竞争条件,同时我们不能为了实现这个机制付出过于巨大的代价。所以对这个机制有如下要求
1、任何两个进程不能同时处于临界区(基本要求)
2、不应对CPU的数量和速度做任何限制(???)
3、临界区外运行的进程不得阻塞其它进程
4、不得使不得使进程无限期等待进入临界区

忙等待的互斥

介绍实现互斥的几种方法:
1、屏蔽中断
当某个进程进入临界区后立即屏蔽所有中断,包括时钟中断,也就是说CPU只能执行当前进程。无法进行进程切换,这样做的确实现了目标,但是这样做是不好的。首先,将CPU的控制器交给一个普通进程是十分不可取的,那样会导致整个系统瘫痪,其次在现代计算机中多核已经越来,目前双核甚至四核已经很常见了,因此即使一个CPU核被中断了,另一个仍然可以继续执行,仍然会造成竞争。
2、锁变量
这是一种软件解决方案,设想有一个共享变量,我们称之为锁变量。当有进程进入临界区,锁变量就被置1,当一个进程想要进入临界区,它首先会探测这个锁变量,如果锁变量为零,它就会就绪进入临界区,否则就会一直等待。
这个方法存在一个时序上的问题,假设有两个进程,一个进程首先探测锁变量,发现它是0,在这个进程将它置1之前,另一个进程也探测了锁变量,发现它也是0,然后这个进程也准备将它置1,这样就相当于有两个锁变量准备进入临界区,这就会造成竞争。
3、严格轮换法
顾名思义,严格轮换,意思就是严格的轮换,不可能出现两个进程同时满足进入临界区条件的状态,也就是说,同一时刻条件只能允许某一个进程进入临界区。不像上面的锁变量方法,只要共享变量为0,任何进程都可以准备进入。下面以两个进程为例。
有一个整型变量turn,初始值为0,用于记录哪一个进程到了临界区,开始时,进程0发现其初始值为0,于是进入临界区,当临界区结束的时候,将turn置为0,在这之前,进程1也发现其值为0,于是就一直在测试turn,观察它什么时候变成1。
这种方法也存在一些问题,如果进程1结束之后,turn被置位0,进程0迅速完成了临界区,并将turn置位1,但是这个时候,由于进程1的执行速度比较慢,所以它还在非临界区中,但是由于进程0执行比较快,所以它快速地完成了非临界区的操作,所以这个时候进程0不得不继续等待进程1将turn置位0。这显然违反了临界区章节的第三个条件,所以这种方法只适用于那种两个进程严格轮流地进入临界区的情况。
4、Peterson解法
这个时候就不得不上代码了

#define FALSE 0
#define TRUE 1
#define N 2


int turn;
int interested[N];


void enter_region(int process){
	int other;
	
	other = 1- process;
	interested[process] = TRUE;
	turn = process;
	while(turn = process && interested[other] == true);
}
void leave_region(int process){
	interested[process] = FALSE;
}

在这段代码中,进程进入和离开临界区分别调用enter_region和leave_region,假设现在有两个进程,当进程0准备进入临界区的时候,进程1不准备进入,它会调用enter_region,other = 1,interested[process] = TRUE,turn = 0,while条件不成立,进程0会顺利进入临界区,如果这是进程1要进入的话,只能挂起等待进程0调用leave_region,
当进程0和进程1几乎同时准备进入临界区的时候,进程0稍微提前一点,当到达判断条件的时候,turn由于被进程1的执行而覆盖掉,所以进程0可以继续往下执行,但是对于进程1,由于当前条件都满足,所以,进程1会停留在while循环中,一直等到进程0调用leave_region使循环条件不满足。
5、TSL
这是一种硬件支持的方案。

睡眠与唤醒

以上的Peterson解法与TSL法均可行,但是他们都是以忙等待为代价,基本思想都是:当一个进程准备进入临界区,首先检查是否允许进入,如果不允许,就进入忙等待挂起,直到允许为止。
就是唤醒进程与让进程睡眠
首先介绍一种消费者与生产者的模型,有一块缓冲区,消费者从这块缓冲区中读数据,生产者向这块缓冲区中写数据,有一个变量count用来记录缓冲区中数据项的个数,当缓冲区中数据项满了的时候,调度程序就让生产者睡眠,当缓冲区中数据项空了的时候,调度程序就让消费者睡眠,现在有下面这样的问题。
当缓冲区空了的时候,消费者读取count中的值,发现里面是0,那么它就准备睡眠,但是记住,这时候它只是准备睡眠,然后就切换到生产者,生产者发现count是0,于是就向里面送一个数据,接着把count加1,这个时候,它推测消费者应该是在睡眠状态,于是发送一个wakeup给消费者,但是消费者此时只是准备睡眠,但并未进入睡眠,所以这个wakeup信号丢失了。生产者不管这个信号有没有丢失,它只知道它已经发送了wakeup信号并且在count重新变成0之前不会再发送wakeup信号,因此,消费者就会一直睡眠,由于生产者最终会把缓冲区加满,因此,它最终也会进入睡眠状态,这样,生产者和消费者就都一直在睡眠态了。(书上是这么个意思,但笔者有一些疑问,为什么消费者读完count就被切换了,而生产者却在处理了那么多事情后才被切换,这是不是有点不公平,可能这是系统中真实存在的情况,只是我目前还不了解罢了)。

信号量

1965年,由荷兰计算机科学家Dijkstra提出信号量的概念。他提出用一个整型变量来累计唤醒次数,供以后使用,这个整型变量就是信号量,信号量的取值可以为0或正数。
同时Dijkstra提出了down和up两种操作,这是两种原子操作(一组关联的操作不间断地执行),对信号量执行down操作,首先检查其值是否大于0,如果大于0,则将其减1,否则进程进入睡眠状态,这一系列的动作,是一个整体不可分割,这就是原子操作。
up操作对信号量增加1,如果有多个进程在该信号量上睡眠,无法完成先前的一个down操作,那么系统就会选择其中的一个进程,让它完成这个down操作,这样count的值就变成了-1,但是紧接着,又对其执行了一个up操作,所以最终结果是,count结果仍然是0,但有一个进程被唤醒了。
上面是我在书上理解的,但是我还是被它给绕晕了,下面是我在网上看别人的博客理解的。
有一群人(一群进程),想要进入厕所(临界区),厕所门口有位有一个记事本的大爷(信号量)在看守,他只允许同时4个人上厕所(只有4个茅坑😆),当有一个人进去之后,他就将茅坑位减一(down操作),当茅坑位变成0的时候,所有后来的人都只能在门口等(睡眠),当有一个人出去的时候,大爷就把茅坑位加1并允许另一个人进来(一次完成),此时茅坑里面仍然没有位置(up操作)。这就是信号量。是不是好理解多了。
这里面最重要的一点应该是那个原子操作,之前的乱序,好像都是因为无法一次执行完而被打断,导致竞争问题。

互斥量

如果不需要信号量的计数能力,我们可以使用互斥量。
互斥量是一个只有两种状态的变量:开锁和加锁,常用一个整型变量表示,当有进程进入临界区时,这个变量就被加锁,其它进程就无法进入,当当前进程结束临界区时,锁被打开,其它进程可以进入临界区。
就相当于一个只看守一个坑位🚽的大爷。

管程

管程是由过程、变量、以及数据结构组成的一个集合。可以理解为一种ADT,里面有多种进程的一部分操作,但同一时刻只能有一个进程的操作在执行,也就是说管程中同一时刻只能有一个活跃的进程。要实现管程互斥的方法,只要把所有的临界区换成管程过程就可以了,管程的互斥实现是由编译器完成的,程序员只需要调用就可以自动互斥。尽管管程已经实现了自动互斥,但这还不够,我们还要实现管程的自身阻塞,用上面的生产者作为例子,当生产者调用管程过程,但是发现缓冲区中满了,他需要自动挂起,这时候就需要条件变量以及相关的两个操作wait和signal,当一个管程过程无法继续运行时,他会在某个变量上执行wait操作,并且调用另一个等待进程进入管程,同时,一个进程也可以通过signal来激活另一个正在睡眠的伙伴进程,它通过对其伙伴进程正在等待的条件变量执行signal来完成激活工作,为了避免管程中有两个活跃进程,我们必须要决定signal之后,该怎么办,有两个方案,第一个是让被唤醒的进程继续执行,而挂起另一个进程,第二个是执行signal的进程必须立即退出管程,我们采用第二个方案,这样,signal只能放在管程过程的最后执行。还有另一种方法,那就是在发送signa程后,不会立即启动被唤醒的进程,而是等待发送信号的进程完成当前管程进程后才启动。

消息传递

屏障

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值