清华大学操作系统OS学习(十一)——同步互斥

一、背景

①多个进程会交互,对共享资源的访问。处理不当就会饥饿,死锁。

②独立的线程:不和其他线程共享资源或状态,不交互,所以具有确定性(输入状态决定结果),可重现(能重现起始条件),I/O,调度顺序不重要

合作的线程:在多个线程中共享状态,不确定性,不可重现

不确定性和不可重现意味着BUG可能是间歇性发生的

③为什么要合作?

共享资源(嵌入式系统);

加速,效率高(I/O操作和计算可以重叠,拆分小任务,流水,并行,多处理器);

模块化:大程序分解成小程序,使系统易于扩展

二、现实中同步问题

1、原子操作 (atomic operation)

指一次不存在任何中断或者失败的执行,要么成功done要么没执行

实际操作往往不是原子的,甚至单个机器指令都不一定是原子的。

2、临界区critical section:

进程中访问共享资源的代码区域,且当另一个进程处于相应代码区域时便不会执行。

3、互斥mutual exclusion:

任一时刻只能有一个进程进入临界区访问。当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区,并访问任何相同的共享资源。

4、死锁Dead lock

多个进程相互等待完成特定任务,而最终没法继续自身任务

5、饥饿starvation:

一个可执行的进程长期等待执行,被调度器持续忽略。

三、临界区和禁用硬件中断同步方法

1、临界区(Critical Section)

①临界区:进程中访问临界资源的一段需要互斥执行的代码

②进入区:检查可否进入临界区的一段代码;如可进入,设置相应“正在访问临界区”标志

③退出区:清除“正在访问临界区”标志

④剩余区:代码中的其余部分

代码:

entry section

critical section

exist section

remainder section

⑤临界区访问规则

Ⅰ、空闲则入:没有进程在临界区时,任何进程可进入

Ⅱ、忙则等待:有有进程在临界区时,其他进程均不能进入临界区

Ⅲ、有限等待:等待进入临界 区的进程不能无限期等待

Ⅳ、让权等待:不能进入临界区的进程,应释放CPU(如转换到阻塞状态)

⑥临界区的实现方法

三种方法:禁用硬件中断,基于软件,原子操作指令

2、禁用硬件中断

①思路:没有中断,就没有上下文切换,没有并发。减少不确定性。进入临界区禁用中断,退出时再开启。

②问题: 一旦禁用中断,线程无法停止 ;整个系统都会停下来,I/O啥的都没用了,其他线程可能会饥饿 影响效率;临界区要是太长咋整?无法限制响应中断所需的时间,可能有硬件影响。一般都用于短的临界区时间。

三、基于软件的同步方法

1、Peterson算法

①共享变量

int turn; // who ‘s turn to enter the critical section bollean flag[]; //whether the process is ready to enter

②进入区代码

flag[i] = TURE; turn=j; while(flag[j]&&turn==j ) ;

③退出区代码

flag[i]=FLASE;

④Peterson算法实现(2个进程)

use two shared data items
int  turn; // who ‘s turn to enter the critical section
bollean flag[];  //whether the process is ready to enter
PETERSON算法
do{
    flag[i] = TURE;
    turn=j;
    while(flag[j]&&turn==j ) ;
    CRITICAL SECTION
    flag[i]=FLASE;
    REMAINDER SECTION
} while(TURE);

2、Dekkers算法(与Peretson改入临界区条件)

use two shared data items
int  turn=0; // who ‘s turn to enter the critical section
bollean flag[];  //whether the process is ready to enter
flag[0]:=false;flag[1]=false;
Dekkers算法
do{
    flag[i] = TURE;
    while(flag[j] == turn==j ) {
        if(turn!=i){
            flag[i] := false
            while turn!=i{}
            flag[i]:=ture
        }
    };
    CRITICAL SECTION
    turn := j;
    flag[i]=FLASE;
    REMAINDER SECTION
} while(TURE);

3、N线程的软件方法(Eisenberg and Mcguire’s algorirhm)

四、高级抽象的同部方法

硬件提供了一些原语,用原子操作直接实现进退临界区

1、锁是一个抽象的数据结构,获得锁就是进入临界区的实现过程

lock_next_pid->acquire();

new_pid=next_pid++ ;

lock_next_pid->release();

多数现代体系结构都提供特殊的原子操作指令 

(1) test-and-set 测试和置位 

从内存中读值,判断是否为1并返回,同时设置内存值为1 

(2) exchange 交换 输入两个内存单元,交换其值并返回 

这两条如果有一条可以是原子指令,就可以完成

class lock{ int value = 0; waitqueue q;}
lock::acquire(){
while (test-and-set(value)){
add this TCB to wait queen q;   //加入wait queen list
schedule();}
}
lock::release(){
value=0;
remove one thread t from q;
wakeup(t);
}

2、特征

①、优点:实现简单,易扩展到多临界区,开销小,适用于单处理器或共享主存的多处理器中任意数量的进程 广泛使用

②、缺点: 还是有忙等,浪费时间;

抢LOCK随机可能某个一直抢不到,当进程离开临界区,且多个进程在等待时可能导致饥饿;

也许死锁,一个低优先级的进程拥有临界区,一个高优先级的 进程也需求,那么高优先级进程忙等,占用cpu,低优先级的不能释放Lock,要通过优先级反转来解决。

③总结:

用锁来解决互斥问题,锁是高层编程抽象,需要一定硬件支持

常用三种:禁用中断(仅可单处理器),软件方法(复杂),原子操作指令(单处理器或多处理器都可以)

可选的实现内容:有忙等待,无忙等待(进程睡眠)

猜你喜欢

转载自blog.csdn.net/qq_36552489/article/details/92800054