目录
1 进程的常见状态以及转换
就绪:进程已经获取到除了cpu以外的所有资源,只要获得cpu时间片,就能进入运行状态
阻塞(等待):进程等待另一事件或者IO,放弃处理机而处于暂停状态
运行:进程获得cpu,正在运行
2 进程同步
进程同步的目的:对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合作
2.1 进程同步的方式
临界资源(临界区):指一次只能允许一个进程使用的共享资源称为临界资源;
同步:指为完成某种任务而建立的两个和多个进程,这些进程在合作的过程中需要协调工作次序进行有序的访问而出现等待所产生的制约关系。
- 互斥量 Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
- 信号量 Semphare:本身是一个计数器,使用P,V两个操作来实现计数的减与加,当计数不大于0时,则进程进入睡眠状态,它用于为多个进程提供共享数据对象的访问。
- 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作
2.2 同步机制遵循的原则
- 空闲让进:无进程进入临界区时,相应的临界资源处于空闲状态,因而允许一个请求进入临界区的进程立即进入自己的临界区。
- 忙则等待:当已有进程进入自己的临界区时,其它试图进入临界区的进程必须等待保证对临界区的互斥访问
- 有限等待:对要求访问临界资源的进程,应保证进程能在有限时间进入临界区,避免“饥饿”
- 让权等待:当进程不能进入自己的临界区时,应该释放处理机,以免陷入忙等状态
3 进程通信的方式
进程通信,是指进程之间的信息交换(信息量少则一个状态或数值,多者则是成千上万个字节)
管道:管道是单向的,先进先出的,无结构的,固定大小的字节流,它把一个进程的标准输出和另一个进程的 标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的头端读取数据。数据读出后将从管道中移走,其他读进程都不能在读到这些数据
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。 他常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点
信号:信号是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生
共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
套接字:套接字也是一种进程间通信机制,与其他通信机制不同的是,它可以用于不同机器间的进程通信
4 线程
4.1 进程与线程的区别
我们从调度,并发性,系统开销,拥有资源等方面来比较线程与进程
- 调度
线程是调度和分派的基本单位
进程是资源拥有的基本单位
在同一个 进程中,线程的切换不会引起进程的切换,在由一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换
- 并发性
进程之间可以并发,进程中的多个线程之间,也可并发,因而使操作系统具有更好的并发性,从而能够有效的使用系统资源和提高系统吞吐量
- 拥有资源
进程是拥有资源的一个独立单位。一般地说,线程自己不拥有系统资源(一个线程必不可少的资源:线程标识符,程序计数器,一组寄存器的值,和堆栈),但是它可以访问其隶属于进程的资源:代码段,数据段,以打开的文件,I/O设备等
- 系统开销
由于在创建或撤销进程时,系统都要为之分配或回收资源,如内存空间,i/o设备等。在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调用运行的进程的CPU环境的设置。
而线程切换只需保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易
4.2 用户级线程与内核线程
- 用户级线程(user level thread)
这类线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。
在应用程序启动后,操作系统分配给该程序一个进程号,以及其对应的内存空间等资源。应用程序通常先在一个线程中运行,该线程被成为主线程。在其运行的某个时刻,可以通过调用线程库中的函数创建一个在相同进程中运行的新线程。
优点:是非常高效,不需要进入内核空间。
线程的切换无需陷入内核,故切换开销小。速度非常快
线程的调度不需要内核直接参与,控制简单
允许每个进程定制自己的调度算法,线程管理比较灵活
线程能够利用的表空间和堆栈空间比内核级线程多。
缺点:并发效率不高,同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起
- 内核级线程(kernel level thread)
这类线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只能调用内核线程的接口。
内核维护进程及其内部的每个线程,调度也由内核基于线程架构完成。
优点:内核可以将不同线程更好地分配到不同的CPU,以实现真正的并行计算,当一个线程阻塞时,内核根据选择,可以运行同一个进程 或其他进程
缺点:由内核进行调度
即使CPU在同一个进程的多个线程之间切换。也须要陷入内核,因此其速度和效率不如用户级线程。
用户级、核心级线程的区别
- 用户级、核心级线程相结合
一些系统中,使用组合方式的多线程实现, 线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行. 一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。
用户级、内核级线程的区别
- 内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的
- 用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
- 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
- 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
- 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。
5 进程调度
5.1 上下文切换
上下文切换(Context Switch):一种将CPU资源从一个进程分配给另一个进程的机制。
从用户角度看,计算机能够并行运行多个进程。
切换过程:
- 存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等)
- 读入下一个进程的状态,然后执行此进程。
5.2 进程调度
调度种类
-
高级调度:(High-Level Scheduling)又称为作业调度,它决定把后备作业调入内存运行;
-
低级调度:(Low-Level Scheduling)又称为进程调度,它决定把就绪队列的某进程获得CPU;
-
中级调度:(Intermediate-Level Scheduling)又称为在虚拟存储器中引入,在内、外存对换区进行进程对换。
非抢占式调度与抢占式调度
-
非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
-
抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。
调度策略的设计
-
响应时间: 从用户输入到产生反应的时间
-
周转时间: 从任务开始到任务结束的时间
CPU任务可以分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽可能短,用户不至于感到延迟,同时使得批处理任务的周转时间尽可能短,减少用户等待的时间。
5.3 调度算法
先来先服务算法,FIFO或First Come, First Served (FCFS)
-
调度的顺序就是任务到达就绪队列的顺序。
-
公平、简单(FIFO队列)、非抢占、不适合交互式。
-
比较有利于长作业,而不利于短作业。因为长作业会长时间占据处理机。
-
有利于CPU繁忙的作业,而不利于I/O繁忙的作业。
短作业优先算法,Shortest Job First (SJF)
-
最短的作业(CPU区间长度最小)最先调度。
-
SJF可以保证最小的平均等待时间。
-
要的不足之处是长作业的运行得不到保证
最高响应比优先算法
R=(w+s)/s (R为响应比,w为等待处理的时间,s为预计的服务时间)
根据响应比完成调度。优点:长作业也有机会投入运行,缺点:每次调度前要计算响应比。
优先级调度算法
-
根据进程优先级决定运行的进程
-
注意:优先权太低的任务得不到运行,出现“饥饿”现象。
时间片轮转调度算法,Round-Robin(RR)
-
设置一个时间片,按时间片来轮转调度
-
时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。
-
如果时间片很小,那么处理机在进程间频繁切换,处理机真正用于运行用户进程的时间将减少
多级反馈队列算法
- 设置多个就绪队列,并为各个队列赋予不同的优先级。在优先权越高的队列中,为每个进程所规定的执行时间片就越小。
- 当一个新进程进入内存后,首先放入第一队列的末尾,按照先来先去原则排队等候调度。如果他能在一个时间片中完成,便可撤离;如果未完成,就转入第二队列的末尾,同样等待调度.....如此下去,当一个长作业(进程)从第一队列依次将到第n队列(最后队列)后,便按第n队列时间片轮转运行。
- 仅当第一队列空闲的时候,调度程序才调度第二队列中的进程运行;仅当第1到(i-1)队列空时,才会调度第i队列中的进程运行,并执行相应的时间片轮转。
- 如果处理机正在处理第i队列中某进程,又有新进程进入优先权较高的队列,则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾。
-
在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。
-
可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。
-
最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等。
6 死锁
两个或多个并发进程中,如果每个进程持有某种资源而又都等待别的进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗地讲,就是两个或多个进程被无限期地阻塞、相互等待的一种状态。
6.1 产生死锁的必要条件
-
互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
-
请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源,它请求的资源不能立即获得,需要等待。
-
非抢占条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
-
循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。
6.2 解决死锁的方法
- 预防死锁:破坏产生死锁的四个必要条件中的一个或几个条件。
- 避免死锁:在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。如银行家算法
- 检测死锁:允许系统在运行过程中发生死锁,但通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源; 然后,采取适当措施,从系统中将已发生的死锁清除掉。通过资源分配图
- 解除死锁:当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤消或挂起一些进程,以便回收一些资源。
6.2.1 预防死锁
- 打破互斥条件:允许进程同时访问某些资源。但是,有些资源是不能被多个进程所共享的,这种方法难以实现。
- 打破请求与保持条件:一次性分配所有的资源。缺点:在很多情况下,无法预知一个进程执行前所需的全部资源。同时也降低了资源利用率
- 打破非抢占条件:允许进程强行从占有者哪里夺取某些资源。这种方式实现起来困难,会降低系统性能
- 打破循环等待条件:有序分配资源,对所有资源排序编号,所有进程对资源的请求必须严格按资源序号递增的顺序提出,即只有占用了小号资源才能申请大号资源
6.2.2 避免死锁
基本思想:是动态地检测资源分配状态,以确保循环等待条件不成立,从而确保系统处于安全状态
安全状态:如果系统能按某个顺序为每个进程分配资源(不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态
经典算法:
- 银行家算法
需要四个矩阵:
Max最大需求矩阵:n×m的矩阵,它定义了系统中n个进程中的每一个进程对m类资源的最大需求
Allocation分配矩阵:n×m的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数
Need还需要的需求矩阵:n×m的矩阵,用以表示每一个进程尚需的各类资源数
Available可利用资源矩阵:是个含有m个元素的数组,其中的每一个元素代表一类可利用的资源数目
如:
主要步骤是:
- 寻找need<available的进程,即剩余进程能否支持其中一个进程运行完成,若不能,则不安全
- 若能,则该进程运行完,available就变成了剩余的available加上该进程的max,因为运行完后,该进程释放了它所有的资源
- 以此类推,若存在一个运行序列,能是所有进程都运行完,则当前序列处于安全状态,则同意第一步中分配资源给该进程的请求
6.2.3 检测死锁
- 资源分配图算法
p是进程,矩形是资源,矩形里的圆是还有多少资源,从资源到进程的箭头指分配给了进程资源,从进程到资源的箭头指进程请求一个资源
步骤:
- 第一步:先看系统还剩下多少资源没分配,再看有哪些进程是不阻塞(“不阻塞”即:系统有足够的空闲资源分配给它)的
- 第二步:把不阻塞的进程的所有边都去掉,形成一个孤立的点,再把系统分配给这个进程的资源回收回来
- 第三步:看剩下的进程有哪些是不阻塞的,然后又把它们逐个变成孤立的点。
- 第四步:最后,所有的资源和进程都变成孤立的点。这样的图就叫做“可完全简化”。
如果一个图可完全简化,则不会产生死锁;如果一个图不可完全简化(即:图中还有“边”存在),则会产生死锁。
6.2.4 解除死锁
- 撤销进程
撤消陷于死锁的全部进程
逐个撤消陷于死锁的进程,直到死锁不存在
- 剥夺资源
将陷于死锁的进程所占用的资源逐个强迫放弃,直至死锁消失
从另外一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态