如果把计算机的操作系统比作一个大的工厂,那么进程就是这个工厂中的各个相互独立的车间,线程指的是车间中的流水线工人。每个车间中至少有一个工人,一个车间也也可以有多个工人,他们共享这个车间中的所有资源。
也就是说,一个进程中可以有多个线程,但一个进程中至少有一个线程,他们共享这个进程下的所有资源。
为了可以使程序能够并发执行,并且可以对并发执行的程序加以描述和控制,人们引入了进程这个概念。
进程:进程就是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
进程实体 :由程序段,相关数据段和PCB三部分就组成了进程实体(进程映像),一般情况下,我们把进程实体简称为进程,例如,所谓创建进程就是创建进程实体中的PCB,而撤销进程就是撤销进程中实体的PCB。
进程控制块(PCB):在操作系统中必须为之匹配一个专门的数据结构,我们称之为进程控制块(Process Control Block,PCB),系统利用PCB来描述进程的基本情况和活动过程。
由于一个进程不会总是占着CPU,致使他们在运行过程中呈现间断性的运行特点。所以进程在其生命周期内可能存在多种状态,但一般而言,每一个进程至少应处于下面三种基本状态之一:
a.就绪状态: 这是指进程以及具备运行的条件,只要再获得CPU,即可立即执行。如果系统中有多个处于就绪状态的进程,通常按照一定的策略将他们排成一个队列,即就绪队列。
b.执行状态: 进程独占这CPU,并且在CPU上运行。显然处于这种状态的进程数量<=CPU的数目。若只有一个CPU那么任何时刻最多只能有一个进程处于运行状态。
c.阻塞状态: 这是指正在执行的进程因为某些事件(如I/O请求等)暂时无法继续执行的状态。此时处理机分配给另一个就绪进程,让受阻进程暂停。同就绪队列,也存在一个阻塞队列。
a. 创建状态:进程申请一个空白PCB,并向PCB中填入用于控制和管理进程的信息,然后为该进程分配运行所必要的资源。最后把该进程转入就绪状态并插入就绪队列中。
b. 终止状态:等待操作系统进行善后处理,最后将其PCB清零,并将PCB返回空间。在进程结束,或出现错误,或被系统终止,进入终止状态,无法再执行。
进程通信是指进程间的信息交换。在进程的互斥与同步中,也在进程之间交换了一些信息,因此不少学者也将他们归为低级进程通信。他们低级的原因在与:① 效率低 ② 通信对与用户不透明.
常见的高级通信机制可归为三类:共享存储器,管道通信系统,消息传递系统.
相互通信的进程间共享某些数据结构或者共享存储去,进程之间通过他们进行通信,据此把他们分为两种类型:基于共享数据结构,基于共享存储区。
管道也称共享文件方式,基于文件系统,利用打开的共享文件连接两个相互通信的进程,文件作为缓冲传输介质。就是在内存种开辟一个大小固定的缓冲区。
a. 管道只能采用半双工通信,某个时间段只能实现单向传输,如果要实现双向同时通信,则需要两个管道。
b. 各进程互斥地访问管道。
c. 数据以字符流的形式写入管道,当管道写满时,写进程的writer()系统调用被阻塞,等待读进程将数据取走,当读进程将数据全部取走后,管道变空,此时读进程read()系统调用被阻塞。
d. 数据一旦被读出,就从管道中被抛弃,这就意味着读进程最多只能有一个,否则可能会有读错数据的情况。
进程中的数据交换以格式化的消息为单位 进程通过操作系统提供的 发送消息/接受消息 两个原语进行数据交换。
进程是一个资源的拥有者,因而在创建,撤销和切换中,系统他需要付出较大的时空开销,这就限制了系统中所设置进程的数目,而且进程切换也少不易过于频繁,这就限制了并发程序的进一步提高。
引入线程的概念就是为了减少程序在并发执行时所付出的时空开销。
a.定义:
进程是操作系统资源分配的基本单位。
线程是任务调度和执行的基本单位。
b.在开销方面:
进程是使多个程序并发执行,提高系统资源利用率和吞吐量,系统开销比较大。
线程是提高系统并发性能,减少程序并发时的资源开销。
c.资源分配方面:
进程的地址空间是相互独立的,进程拥有内存资源。
线程自己不拥有系统资源,与其他线程共享同一进程拥有的资源。
d.包含关系:
没有线程的进程可以看做是单线程的,一个进程内可以有多个线程,运行过程是多条线程共同完成的;而线程是进程的一部分,也可以被看做轻量级进程。
调度:当有一堆任务要处理,但是由于资源有限,这些事情没办法同时处理,就需要某种规则来决定处理这些任务的顺序,这就是调度研究的问题。
处理机调度:从就绪队列中按照一定的算法选择一个进程并将处理机分配给他运行,以实现进程的并发。
在多道程序系统中,调度实质是一种资源分配,处理就调度算法是指根据处理机分配策略所规定的处理机分配算法。一个作业从获得处理机执行到作业运行完毕,可能会经历多级处理机调度。下面介绍处理机的层次。
高级调度又称为长程调度或者作业调度,它的调度对象是作业。主要功能是根据某种算法,决定将外存上处于后备队列中那几个作业调入内存,为它们创建进程,分配必要的资源,并将它们放入就绪队列。高级调度主要用于多道批处理系统,而在分时和实时系统中不设置高级调度。作用调度的频率很低,周期很长,大于几分钟一次。
中机调度又称为内存调度,引入中级调度的主要目的是,提高内存利用率和系统吞吐量。中级调度的作用就是讲暂时不能运行的进程,调至外存等待(挂起状态),和将外存上已经满足条件的就绪进程在调入内存中。内存调度的频率和周期处于,作业调度和内存调度之间。
低级调度有称进程调度或者短程调度,它的调度对象是进程。其主要功能是,根据某种算法,决定就绪队列中的那个进程获得处理机。并由分派程序将处理机分派给选择的进程。进程调度这是一种最基本的调度,在多道批处理,实时和分时三种类型的OS中,都必须配置这级调度。进程调度的频率很高,周期很短,在分时系统中大概仅10~100nm.
决策模式说明选择函数在执行的瞬间的处理方式, 一个调度算法是否能抢占,对进程的顺序有着极大的影响。通常分为以下两类:
非抢占:一旦进入运行状态,就不会终止直到运行结束。
抢占:当前正在运行的进程可以被打断,并转移到就绪态。
先来先服务是最简单的策略,也成为先进先出FIFO。首先它是一个非抢占的。如字面的意思,它根据进程到达时间决定先运行哪一个进程。
这里给出一个实际的例子。以表格的形式表现出在FIFO策略下各进程的情况。
简单说就是依次执行完成,从时间轴上来看
以表格的形式展现:
其中开始时间是上一个进程的结束时间
结束时间=开始时间+服务/执行时间
周转周期=结束时间-到达时间
带权周转时间=周转时间/服务时间
也称最短作业优先(Short Job First,SJF)。它也是一个非抢占的。是根据服务的时间经行选择。在这里要注意下到达时间的顺序。
比如实例中单纯以大小来排序的话是E-A-C-D-B,但正确的排序一定是A-B为开头。以时间为顺序:
例子中A运行结束时间为3,这时只有B进程等待。所以A运行结束后直接运行B。B结束后时间点到9,CDE都在等待。这个时候就选择服务时间最少的E,然后是较少的C,最后是D。以表格的形式展示:
SRT是针对SPN增加了抢占机制的版本,就好比例子中B运行时间非常长,在这期间其他所有的进程都在等待,如果将其中断,先处理所需时间少的,运行效率会有显著提升。一定要先明确SRT是抢占的。先给出时间为顺序的图:
1. A先运行至2,B到达等待。
2. A运行到3结束,B开始运行。
3. B开始运行,运行到4时,C进程到达,且C只需要4,此时B还需要5。所以先运行C,B继续等待。
4. C运行时间点到达6时,D到达,D需要5,进入等待,排在B后。
5. C运行结束,此时时间点是8,E到达,运行时间只要2,小于等待的BD,直接运行。
6. C运行结束,B开始运行。
7. B运行结束,D开始运行。
高响应比优先调度算法主要用于作业调度,该算法是对FCFS调度算法和SJF调度算法的一种综合平衡,同时考虑每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。
响应比的变化规律可描述为:
响应比=(等待时间+服务时间)/服务时间
根据公式可知:
当作业的等待时间相同时,则要求服务时间越短,其响应比越高,有利于短作业。
当要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越长,其响应比越高,因而它实现的是先来先服务。
对于长作业,作业的响应比可以随等待时间的增加而提高,当其等待时间足够长时,其响应比便可升到很高,从而也可获得处理机。克服了饥饿状态,兼顾了长作业。
轮转也称时间片技术(time slicing,SL),对于轮转法,最重要的是时间片的长度。轮转算法以一个周期(q)产生中断,当中断发生时,当前运行的程序置于就绪队列(队尾)中,然后基于FCFS选择下一个就绪作业运行。
在这里我们以时间片q=1举例。q=1,所以一次只能运行一个时间片。
0:A1运转(右标表示运行了几个)
1:A2运转
2:B1运转,A3等待(B开始)
3:A3运转,B2等待
4:B2运转,C1等待,(A结束)
5:C1运转,B3等待(C开始)
6:B3运转,D1等待,C2等待
7:D1运转,C2等待,B4等待(D开始)
8:C2运行,B4等待,E1等待,D2等待
9:B4运行,E1等待,D2等待,C3等待
10:E1运行,D2等待,C3等待,B5等待(E开始)
11:D2运行,C3等待,B5等待,E2等待
12:C3运行,B5等待,E2等待,D3等待
13:B5运行,E2等待,D3等待,C4等待
14:E2运行,D3等待,C4等待,B6等待
15:D3运行,C4等待,B6等待(E结束)
16:C4运行,B6等待,D4等待
17:B6运行,D4等待(C结束)
18:D5运行,D6等待(B结束)
19:D6运行
20:D结束
车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。
虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所使用,我们把一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机等。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源。
对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区。为了保证临界资源的正确使用,可以把临界资源的访问过程分成四个部分:
a.进入区。为了进入临界区使用临界资源,在进入区要检查可否进入临界区,如果可以进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区。
b.临界区。进程中访问临界资源的那段代码,又称临界段。
c.退出区。将正在访问临界区的标志清除。
d.剩余区。代码中的其余部分。
do {
entry section; //进入区
Critical section; //临界区
exit section; //退出区
remainder section; //剩余区
} while (true)
互斥亦称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待, 当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。
例如,在仅有一台打印机的系统中,有两个进程A和进程B,如果进程A需要打印时, 系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态。
同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。
例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。
为禁止两个进程同时进入临界区,同步机制应遵循以下准则:
a. 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。
b. 忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待。
c. 有限等待。对请求访问的进程,应保证能在有限时间内进入临界区。
d. 让权等待。当进程不能进入临界区时,应立即释放处理器,防止进程忙等待。
信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。要获取信号量资源,则对信号量进行-1操作,要释放信号量资源,则对信号量资源进行+1操作。
当信号量的值大于0时,表示当前可用资源的数量;
当信号量的值小于0时,其绝对值表示等待使用该资源的进程个数。
注:信号量操作是原子操作,信号量的值仅能由PV操作来改变。
原理:进程获取临界资源之前,要先获取信号量资源;
若无信号量资源,则该进程阻塞等待,进入等待队列。
若有信号量资源,则对信号量进行P(-1)操作,再获取临界资源。
当临界资源+1时,对应的信号量资源则执行V(+1)操作,然后唤醒在等待队列中等待获取临界资源的进程。
原理:一个进程获取了该临界资源之后,另一个进程无法再访问该临界资源。
实现互斥,采用一元信号量,即:该信号量的计数器,只能为0或1。
一个进程要获取临界资源时,先获取对应的信号量资源;
当无信号量资源时,则该进程阻塞等待,进入等待队列。
当有信号量资源时,则对该信号量资源进行P(-1)操作,然后获取该临界资源。
当该进程使用完临界资源时,将释放信号量资源(对信号量资源进行V(+1)操作),然后唤醒等待队列中的进程。
哲学家就餐问题
概念:多个并发进程因争夺系统资源而产生相互等待的现象。
原理:当一组进程中的每个进程都在等待某个事件发生,而只有这组进程中的其他进程才能触发该事件,这就称这组进程发生了死锁。
本质原因:系统资源有限、进程推进顺序不合理。
1)互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2)占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3)不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4)循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。
目的:确保系统永远不会进入死锁状态。
产生死锁需要四个条件,那么,只要这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享资源所必须的,不仅不能改变,还应加以保证,所以,主要是破坏产生死锁的其他三个条件。
a、破坏“占有且等待”条件
方法1:所有的进程在开始运行之前,必须一次性地申请其在整个运行过程中所需要的全部资源。
优点:简单易实施且安全。
缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费,使进程经常发生饥饿现象。
方法2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源,便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源。这样的话,资源的利用率会得到提高,也会减少进程的饥饿问题。
b、破坏“不可抢占”条件
当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。
该种方法实现起来比较复杂,且代价也比较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。
c、破坏“循环等待”条件
可以通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源。如图所示:
这样虽然避免了循环等待,但是这种方法是比较低效的,资源的执行速度回变慢,并且可能在没有必要的情况下拒绝资源的访问,比如说,进程c想要申请资源1,如果资源1并没有被其他进程占有,此时将它分配个进程c是没有问题的,但是为了避免产生循环等待,该申请会被拒绝,这样就降低了资源的利用率。
在使用前进行判断,只允许不会产生死锁的进程申请资源。
死锁的避免是利用额外的检验信息,在分配资源时判断是否会出现死锁,只在不会出现死锁的情况下才分配资源。
两种避免办法:
1)如果一个进程的请求会导致死锁,则不启动该进程。
2)如果一个进程的增加资源请求会导致死锁 ,则拒绝该申请。
避免死锁的具体实现通常利用银行家算法。
银行家算法:当一个进程申请使用资源的时候,银行家算法通过先 试探 分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。
首先是银行家算法中的进程:
包含进程Pi的需求资源数量(也是最大需求资源数量,MAX)
已分配给该进程的资源A(Allocation)
还需要的资源数量N(Need=M-A)
Available为空闲资源数量,即资源池(注意:资源池的剩余资源数量+已分配给所有进程的资源数量=系统中的资源总量)
假设资源P1申请资源,银行家算法先试探的分配给它(当然先要看看当前资源池中的资源数量够不够),若申请的资源数量小于等于Available,然后接着判断分配给P1后剩余的资源,能不能使进程队列的某个进程执行完毕,若没有进程可执行完毕,则系统处于不安全状态(即此时没有一个进程能够完成并释放资源,随时间推移,系统终将处于死锁状态)。
若有进程可执行完毕,则假设回收已分配给它的资源(剩余资源数量增加),把这个进程标记为可完成,并继续判断队列中的其它进程,若所有进程都可执行完毕,则系统处于安全状态,并根据可完成进程的分配顺序生成安全序列(如{P0,P3,P2,P1}表示将申请后的剩余资源Work先分配给P0–>回收(Work+已分配给P0的A0=Work)–>分配给P3–>回收(Work+A3=Work)–>分配给P2–>······满足所有进程)。如此就可避免系统存在潜在死锁的风险。
在银行家算法中,若出现下述资源分配情况:
利用安全性算法对上面的状态进行分析(见下表),找到了一个安全序列{P0,P3,P4,P1,P2},故系统是安全的。
在检测到运行系统进入死锁,进行恢复,允许系统进入到死锁状态。
系统死锁,可利用资源分配图来描述。
死锁定理:当且仅当 S 状态的资源分配图是不可完全简化的,该条件为死锁定理。
可以通过将资源分配图简化的方法来检测系统状态 S 是否为死锁状态。简化方法如下:
1)在资源分配图中,找到既不阻塞又不是孤点的进程 Pi (即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量)。消去它所有的请求边和分配边,使之成为孤立的结点。在这里要注意一个问题,判断某种资源是否有空闲,应该用它的资源数量减去它在资源分配图中的出度。
2)进程 Pi 所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可以变为非阻塞进程。根据 1)中的方法进行一系列简化后,若能消去图中所有的边,则称该图是可完全简化的。
如果利用死锁检测算法检测出系统已经出现了死锁 ,那么,此时就需要对系统采取相应的措施。
常用解除死锁的方法:
1)抢占资源:从一个或多个进程中抢占足够数量的资源分配给死锁进程,以解除死锁状态。
2)终止(或撤销)进程:终止或撤销系统中的一个或多个死锁进程,直至打破死锁状态。
a、终止所有的死锁进程。这种方式简单粗暴,但是代价很大,很有可能会导致一些已经运行了很久的进程前功尽弃。
b、逐个终止进程,直至死锁状态解除。该方法的代价也很大,因为每终止一个进程就需要使用死锁检测来检测系统当前是否处于死锁状态。另外,每次终止进程的时候终止那个进程呢?每次都应该采用最优策略来选择一个“代价最小”的进程来解除死锁状态。
一般根据如下几个方面来决定终止哪个进程: