操作系统笔记:(八)进程同步

有关进程同步的问题,我主要分3节来表述,第一节就是本节主要讲一些底层的同步方法: 禁用中断,软件方法,锁机制。第二节即下一节主要是讲信号量机制,第三节主要讲管程机制

本节内容组织如下:

  • 同步互斥的背景
  • 同步互斥的解决方案,禁用中断,软件方法,锁机制

同步互斥的背景

如果程序之间是独立的,没有并发的,那么肯定不会出现同步互斥问题。由于引入了操作系统对进程的调度,特别是中断机制允许在任何时候发生中断切换到下一个进程执行,这就会发生进程同步问题。比如下面的一个例子:

fork():创建新的进程的时候会将新进程的id赋值为next_id++,即有下面一条语句:

new_id = next_pid++

而这条语句并不是原子操作,转化成汇编指令如下:

load next_pid reg1
store reg1 new_pid
INC reg1
store reg1 next_pid

由于中断发生的任意性没人知道会在什么时候发生,在一种情况下如果在第二条指令后插入一个新进程,同时执行这段代码,那么就会发生同步错误,如下图所示:
操作系统笔记:(八)进程同步_第1张图片

同样的例子可以在生活中找到,这里就不在举例了。

临界区
操作系统笔记:(八)进程同步_第2张图片

OS中将进程访问资源的互斥代码称为临界区(同一时刻仅仅允许一个一个进程进入)
对于临界区我们希望做到以下几点访问规则:
  • 空闲则入(没进程访问的时候任何进程都能访问)
  • 忙则等待 (有进程访问的时候当前必须等待)
  • 有限等待 (等待的进程应该可以进入,不能发生饥饿)
  • 让权等待 (等待的进程应该释放掉CPU)

接下来我们说一说,关于临界区保护的一些机制:

  • 禁用中断
  • 软件同步方法
  • 原子操作-锁机制

禁用中断

这是一种最简单的硬件同步方法,也就是硬件提供了两条指令:

  • cli : 关中断
  • sti:开中断

这个可以说是从源头上解决了进程同步问题,不过也会出现一些其他问题:

  • 进程无法被抢占
  • 可能导致其他进程饥饿
  • 无法确定响应中断所需的时间,无法适应应用层临界区很长的情况。

软件同步方法

这个是相当复杂的一种方法,现代OS已经不再使用,不过他提供了一种基于软件同步的解决方案,这里我们简要介绍一下 Peterson 算法
操作系统笔记:(八)进程同步_第3张图片

turn变量的值决定了谁能进入临界区,前两条指令执行后,turn值总会变成 i or j , (假设两个进程同时访问这段代码)那么这是的while代码不会将两个进程都”悬挂”在这里。这时 flag 均为ture,而turn 总是仅能为其中一个所以总有一个能进入。满足”空闲则入”和”互斥进入”,而且 在其中一个进程,不妨设为 Pj 进入临界区后,会将flag[j]设为false,这时 Pi 就能进入了,也即满足 “空闲则入”

缺点

这种算法最大的问题是复杂,想想你是应用程序员,你去写这段代码来实现临界区保护,是不是很可怕,更别说多个进程的情况了。2)第二个问题是忙等,进程等待的时候在空转。

接下来我们来说一下基于硬件同步的第二种机制-

高级抽象方法-锁

原子操作指令

现代CPU体系结构都会提供一些特殊的原子操作指令(就是在硬件层实现的一条指令的操作?)
TSExchange

测试并置1

bool TestAndSet(bool *tar ){
    bool rv = *tar;
    *tar = true;
    return rv;
}

Exchange

void Exchange(bool *a,bool *b){
    bool tmp = *a;
    *a = *b;
    *b = tmp;
}

用这两条指令我们就可以显现高层抽像-

原子锁

自旋锁

操作系统笔记:(八)进程同步_第4张图片

这种说顾名思义,如果锁被占用,访问锁的进程将会忙等。

无忙等锁

这种锁的进步就是加一个等待队列,将无法获得锁的进程加入等待队列。
操作系统笔记:(八)进程同步_第5张图片

优点:

  • 实现简单,任意多个进程都容易处理
  • 很容易扩展到多处理器

缺点:

  • 可能导致饥饿?
  • 忙等锁占用空耗CPU
  • 死锁(低优先级进程占有锁等待CPU,高优先级进程占有CPU等待锁)

你可能感兴趣的:(system&software,engineer)