操作系统 —— 6 互斥

在这里插入图片描述

1 背景

在这里插入图片描述

在计算机系统中会有多个进程存在,多个进程之间还会进行交互,交互会引起对共享资源的访问,如果这些访问处理不当就会出现一些情况,如饥饿死锁等等,这些情况与调度相关
在这里插入图片描述

如果这些进程相互独立,他们不需要去访问同一个资源,他们之间也不需要交互,则每一个进程是确定的并且是可重复的
如果他们之间要交互要访问同一块资源,则会一会访问这个一会访问那个,并且结果也是不确定的,不可复现,会导致系统不稳定
在这里插入图片描述

2 并行并发提高系统效率,实现更有效地资源利用,例如把一个大的任务拆分成多个小的任务,通过流水或并行的方法提升系统性能,所以加速
3 大的工作分成小的工作
在这里插入图片描述

next_pid:当前系统中最高的pid值
LOAD next_pid Reg1 把next_pid赋给寄存器1,把内存单元的内容加载到寄存器中
STORE Reg1 new_pid 把寄存器1内容存到new_pid,把寄存器中的内容存到内存单元中去
INC Reg1 Reg++
STORE Reg1 next_pid

如果都做上面的操作的2个进程具有并发性:
在这里插入图片描述
过程:
在这里插入图片描述
产生上下文切换,造成错误。
预期是进程1的PID为100,进程2的PID为101,next_pid=102
在这里插入图片描述
我们想要无论怎么执行,最后的结果是固定的

1.1 一些概念1

产生不确定性和不可重现的现象可以用竞争条件来表述
在这里插入图片描述

如何保证四条指令的执行顺序不被打断,用原子操作来实现,让那四条指令不被打断一起执行
在这里插入图片描述

如果保证C语言的一条指令为原子的,也会出现这种被打断的奇怪的现象
在这里插入图片描述

i=i+1;i=i-1;都不是原子操作,由三四条原子操作的指令构成
可能出现的情况:

  • A 赢
  • B 赢
  • AB都打印不出来,A执行到i=i+1后,调度开始执行B,执行到 i=i-1,那么A和B都将一直循环,A和B都不能执行完(i是共享资源)

在这里插入图片描述

临界区(Critical section):访问共享资源的代码
互斥:不允许多个进程都进入临界区去访问,当多个进程进入临界区,可能会产生不确定的结果
死锁:谁也执行不下去
饥饿:一个进程持续得不到执行

1.2 一些概念2(通过编程来实现看是否会产生这些不确定的结果)

在这里插入图片描述
只需要买一份面包,但由于时间的差异性,导致不同的人在不同时刻做了重复性动作
在这里插入图片描述

什么是“面包太多”的问题的正确性质?

  1. 没有限制只能一个人买面包
  2. 需要买面包时才去买

如何解决?


  1. 在这里插入图片描述
    把冰箱都锁柱的话,锁的粒度就大了点,需要轻量级的方式,如便签
  2. 便签
    在这里插入图片描述
    程序中无效: 在这里插入图片描述
    修正:将leave Note;放到第一步,会出现更大的问题
    在这里插入图片描述
    因为进程A执行第1步放置标签,但还没开始执行第2步时,开始上下文切换,开始执行进程B的第1步留标签,然后2个都开始看是否有面包,再去看是否有标签,会导致谁也不回去买面包

1.3 一些概念3

在这里插入图片描述
执行顺序
leave note_1;
上下文切换
leave note_2;
还是会导致谁都无法执行
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  1. 能够确保总有一方能去执行购买面包
  2. 也不会造成2个人都去购买面包的情况
    进程A更有可能去购买面包
    在这里插入图片描述
    依旧存在问题:
    1. 2个进程都已经这么复杂了,很多进程不适应
    2. 我们希望A和B能够均等的去执行购买面包(对称性,打击都有同等概率去执行后续操作)

在这里插入图片描述

在这里插入图片描述

2 临界区(Critical section)

在临界区执行的一些属性:
在这里插入图片描述

无限等待就会造成饥饿的现象
尽量不要忙等,如在进入临界区前一直做死循环,太耗cpu资源,

  • 如果能确定可以很快进入临界区,那么忙等是有效的
  • 如果一直在等待,那么就很耗资源

讲三个方法之前:
在这里插入图片描述

3 方法1:禁用硬件中断

中断可以强制打断进程的执行,完成进程切换的能力
因为随时可以切换,所以结果具有不确定性

(简单有效,但受制于临界区执行时间,对系统的效率造成影响,不适应用于多CPU的情况)

进入临界区前禁用中断,离开临界区再开启中断
在这里插入图片描述
在这里插入图片描述

缺点:

  1. 中断是为了及时的外部响应(如外设产生的时间),来及时与外设进行交互,比如来一个网络包、时钟信号、磁盘块的读写,禁用中断意味着对于这些事件,系统无法及时响应,整体效率降低
  2. 临界区的时间可能很短也可能很长,如果很长,就会对系统造成影响。
  3. 如果2个都要去执行临界区资源,由2个CPU并行执行,中断机制一次屏蔽1个CPU的机制,使得问题无法解决,即中断机制对多CPU有一定的限制,因为当一个CPU执行屏蔽中断指令时,只是把自身响应中断暂时屏蔽,并不是把其他CPU的中断也屏蔽了,其他CPU依旧能产生中断,所以屏蔽中断无法解决多CPU的情况

4 方法2:基于软件的解决方法

(软件开销,时间复杂性较大)
依靠软件完成互斥

2个进程

在这里插入图片描述
使得只有1个线程进入临界区,而且还要保证线程能成功进去,并且是在有限得时间内进去
在这里插入图片描述
turn为进入临界区得次序,turn=0/1
过程:

  • 初始化 turn=0,代表轮到进程0进入临界区了;
  • turn不等于0在while(turn != i)循环内打转,代表没轮到这个进程,在外循环做死循环,一旦=0,跳出循环,进入临界区代码执行代码,退出临界区将j(另一个进程号,如1)赋给turn
  • turn=1时也是类似得操作
  1. 满足互斥,while循环保证只有1个进程进入临界区(turn 只有1个值)
  2. 不满足process(前进),因为假设进程0先进入临界区并退出后,turn=1,进程1进入临界区执行,turn=0,此时进程0不再需要进入临界区,而进程1需要进入临界区,此时turn=0,只有进程0进入临界区才会让turn=1,但是进程0不会在进入了,所以进程1永远无法得到执行

优化1:
在这里插入图片描述

flag[0]=1 代表 进程0 要进入临界区
flag[1]=1 代表 进程1 要进入临界区
若flag[j]=0,说明进程 j 要进入临界区,则让进程 j 进入临界区,进程 j 出临界区时flag[j]会变为0,此时跳出while(flag[j]==1)的循环,继而将flag[i]设为1,表明进程 i 想进入临界区,执行完毕后将flag[i]重新设为0,以便其他进程进入临界区

但是,上述方法没有互斥,因为初始化时flag[0]=flag[1]=0,他们均能通过while循环,然后执行flag[i]=1,flag[j]=1的代码,进而都能进入临界区

优化2:
在这里插入图片描述
在对其进行改进,即把flag[i]=1放在第一条,此时会满足互斥,但是存在死锁,因为执行进程i时,将flag[i]设成1,此时进程切换,开始执行进程j,将flag[j]设为1,这种情况下,while循环均过不去,就会产生死锁

正确的方法:
在这里插入图片描述

进入临界区:
while(flag[j] && turn==j)这句是说 j 要进入临界区,并且轮到 j 进入临界区了,进程 i 要等进程 j 进出完临界区才能跳出这个while循环,进入临界区
在这里插入图片描述

可以用反证法来证明以上算法满足互斥、有限等待、前进
假设2个进程都跳过了while循环,那么

  • 对于进程0:flag[1]=false || turn=1
  • 对于进程1:flag[0]=false || turn=0
    turn只能为0或1,那么只能跳出其中1个循环,进入其中一个循环

另一算法,也是可以的,但比较复杂
在这里插入图片描述

n个进程

n个进程解决方法之一:
在这里插入图片描述
大致思路:n个进程的互斥的保护,对于想进入临界区的进程i,如果有进程要进入临界区或者已经进入临界区,i进程就要等着;i后面的进程也要等着i完事之后才行,前提是i 想进入临界区

n个进程解决方法之二:
在这里插入图片描述
可以用去银行来想,去银行要先排队取票,但假如不同柜台取到相同的号,则进程号小的先进临界区(取钱)

软件方法总结:
在这里插入图片描述
软件实现开销、复杂性比较大

5 方法3:基于硬件的更高级的抽象(硬件原子操作指令)(常用)

在这里插入图片描述

假定这个原子操作抽象成lock
在这里插入图片描述
在这里插入图片描述
获得锁,则可以进入临界区

  1. TestAndSet
    在这里插入图片描述
    TestAndSet:无论之前内存中是多少,最后都要把它设为1
  2. Exchange
    在这里插入图片描述
    上述2段代码(TestAndSet和Exchange)都把3条语句封装成1条机器指令,也就是说在执行这3条语句时不允许被打断,不会产生中断或上下文切换

test-And-Set实现

在这里插入图片描述

过程:

  • 最开始时,value=0(表示没有1个进程进入临界区),当一个进程要进入临界区时,调用Lock::Acquire(),test-and-set(vaule)返回false并将value设为1,之后进入临界区去执行。
  • 当其他进程也要进入临界区时,因为vlaue=1,即锁处于忙状态,所以该进程要一直在这忙等,直到在临界区的进程出临界区时,value设为0,在忙等的1个进程跳出while循环,开始进入临界区执行,而其他想要进入临界区的接着忙等,这也可以体现出该过程对n个进程同样适用
    在这里插入图片描述
    问题:如果临界区执行时间过长,忙等时间就会长,消耗的CPU资源会多
    解决方法:让忙等的进程阻塞睡眠(挂到等待队列),就可以把CPU让出来给其他进程使用,处于临界区的进程出临界区时会把value赋为0,同时把一个等待的进程唤醒,
    在这里插入图片描述
    上面2者如何选择?
  • 当临界区很短时可以采用忙等的,因为上下文切换也是比较大的开销
  • 当临界区很长时,忙等开销大于上下文切换的开销,则采取非忙等

exchange实现

在这里插入图片描述
第一个想进入临界区的进程,key=1,lock=0,执行过exchange后,key=0,lock=1,跳出while循环,进入临界区,而其他想进入临界区时,key=1,lock=1无论怎样exchange也跳不出while循环。当第一个进入临界区的进程出临界区后,lock=0,则其余未进入临界区的第1个进程可以跳出while循环了,并进入临界区执行,而其他进程依旧继续等待

优缺点

在这里插入图片描述
在这里插入图片描述
饥饿现象:while循环会出现好多进程一起抢 lock,就会有其他进程一直抢不到的情况
实时系统会出现死锁的情况,低优先级的进程进入了临界区,这时来了一个高优先级的进程也要进入临界区而处于忙等的情况,那么此时会出现低优先级进程无法释放锁的情况(优先级反转)
在这里插入图片描述
**1个问题,锁在忙等的时候是FIFO吗?**谁抢到是谁的(?)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值