现代操作系统笔记之 进程间通信(上)

来源:互联网 发布:万网备案域名批量查询 编辑:程序博客网 时间:2024/05/20 06:51

进程间的通信其实就是要解决3个问题1、一个进程如何把信息传递给另一个进程2、怎么处理多个进程在关键活动中不出现交叉3、确保进程处理的顺序性。当然,进程间的通信同样也是适合线程,对于第一个问题,由于线程是共享同一个地址空间,通信比较容易,要处理的其实就是后两个问题,解决方法与进程的方法一致。

竞争条件

在一些操作系统中,写作的进程可能拥有一些共享区,现考虑一个例子,一个脱机打印程序,当一个进程需要打印一个文件时,它就将文件名放在一个特殊的假脱机目录下,另一个进程则周期性的检查是否有文件需要打印,若有则进行打印并将文件名从目录中删去(其实这里的程序就可以当成一个队列,没那么复杂,如下图)


如图,假设该假脱机目录有槽位0、1、2...,其中有两个共享变量in和out,in指向下一个空闲槽位,out指向需要打印的槽位,这两个变量可以被所有进程访问。这时候出现一种情况,假设进程A和B同时需要打印一个文件,而进程A先访问假脱机目录,将变量in的值保存在自己的变量中,而恰好此时发生一次时钟中断,CPU从进程A切换到进程B,此时变量in的值没有做任何修改,进程B保存的in同样是7,然后进程B将自己需要打印的文件加入到目录中,并将in的值设为8。而在此切换到A时,A中保存的in的值仍旧是7,此时进程A将自己需要打印的文件放置在槽位7,这样就覆盖了进程B的文件,导致冲突的发生。

类似这样的情况,即两个或多个进程读写共享文件时,最后的结果取决于进程运行的精确时序,称为竞争条件。

临界区

要避免之前的竞争条件的发生,就要避免多个进程同时读写共享区,即当一个进程在使用一个共享变量或文件时,另外进程不能访问这些共享变量或文件。但出现的问题也是在一个进程还未使用完共享区文件,另一个进程就开始使用这些文件,如同刚刚提到的假脱机打印程序。

对于一个进程,它可能在做内部计算或是在访问共享内存区,在访问共享内存区的那部分程序代码,称为临界区。如果经过适当安排,使两个进程不可能同时处于临界区,那么就可以避免竞争条件的发生。

当然,一个好的解决方案需要满足一下四个条件:

1、任何两个进程不能同时处于临界区

2、不能对CPU的速度和数量做任何假设

3、临界区外的进程不能阻塞其他进程

4、不得使进程无限期等待进入临界区

可以用下图进行描述,当进程A进去临界区时,稍后,进程B试图进入,但失败了,此时进程B处于阻塞状态,等待进程A离开临界区,而一旦进程A离开临界区,进程B才能够进入临界区。


忙等待的互斥

下面是实现几种互斥的方案

1、屏蔽中断

这是最简单的方法,就是一旦当一个进程进入临界区后立即屏蔽所有中断,包括时钟中断,这样CPU就不会进行进程切换,其他进程就不会进入临界区。但是,这样做并不好,首先,将中断的权利交给用户本身就很危险,一旦中断无法恢复,会导致系统的崩溃。其次,对于多核CPU而言,一个CPU的中断暂停不会导致其他CPU中断暂停,这样就没有什么效果。

2、锁变量

类似于数据库里的共享排他锁吧,设定一个共享锁变量,当一个进程访问共享区时,将锁变量设为1,离开共享区后,将锁变量设置为0。这样,其他进程需要访问变量时首先观察共享锁的值是否为1,是的话则一直等待直到为0,然后才可以访问并把锁置为1。但是,这种方案与之前的假脱机程序有同样的毛病,就是当一个进程刚刚读取出共享锁的值为0,并即将把这个值设置为1时,发生一次时钟中断,其他进程访问共享区,发现共享锁还是0,于是两个进程都可以欢快的同时访问同一个共享区了。

3、严格轮换法

以如下C语言程序段为例,整型变量turn,初始值为0,用来记录轮到那个进程进入临界区,并检查更新共享内存。一开始进程0检查turn,发现其值为0,进入临界区,进程1发现其值也为0,所以在一个等待循环中不断测试turn的值,直到这个值为1,这个称为忙等待。只有忙等待时间比较少的情况下才使用,用于忙等待的锁称为自旋锁。但是,当一个进程比另一个进程满很多的情况下,这种方法也不再适用。

详细的说下这种问题吧,假设一开始turn的值为0,进程0可以进入临界区(就是左边的critical_region()),右边的进程1只能在while循环中不断检测turn是否为1以便进入临界区,当进程0执行完临界区任务后,将turn的值置为1,开始执行非临界区任务,进程1此时便可以进入临界区,进程1执行完临界区任务后将trun置为0,开始执行非临界区任务。此时假设进程0速度比较块,开始了下一轮工作,由于turn的值为0,所以可以进入临界区工作,然后呢工作完成后将turn置为1,进入非临界区,但假设此时进程1速度太慢了,还在执行非临界区的工作,而turn的值仍旧是1,那么这两个进程都无法进入到临界区,这样就违反了之前提到的第三个原则。


4、Peterson解法


如上图所示,这是一种不需要严格轮换的软件算法,也就是不会存在刚刚提到的问题。它的工作原理如下:假如进程0需要进入临界区,那么在进入临界区之前需要调用这个enter_region函数,该函数将turn的值置为0,并把数组为置为真,假设进程A并不想进入临界区,那么下一条循环语句直接跳出,进程0进入临界区,假设此时进程1想要进入临界区,那么必须等待进程0退出临界区并把数组的为置为假。此时假设进程0和进程1都想要进入临界区,那么他们都会改写turn的值,具体turn的值是什么取决于后改写的那个进程,假设进程1后改写,turn的值为1,但是数组上两个位都是真值,那么进程1就会在循环那里不断等待,而进程0就可以顺利进入临界区了。

5、TSL指令

以硬件方式加锁。

睡眠与唤醒

前面的几种解法都有一个缺点,就是当一个进程进入到临界区后,另一个必须要等待,这样比较浪费CPU时间,而且可能引起预想不到的后果。现在假设有两个进程H和L,H的优先级较高,调度规定一旦H进入就绪状态就可以运行,但此时加入L在临界区,H刚刚处于就绪状态,由之前的算法,H就要处于忙等待状态,但一旦H处于就绪状态,L就不会被调度,那么L就一直在临界区内。这就是优先级反转的问题。

原创粉丝点击