下面我们来介绍第三类同步方法
就是这里的更高级的抽象方法
它实际上是基于硬件的同步原语
来实现的同步方法
我们在前面已经说过中断禁用
可以实现进程之间的同步
那还有一类呢是原子操作指令
也就说把若干个我们在同步当中
会用到的操作
把它合成一个原子操作
在硬件上保证它的原子性
这样的话 操作系统
就可以基于这个来实现
更高级的编程抽象
简化进程同步
第一个呢是锁
锁呢实际上是一个抽象的数据结构
这个数据结构呢
由一个二进制变量
和两个操作原语组成
二进制变量呢它表示两种状态
一种是锁定一种是解锁
而两个操作呢是这样的
一个是锁的请求
我要申请这个锁
那这个操作呢 它是原子的
在请求的过程当中
一直到获取到这个锁之前
它都一直处于等待状态
那它返回的时候
你就已经获得这个锁了
第二个操作呢是释放
释放锁呢 它是唤醒其它的
等待这个锁的进程
有了这两个操作之后
我们用它来实现临界区的访问
那就很方便了
那这是临界区的访问
进入区就是请求操作
退出区呢就是释放操作
那有了这个之后这件事情就行了
那它内部呢我们是
基于原子操作指令
原子操作指令呢
是CPU体系结构当中
提供的一类特殊的指令
这些指令呢把若干个操作
合成一个原子操作
保证它们之间中间
不会出现部分执行的状态
一个呢测试与置位指令
通常我们说的TS指令
它的功能呢
是从内存单元中读取数据
测试并且置位该存储单元
测试呢就是判断这个值是不是1
然后并且往这个单元里呢写1
也就说如果里头是1
那你读出来写进去的还是1
内容没变
但是我们知道里头是1了
好 如果里头是0
那这时候呢我读出来的0
写进去的1
好 那这是我对它进行修改了
这条指令的准确含义呢
可以用这个伪码来表示
实际上这是从里头读出一个量来
然后把另外一个量写进去
并且把读出的量呢返回回来
这是它的功能
这三个基本的操作合到一起
它是一个原子操作
它们不会被中断
好 基于这一条 我们来实现
我们前面的同步就会很方便
另一条呢是交换指令
它的功能呢是交换内存中两个值
实际上它的指令含义呢
我们可以用这样一段
伪码来表示
用一个temp
把第一个值读出来
然后把第二个值复制过去之后
再把你读出来的值呢
复制到第二个值
好 用这种办法呢
我们可以把两个值呢做交换
这也是一个原子操作
也就说读出来和写进去之间
不会产生部分执行的状态
那如果说我们
以前面的分配进程ID为例
如果我们这里读出来和
写进去这两个是一个整体的话
那我ID加1就没问题了
好 这是两个基本的原语
基于这两个原子操作指令
我们可以来实现锁
那在这儿呢 我们先是
用TS指令来实现自旋锁
它的做法是这样的
初始化
你这个锁里这个初值为0
然后我来构造它的请求操作原语
实际上这里头呢
就是用TS指令去把
你这里的value这个值读出来
并且往里写1
那如果里头是1
那这个操作呢就相当于是个检查
如果里头是0
那么就把里设置为1
并且这个循环结束
好 它就进到下面这个页区去了
好 释放的时候呢
就是把这个值改成0
那我们看这个写入的过程呢
我们把它展开之后
变成这样两种
一种呢原来里头是0
那么这时候呢
实际上相当于这个锁
处于解锁的状态
好 这时候我把里写成1
也就相当于我占用这个锁
并且使得其它的进程就处于
后续来的就处于等待状态了
而如果锁处于锁定的状态
也就处于忙的状态
那么这时候呢你读出来的是1
写进去的是1
好 那它的状态
没有发生任何的改变
好 那这是用TS指令
实现的自旋锁
那我们看看它有什么样的特点
那在这里头呢是
这个自旋锁里头的TS指令的执行
它是要耗用CPU时间的
所以它在等待状态的时候呢
它是消耗CPU的
好 那我们看基于这个
我是不是可以把等待的时候
占用CPU的这件事情呢有所缓解
那我们就把这个
变成一个无忙等待的锁
那怎么做呢
首先我在你那个锁的数据结构里呢
加上一个等待队列
这个等待队列呢
就是我等待这个锁的
相应的进程所排的队列
然后我把这个锁的请求操作
在里又增加一段内容
先while 进行判断
如果说在这里头呢
查询一次之后 里头是1
那么我就把我当前线程呢
放到等待队列里头
同时呢 执行调度程序
那这时候其它进行可以继续执行
好 一旦是切换回来
轮着我再运行的时候
就是有释放锁操作
把我这个线程从等待状态
变成就绪状态了
好 回来的时候呢我再继续去查
好 如果这时候
它的状态是解锁的状态
那请求就完成了
好 这时候我就可以进入到临界区
好 转过身来在释放的时候
比我们刚才的忙等待多了点什么
填0 表示释放锁
这跟原来是一样的
那加了一句就是
我把这个线程呢
从等待队列里头
放到就绪队列里头去
这是这一步做的
好 有了这个的话
那你在等待的过程当中呢
我就处于放弃CPU使用权的状态
这样的话我CPU就可以干别的了
好 我们看到在这个过程当中呢
我们就实现了让权等待
这是基于TS指令的实现
那我们说基于交换指令
也是一样可以来做成这件事情的
那基本上就是把这个
TS指令的功能换成交换指令
好 基于原子操作指令
所构造的这种锁
它有什么样的特征呢
它是适应于单处理机
也适应于共享内存的多处理机
我们前面讲的中断禁用
只能用于单处理机
如果我有多个处理机的话
你在一个处理机上
禁止中断的响应 那不管用
另一个处理机上如果说有中断响应
或者说有其它的进程执行
那它仍然可以修改
你共享的那些变量
好 再有一个呢
它能适应任意数目的进程同步
那在这里头它不像我们前面
你两个进程同步是一种状态
三个更多的时候
这种状态就会更复杂
那么在这儿呢都是一样
相对来说它是简单
并且容易验证它的正确性
好 它支持多临界区
如果你有若干个临界区
每一个临界区对应着
一个锁那就可以了
当然它也有问题
如果说你是忙等待锁
那这时候呢它会占用CPU的时间
另外呢它可能导致饥饿
也就相当于我在这里呢
并没有做到
按先来后到的顺序来使用资源
因为我在锁的请求操作当中呢
我放到就绪队列里头来之后
我会再去检查
就像我们前面说的
你在检查那个时刻
如果说资源处于空闲状态
那就申请到了
那你回来的时候呢
实际上各个线程它把就绪队列
排定的顺序并不见得是
我们申请锁的这个顺序
同时它还可能出现死锁
那这描述一种死锁的情况是
有一个低优先级的进程
它占用了临界区资源
好 另一个请求
访问临界区的高优先级进程
由于它优先级高
那么这时候呢
它就可以占用CPU来执行
而占用CPU来执行呢
实际上它执行的那个代码是请求
如果那个地方你用的是忙等待
那么这时候呢它就一直等待
这样的话两边就相互等了
低优先级等CPU
高优先级等临界区资源
好 那这两个互不相让
那我们就构成死锁了
好 这是基于硬件同步操作指令
来实现的同步办法
好 它可以有很好的性能
有很好的特征
但它需要硬件的支持
好 到这个地方呢
我们就介绍完了
这三种同步的方法
那禁用中断呢
它比较简单 然后
它仅适用于单处理机
并且它会对
系统中断的响应时间呢会有影响
那通常情况下
我们只是在不得已的时候
才会去用它
也就说我没有其它办法的时候
我才会去用它
第二种呢是软件办法
它依赖的条件会比较弱
但是它的实现非常复杂
那在我们现在这里头呢
用的最多的呢
是基于原子操作指令
实现的同步的方法
那它不仅适用于单处理机
也适用于多处理机
具有容易验证的特征
好 这是我们介绍的三种同步方法
好 今天的课就上到这里 下课