操作系统 —— 同步机制

基础概念

  1. 进程互斥
    由于各进程要求使用共享资源(变量、文件等),而这些资源需要排它性使用,各进程之前竞争使用这些资源 —— 这一关系称为进程互斥。

  2. 临界资源
    系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源或共享变量。

  3. 临界区(互斥区)
    各个进程中对某个临界资源(共享变量)实施操作的程序片段。

临界区的使用规则

  1. 没有进程在临界区时,想进入的临界区可进入
  2. 不允许两个进程同时处于其临界区中
  3. 临界区外运行的进程不得阻塞其它进程进入临界区
  4. 不得使进程无限期等待进入临界区

实现临界区互斥的方案

  • 软件方案 Dekker 解法,Peterson 解法
  • 硬件方案 屏蔽中断,TSL、XCHG 指令

进程互斥的解决方案

软件解法

  1. Dekker 算法
    1965年,第一个软件解法

  2. Peterson算法
    1981年

enter_region(i);
//code
leave_region(i);

硬件解法

1 中断屏蔽法

“开关中断”指令

算法
① 执行关中断指令
② 临界区操作
③ 执行开中断指令

特点

  • 简单,高效
  • 代价高,限制CPU并发能力(临界区大小)
  • 不适用于多处理器
  • 适用于操作系统本身,不适用用户进程

2 “测试并加锁”指令

TSL指令:TEST AND SET LOCK

TSL执行前把总线锁住,结束后把总线打开

enter_region:
    TSL REGISTER,LOCK   ;复制锁到寄存器并将锁置1
    CMP REGISTER,#0     ;判断寄存器内容是否是0
    JNE enter_region    ;若不是0,跳转到enter_region
    RET                 ;返回调用者,进入了临界区
leave_region:
    MOVE LOCK,#0        ;在锁中置0
    RET                 ;返回调用者

3 “交换”指令

XCHG指令:EXCHANGE
(类似于 TSL 指令)

enter_region:
    MOVE REGISTER,#1    ;给寄存器存1
    XCHG REGISTER,LOCK  ;交换寄存器和锁变量的内容
    CMP REGISTER,#0     ;判断寄存器内容是否是0
    JNE enter_region    ;若不是0,跳转到enter_region
    RET                 ;返回调用者,进入了临界区
leave_region:
    MOVE LOCK,#0        ;在锁中置0
    RET                 ;返回调用者

忙等待(busy waiting)
进程在得到临界区访问权之前持续作测试而不做其它事情。

自旋锁(Spin Lock)
适用于多处理器,通过忙等待减少进程切换的开销

进程同步

进程同步指系统中多个进程中发生的事件存在某种时序关系,需要相互合作,共同完成某一项任务。

信号量及 PV 操作

一种卓有成效的进程同步机制。1965年,由荷兰学者Dijkstra提出。定义如下:

struct semaphore{
    int count;
    queueType queue;
}

对信号量可以实施的操作:初始化、P和V

  • P、V分别是荷兰语的test(proberen)和increment(verhogen)
  • P,down,semWait
  • V,up,semSignal
  • PV操作为原语操作(primitive or atomic action)

用PV操作解决进程间互斥问题

  1. 分析并发进程的关键活动,划定临界区
  2. 设置信号量mutex,初值为1
  3. 在临界区前实施P(mutex)
  4. 在临界区后实施V(mutex)
P(s){
    s.count--;
    if(s.count < 0){
        //该进程状态置为阻塞状态;
        //将该进程插入相应的等待队列s.queue末尾;
        //重新调度;
    }
}
V(s){
    s.count++;
    if(s.count <= 0){
        //唤醒相应等待队列s.queue中等待的另一个进程
        //改变其状态为就绪态,并将其插入就绪队列
    }
}

信号量解决生产者消费者问题

void producer(void){
    int item;
    while(TRUE){
        item = produce_item();
        P(&empty);
        P(&mutex);
        insert_item(item);
        V(&mutex);
        V(&full);
    }
}
void consumer(void){
    int item;
    while(TRUE){
        P(&full);
        P(&mutex);
        item = remove_item();
        V(&mutex);
        V(&empty);
        consume_item(item);
    }
}

信号量解决读者写者问题

问题描述:
多个进程共享一个数据区,这些进程分成两组

  • 读者进程:只读数据区中的数据
  • 写者进程:只往数据区写数据

要求满足条件:

  • 允许多个读者同时执行读操作
  • 不允许多个写者同时操作
  • 不允许读者、写者同时操作

第一类读者写者问题:读者优先

void reader(void){
    while(TRUE){
        P(mutex);
        rc = rc + 1;
        if(rc == 1) P(w);
        V(mutex);

        P(mutex);
        rc = rc - 1;
        if(rc == 0) V(w);
        V(mutex);
    }
}
void writer(void){
    while(TRUE){
        P(w);
        写操作
        V(w);
    }
}

管程

相关概念

管程
管程是一个抽象数据类型。由关于共享资源的数据结构及在其上操作的一组过程组成。

信号量的缺点
程序编写困难、易出错。

进程与管程
进程只能通过调用管程中的过程来间接地访问管程中的数据结构。

管程与互斥
管程是互斥进入的 —— 某一时刻只有一个进程/线程调用管程中的过程。管程的互斥性由编译器负责保证。

管程与同步
管程中通过设置条件变量及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,应先释放管程的使用权),也可以通过发送信号将等待在条件变量上的进程或线程唤醒。

解决同步问题的具体方式
当第一个进程在管程中等待并释放管程的互斥权,第二个进程进入后执行唤醒操作(例如 P 唤醒 Q),此时管程中便存在两个同时处于活动状态的进程。三种处理方法:

  1. P等待Q执行。 Hoare
  2. Q等待P继续执行。 MESA
  3. 规定唤醒操作作为管程中最后一个可执行的操作。

HOARE 管程

基本原理

因为管程是互斥进入的,所以当一个进程试图进入一个已被占用的管程时,应当在管程的入口处等待。为此,管程的入口处设置一个进程等待队列,称为 入口等待队列。如果进程P唤醒进程Q,则P等待Q执行;如果进程Q执行过程中又唤醒进程R,则Q等待R执行。如此,在管程内部可能会出现多个等待进程。在管程内也需要设置一个进程等待队列,称为 紧急等待队列,紧急等待队列优先级高于入口等待队列。

条件变量的实现

条件变量 —— 在管程内部说明和使用的一种特殊类型的变量。对于条件变量,可以执行wait/signal操作。

wait(c):
如果紧急等待队列非空,则唤醒第一个等待者;否则释放管程的互斥权,执行此操作的进程进入c链末尾。

signal(c):
如果c链为空,则相当于空操作,执行此操作的进程继续执行;否则唤醒第一个等待者,执行此操作的进程进入紧急等待队列的末尾。

MESA 管程

介绍

Hoare 管程的缺点 —— 两次额外的进程切换。

MESA 管程的解决:
signal -> notify
notify:当一个正在管程中的进程执行 notify(x) 时,它使得x条件队列得到通知,发信号的进程继续执行。

基本原理

当 x条件队列得到通知时,位于条件队列头的进程将在合适的时候且当处理器可用时恢复执行。由于不能保证在它之前没有其它进程进入管程,因为这个进程必须重新检查条件(需要使用 while 循环)。

进程间通信

信号量及管程的不足:不适用多处理器的情况。

基本通信方式
消息传递、共享内存、管道以及 远程过程调用RPC

linux 内核同步机制

原子操作、自旋锁、读写自旋锁、信号量、读写信号量、互斥体、完成变量、顺序锁、屏障

原子操作

atomic_t v = atomic_init(0);

atomic_set(&v,4);
atomic_add(2,&v);
atomic_inc(&v);
printk("%d\n",atomic_read(&v));

屏障
一组线程协同完成一项任务,需要所有线程都到达一个汇合点后再一起向前推进。

posted on 2020-12-06 17:24  Lemo_wd  阅读(511)  评论(0)    收藏  举报

导航