从溯源角度看:进程间互斥

2021-12-07 17:00:57 浏览数 (1)

文章目录

    • 临界区
    • 屏蔽中断
    • 锁变量
    • 自旋锁
    • TSL 指令

对多种进程间通信方式的介绍在这篇总结过了:进程间通信,不过没有提互斥,因为我一直是把互斥和通信分开的。

这一篇的话将从互斥方面入手,按时间线铺开。这不最近要写自己写网络层了嘛,操作系统的老底子得翻翻。

临界区

避免竞争条件的问题也可以用一种抽象的方式进行描述,一个进程的一部分时间做内部计算或另外一些不会应发竞争条件的操作。 在某些时候进程可能需要访问共享内存或共享文件,或执行另外一些会引发竞争的操作。 我们把对共享内存进行访问的程序片称作临界区。

而我们要做的就是通过适当的安排,使得两个进程不可能同时出在临界区中,就能避免竞争条件。

对于一个好的解决方案,需要满足以下4个条件:

代码语言:javascript复制
任何两个进程不能同时处于其临界区;
不应该对CPU的速度和数量做任何假设;
临界区外运行的进程不得阻塞其他进程

屏蔽中断

在单处理器的系统中,最简单的就是使每个进程在刚刚进入临界区的时候立即屏蔽所有的中断,并在就要离开之前再打开中断。屏蔽中断后,时钟中断也被屏蔽。CPU只有在发生时钟中断的时候才会进行进程切换,这样,在屏蔽中断后CPU就不会被切换到其他进程。

这样的方案有什么问题吗? 问题大了。把本该属于系统内核的权利交给用户是非常危险的。如果一个屏蔽中断之后再不打开,会发生什么事情?如果不是单处理器,则屏蔽中断只会屏蔽某个CPU,而其他CPU依然可能放进程入临界区。

锁变量

以前接触的都是线程锁,这会儿是进程锁,其实也是一个道理。玩过单例模式的朋友应该知道二次检查锁吧,也知道为什么要二次检查锁。 线程锁有这种纰漏,进程锁一样会有这种纰漏。所以这个方案并不是最好的。

先有这个想法,这和我们讲的锁变量不完全是一个东西。

设想有一个锁变量,其初始值为 TRUE。上锁的时候怎么上?

代码语言:javascript复制
1、读取锁变量。
2.1、锁变量的值是TRUE,说明可上锁。
2.2、锁变量的值是FALSE,说明不可上锁。结束,或等待。
3、将锁变量的值改为FALSE。

既然不是原子操作,就有很多可以插一脚的地方了。不用我多说了。

自旋锁

看一个案例,两个进程等待一把自旋锁:

代码语言:javascript复制
while(true){
	while(turn != 0);	//等待
	critical_region();
	turn = 1;
	noncritical_region();
}
代码语言:javascript复制
while(true){
	while(turn != 1);	//等待
	critical_region();
	turn = 0;
	noncritical_region();
}

连续测试一个变量直到某个值出现为止。

如果这时候出现这么一种情况:如果0号进程出了临界区,将锁放给1号进程,而一号进程还有一些临界区外的事务没有处理完,这就很尴尬了。

只有在有理由认为等待事件是非常短的情况下,才使用自旋锁。

TSL 指令

特别是那些为并发而生的计算机中,都有这么一条指令:TSL,RX,LOCK 称为测试并加锁,它将一个内存字 lock 读到寄存器 RX 中,然后在该内存地址上存储一个非零值,读字操作和写字操作保证是不可分割的,即该指令结束之前其他处理器均不允许访问该内存字。执行 TSL 指令的 CPU 将锁住内存总线,以禁止其他 CPU 在本指令结束前访问内存。

代码语言:javascript复制
enter_region:
	TSL REGISTER,LOCK		 复制锁到寄存器并设置锁为1
	CMP REGISTER,#0			 判断锁是否为0
	JNE enter_region		 若不是0,则该锁已被设置,循环返回调用者,进入临界区
	RET

leave_region:
	MOVE LOCK,#0			在锁中存入0
	RET						返回调用者

一个可替代 TSL 的指令是 XCHG,它原子性的交换了两个位置的内容,例如:一个寄存器与一个存储器字。

那这里又出现了新的问题了。这个问题应该说是伴随互斥而出现的。

进程优先级。在互斥条件下,有可能会出现优先级被倒挂的场景。可能我优先级没你高,但是我先到,这个坑位我先拿走了,你就搁外边等着。然后我半天不出来,那就有意思了哈。

我们就是在这样,一换扣一环的问题解决,发现,解决,发现的过程中成长的,不是吗?

0 人点赞