操作系统原理篇(三)

接着前面的管程:
在任何时候,只能口有一个线程活跃在管程内,责任由编译器承担,在管程内,使用两种同步机制:锁用来互斥,条件变量用来控制执行的顺序。从某种意义上来说,管程就是锁上面再加上条件变量。
条件变量:线程可以在上面等待的东西,二另外一个线程则可以通过发送信号将在条件变量上等待的线程叫醒,所以条件变量有点像信号量,但是又非信号量,因为不能对其进行up和down操作。
管程的中心思想
运行一个在管程里面睡觉的线程,但是睡觉前需要把进入管程的锁或者信号量释放,否则在睡觉后别的线程无法进入管程,就会造成死锁。
注意:锁的释放与睡觉必须是原子操作,否则造成两个线程同时活跃在管程内。

消息传递:send与receive

管程最大的问题就是对编译器的依赖。有的编译器确实也没有这功能。

send(destination,&message)receive(source,&message)

同步时阻塞调用,也就是说,如果调用receive,该线程就会被挂起,在收到消息后,才能转到就绪。
缺点:消息丢失和身份识别,效率低。比如设计TCP协议,协议本身也不是100%可靠,身份识别可以使用加密技术来弥补。

栅栏:障碍

到达栅栏的线程必须停下来,知道栅栏被去掉才能往前推进。
在这里插入图片描述

进程调度

在单CPU下,实际上任何时刻只能由一个进程或者线程处于执行状态,其他处于非执行状态。
程序运行的模式大概分为三种:
1):大部分时间在CPU上执行(CPU导向或计算机密集型程序)
2):大部分时间在进行数据输出(IO导向输入输出密集型程序)
3):介于前两种模式之间(平衡型程序)
处理器调度的总体目标
极小化平均响应时间,极大化系统吞吐量,保持系统各个功能部件均处于繁忙状态和提供某种貌似公平的机制。
先来服务器调度算法:FCFS
谁先来就先服务谁
时间轮转片
周期性的进行周期切换。
短任务优先:
优先级倒挂
指的是一个低优先级任务持有一个被高优先级认为所需要的共享资源,这样高优先级任务因为资源缺乏处于受阻状态,直到优先级低的任务资源释放为止。这样实际上造成了两个任务的优先级倒挂,如果这时又其他优先级介于二者之间的任务,并不需要这个共享资源,则该中级优先级的进程将会获得cpu控制,从而超越这两个任务。
如果高优先级进程再等待资源时不是阻塞等待,而是循环等待,则将永远无法获得所需资源,因为低优先级无法与高优先级进程争夺cpu,从而无法执行,进而无法释放资源,这将造成高优先级进程无法获得资源而继续推进。
优先级倒挂解决方案:
1)使用所有中断禁止:采用此策略的系统中只有两个优先级:可抢占优先级和中断优先级。
在多cpu的环境则使用一个简单的变种:单一共享标志锁。所有cpu进入跨cpu临界区都必须先获得该标志。
2)优先级上线
3)优先级继承

锁的实现

用中断启用与禁止来实现锁

明确切换途径:
1)一个线程自愿放弃cpu而将控制权交给操作系统调度器,从而发生上下文的切换
2)线程被强制放弃cpu而失去控制,需要空过中断来实现,操作系统主要是通过周期性的时钟来获得从cpu的控制权。
自愿放弃通过调用yield之类的操作系统调用来实现,而强制放弃则需要通过中断来实现,由于原语执行过程中,不会自动放弃cpu控制权,因此要防止进程切换,就要在你原语执行的过程中不能发生中断,因此,通过禁止中断,并且不自动调用让出cpu的系统调用(如yield之类),就可以方法之进程切换,就能将一组操作变为原子操作。
锁是受操作系统控制的,中断也是,用户可以调用锁,但是不能去初始化中断。
使用中断实现锁的程序
如图:lock的操作
在这里插入图片描述
闭锁的第一个操作是禁止中断,记住,这是一硬件原子操作,如果成功禁止了中断,接下来就检查value的值是否等于FREE,如果是,就表明这个资源没有被其他进程占用,我们就将其设置为忙,然后启用中断(记住,这也是一硬件原子操作),这样就完成了闭锁的操作,如果value不是FREE,则我们循环等待这个值变为FREE,但是在循环等待的过程中,我们使用了一对操作,分别是启用和禁止中断。我们禁止了中断,在进入闭锁后别的线程已经无法获得cpu,自然无法将value的值变为FREE,因此寄希望于启用和禁止中间那个占用value的进程切换进来,完成操作后释放value。
unlock的操作
在这里插入图片描述
unlock的操作就是先禁用中断,设置value的值为FREE,再启用中断。
注意:
value=FREE这个赋值操作不是原子的,所以需要禁止中断来保护,否则可能再赋值的过程中被冲掉。

用测试和设置指令来实现锁

现代的处理器基本上都提供一条所谓的“读-修改-写入”的原子指令,该操作以不可分割的方式执行如下两个操作:
1)将内存指定位置的存储单元的内容读到一个寄存器。
2)将新的值写入到刚才的内存单元。
而测试与设置test&set指令就是类似的一条指令,但是略微不同,他以不可分割的方式执行如下两个步骤:
1)设置操作:将1写入到指定的内存单元
2)读取操作:返回指定内存单元里原来的值
如图:
在这里插入图片描述
锁的实现:
在这里插入图片描述
如果锁是打开的,即value是0,该指令将设置为1,获得锁并退出循环,如果锁是闭合的,即value为1, 则返回1,循环继续,而该指令将value设置为1并不改变value的状态,该循环将一直持续到成功获得锁为止。开锁也很简单,将value设置为0即可。
如图:
在这里插入图片描述

用非繁忙等待,中断启用与禁止来实现锁:

1)使用中断禁止,但不进行繁忙等待
2)如果拿不到锁,等待进程放弃cpu并进入睡觉状态,以便持有锁的进程可以更好的运行
3)当释放锁的时候将睡觉的进程叫醒。
如图:获得锁的方式
在这里插入图片描述
闭锁:的第一个操作就是禁止中断,检查value的值,如果是空闲,接着将value的值设置为繁忙,然后开启中断,获得锁而退出。如果value是繁忙,就把自己加到等待该锁的队列里面去,然后切换到别的进程,最后启用中断。
开锁的方式:
在这里插入图片描述
开锁:先禁止中断,接着设置value的值为FREE,然后检查是否有进程等待在该锁上,如果有,将等待线程从等待队列移到就绪队列,再将value的值设置为繁忙,然后启用中断。

用最少繁忙等待,测试与设置来实现锁

锁:
在这里插入图片描述
与前面的9-8图类似,guard如果为1,将一直循环等待,直到guard变为0,检查value的值,如果是你FREE,将其设置为BUSY,将guard设置为0,获得锁退出lock系统调用。
如果value的值等于繁忙,先将guard设置为0,将自己加到等待队列上,然后切换到别的线程,在这种情况下,则存在另一个线程持有value这把锁,线程再做完自己的事情后,将调用unlock释放锁,而unlock首先测试guard,如果没有人持有,该线程将value设置为 FREE,然后检查是否有线程再锁上等待。这种情况下,这时就像等待线程移动到就绪队列上,然后将锁交给被叫醒的线程,然后将guard设置为0。
开锁:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42271802/article/details/106855094