操作系统中的锁

来源:互联网 发布:ei数据库官网 编辑:程序博客网 时间:2024/05/17 04:53

操作系统内的同步系统,都是使用原子操作实现的。原子操作又是如何实现的呢?


一般的操作系统书都是写使用二值信号量实现的。即用二值信号量挡在原子操作的开始和结束,以保护原子操作不被打断。这个是重要的理论依据,但并不能指导实践。因为,二值信号量其实就是对一个标志位置位,当然,在置位前必须测试一下二值信号量。整个信号量的获取过程可以用伪代码描述如下:

 

1. 如果 信号量S 没有被置位 那么 置位信号量S,信号量获取成功;

2. 否则,测试次数或时间到了没有,如果没有到则跳转到步骤一继续测试;

如果时间或者测试次数到达,则退出信号量置位。信号量获取失败。

 

大家仔细看这个过程,如果S是一个变量的话,需要用if语句判断一下S,然后再对S置位。不论怎么说,这个过程都不可能用CPU的一条指令完成,需要很多指令组成,这也就是说,这个二值信号量的获取过程有可能被打断。但理论上说整个执行过程应该是原子的,即不能被分割。我们来分析一下,都是什么可以打断这个原子过程。我们猜测可以打断一个原子过程的是:


1.中断

2.其他线程

 

让我们来分析一下:

1.中断是肯定可以打断这个过程的,如果没有关闭中断的话,正在置位的时候,就有可能被中断。如果中断里面有对这个信号量或标志位有操作的话,那么可以断定,这个原子操作不原子了。那岂不是起不到二值信号量的作用了?是啊,获取信号量的时候应该把中断关了,这个排除这个干扰因素。

 

2.我们再来看看其他的线程,这个问题就相对中断来讲,理解起来比较困难。这至少分为三种情况,一种是CMP/SMP的情况;一种是单CPU的情况;最后是ASMP的情况。

 

我们先来看看单CPU的情况,操作系统运行在CPU上,所有的线程都运行在该CPU上;只有一个CPU,那么CPU的时间资源是轮流分给每个线程的。当一个线程需要获取一个信号量时,如果别的线程也获取该信号量,我们想一想,这种情况是不存在的。因为只有一个CPU,同一时间只能运行一个线程。必然是串行运行。但是存在这种情况,当第一个线程正在获取信号量时,发生了线程切换,切换到一个也获取该信号量的线程。那么这个过程就变成不是原子的。那么这个禁用线程切换就好了。各位看官不用着急下这个结论。我们需要考虑一个问题,什么情况下才能发生线程切换,其实对于单CPU的系统,无外乎两大类的情况:一种是线程自己放弃执行,把CPU的执行权让给别的线程;另外一种是被强迫放弃CPU的执行权。线程正在执行一个原子操作(获取信号量),怎么可能主动放弃CPU的执行权呢?这是不可能的。外界强迫线程放弃CPU的执行权倒是有可能:

 

1.这个线程的时间片用完了,必须切换走;

2.中断退出时,释放了一个更高优先级的线程,直接调度到其他线程了,没有回到本线程。

 

实际上这两种情况都是一样的,操作系统的时间片是靠中断计时的。而其他中断有可能释放更高优先级别的任务。分析来分析去,原来发生线程切换,也是CPU的中断引起的,那么,对于这种情况关中断就好了。

 

我们再看看CMP/SMP的情况,CMP和SMP的情况比较复杂,首先全部的CPU上只运行了一个操作系统,否则不能视为CMP/SMP。假设有两个CPU的系统,A CPU运行A线程,而B CPU运行B线程,两个同时获取一个信号量。这时候关中断肯定是不奏效的。因为A线程只能关闭A CPU的中断,就算CMP上A 线程可以关闭A、B CPU的中断,但并不能阻止B CPU对B线程的执行。更不能阻止B线程与A 线程竞争信号量。那怎么办?办法总比问题多:

这里就采用了一个很简单的方法,利用一条CPU的指令完成测试和置位。一条CPU指令,要么是执行,要么是不执行,所以必定是原子的。什么样的指令才是原子的呢?其实不同CPU架构下有不同的指令,这里就介绍一个绝大部分CPU下都有的一条指令,内存与寄存器交换指令 XCHG。分析如下伪代码:

 

flag = false

reallocate:

XCHG S, flag

if (flag == true)

    获取到信号量

else

    没有获取的信号量

    goto  reallocate

end if

 

想象一下,s的初始值为true,如果两个线程先后执行了XCHG指令,第一个线程将ture与S值交换,并将s置为false,获取到信号量;另外一个任务用false只能换到false,所以它不能获取到信号量。从而实现了锁。如果多个CPU同时执行交换指令,一般来说,内存控制器会进行仲裁,只有一个CPU能成功交换,其它都不能成功交换。


这个锁在Linux下称为自旋锁。自旋的意思是,这段代码是忙等的,即CPU的时间不能让给其他线程执行。有些童鞋可能想了,这多浪费,应该让给其他线程运行, :) 其实这个问题也比较复杂,因为操作系统切换线程是需要时间的,如果自旋锁浪费的CPU时间比两次线程切换出去,切换回来的时间还短,那么,应该选择自旋锁。不应该将CPU时间让出去,否则那是更奢侈的浪费!!!看来,SMP/CMP的情况下,锁的实现方式就有了变化,必须使用一条CPU的指令完成,一般实现的都是忙等。

 

最后,我们来看看ASMP的情况,ASMP的话实际上有几个CPU就跑着几个相同的操作系统,操作系统之间通过多口RAM或着其他的通讯方式通讯。如CMP/SMP一样,A CPU关了中断并不能影响B CPU的中断。能不能通过一条指令完成原子操作呢?也是不行的,因为CPU的型号不一样,同一条指令的运行时间也不一样。想象一下,一个慢速的CPU测试成功了锁,当他写入的时候,时间较长,在其没写入成功时,另外一个CPU就成功交换了。显然也不能实现这样的原子操作。最重要的,如果整个ASMP系统中根本就没有用多口RAM实现通信,那么也就无法实现XCHG指令,更不要谈能不能交换成功了。所以这个方法从通用的角度来讲,不现实。一个比较好的办法是,采用C/S的思路,整个ASMP系统有一个主CPU,如果其他CPU要获取全局锁,利用通信到主CPU上去仲裁,主CPU去判断了。这样,就转化为一个CPU上多个线程竞争信号量的问题了。


稍作总结:

单CPU实现锁是采用关闭中断的方式;

CMP/SMP实现全局锁采用自旋锁+关闭中断的方式;如果只和本CPU同步,那么就关中断好了;如果只和其他CPU同步,采用自旋锁就可以了;

ASMP下实现全局锁只需要关闭中断即可。

0 0