当你自行搜索到这篇文章的时候,想必已经对进程间通信有了一定的了解,如果不太了解也没关系,我会简单讲解下关于进程间通信的相关内容。
我们想要把信息从一个进程传递到另一个进程,无非是使用公共的存储区,有可能是在内存中,也有可能是一个共享文件,这就是进程间通信。但是有个问题,因为中断的原因,可能进程1读取到公共内存的的时候,切换到了进程2,进程2中对公共内存进行了修改,当进程2运行一段时间切换到进程1的时候,进程1的数据已经算是旧数据了,但进程1并不知道,仍然使用旧数据进行操作,当进程1操作完写回到公共内存时,数据就已经乱了。这里,我们把多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞争条件(引用自《现代操作系统 第4版》)。
假设我们把对共享内存操作的程序片段称为临界区(引用自《现代操作系统 第4版》),我们接下来的目标,就是保证同一个时刻,只能有一个进程对临界区进行操作。
首先我们想想,为什么会产生竞争条件这样的问题?是不是因为中断机制的原因,使得CPU在执行一个进程片段一段时间后,就跑去执行另一个进程,那我们只用保证CPU在进入临界区后,屏蔽掉所有中断,这样,自然就只会执行当前进程,是不是就不会有竞争条件的问题?对的!但是,屏蔽中断有个致命的问题,他只能对当前CPU屏蔽,如果是多核情况下,屏蔽中断就并不管用了。
也许你会想,那我们是不是有个东西能同时屏蔽掉所有CPU的中断呢?对啦!那就是内存总线。啥是内存总线?简单来说,就是所有CPU都是通过这个内存总线,对内存进程访问的。我们只要锁住这个内存总线,就能保证只能有一个CPU对内存区访问,这个是硬件支持的一种方案,叫做TSL指令,这个会再写一篇文章进行详解,接下来我们将考虑怎么用软件的方式去解决竞争条件问题。
首先,我们考虑这样一个问题,是不是我们可以在某个进程A已经访问临界区,当另一个进程B也想访问的时候,用某种方式告诉进程B,公共内存有人在访问,你别访问了。这样貌似可行,那我们可以用一个布尔型变量来声明临界区是否可以访问。每个进程在访问临近区的时候,都先看看这个变量,看看能不能访问临界区,这样一来,就可以保证同一时刻只能执行一个进程了。
这个变量,我们可以称为锁变量。但是,有个问题,这个锁变量,本质上也是竞争条件问题,因为假设进程A在进入临界区前,判断这个变量后,能进入临界区,这个时候CPU切换到进程B,进程B一看,也能进入,这样一来,就又会出现竞争条件问题。
我们来仔细想想,为什么这个锁变量会有竞争条件问题,是不是因为,他可以被多个进程访问,那既然不考虑硬件支持,我们可不可以,使得这个变量,即时让多个进程同时读到,也没关系呢?
也就是说,目前这个锁变量,只有0和1,0代表当前进程能访问,1代表当前进程不能访问,进程A读到锁变量为0,切换到进程B也读到锁变量为0,结果他们同时访问了临界区。
那能不能我们把这个锁变量改成一种类似于hashmap的结构呢?key是当前进程ID,value是0或1,当进程A访问临界区的时候,先把value设置成1,代表A想要访问临界区,这个时候锁变量结构类似于这样:lock{A=1},紧接着再判断lock变量内,是否有其他的key,value = 1,如果没有,就直接进入临界区,如果有,该进程就循环判断直到没有为止。
我们来看看进程A和进程B同时想要进入临界区的情况是怎么样的:
- 进程A设置lock变量为1,此时lock变量为lock{A=1};
- 切换到进程B执行;
- 进程B设置lock变量为1,此时lock变量为lock{A=1,B=1};
- 进程B判断出有其他进程想要进入临界区,则循环判断lock变量;
- 切换到A进程执行;
- 进程A判断出有其他进程想要进入临界区,则循环判断lock变量;
。。。。。
好吧,进程A和进程B都在等别的进程出去临界区,但是谁都进不去,也出不来,结果死锁了,GG。
为啥会死锁呢?是不是因为进程A和进程B都在等另一个执行完?那如果,我们在这种情况下,选择一个进程,执行完,是不是另一个进程就能执行下去了呢?
Good!那我们这样改试试,上面这个变量称为感兴趣变量,因为代表哪个进程想进入临界区嘛,我们再加一个锁变量,用来标记哪个进程在执行,这个锁变量就存进程ID。
具体情况如下:
- 进程A设置感兴趣变量为1,此时感兴趣变量为{A=1},然后将lock变量设置为A; ins :{A=1}, lock = A
- 切换到进程B执行; ins :{A=1}, lock = A
- 进程B设置感兴趣变量为1,此时感兴趣变量为{A=1,B=1},然后将lock变量设置为B; ins :{A=1,B=1}, lock = B
- 关键点来了,当lock变量为当前进程ID,并且其他变量为1的时候,当前进程就循环不进入临界区;(注意!是lock为当前进程ID,不进入临界区哦)
- 假如B接着执行,因为ins内A = 1,并且lock = B,所以B循环; ins :{A=1,B=1}, lock = B
- 假如A接着执行,因为ins内B = 1,lock = B,所以A可以执行进入临界区; ins :{A=1,B=1}, lock = B
- A执行完,设置ins = 0; ins :{A=0,B=1}, lock = B
- 切换到进程B执行; ins :{A=0,B=1}, lock = B
- 因为A = 0,所以进入临界区;
再次提醒,lock变量其实代表哪个进程不能执行,当然你可以能会想,为什么不能判断条件当lock变量为当前进程ID,并且其他变量为0的时候,当前进程进入临界区呢
我们来看看这种情况会是怎么样:
- 进程A设置感兴趣变量为1,此时感兴趣变量为{A=1},然后将lock变量设置为A; ins :{A=1}, lock = A
- 切换到进程B执行; ins :{A=1}, lock = A
- 进程B设置感兴趣变量为1,此时感兴趣变量为{A=1,B=1},然后将lock变量设置为B; ins :{A=1,B=1}, lock = B
- 假如B接着执行,因为ins内A = 1,并且lock = B,所以B循环; ins :{A=1,B=1}, lock = B
- 假如A接着执行,因为ins内B = 1,并且lock = B,所以A循环;
这种情况下,仍然会进入死锁的状态,所以是不可行的。
像上面这种,感兴趣变量加锁变量的方式,就是Peterson解法的思想。这种方法也同样适用于多线程。
734

被折叠的 条评论
为什么被折叠?



