文章目录
- 1 进程与线程
-
- 1.1 进程的概念和特征
-
- 1.2 进程的状态与转换
- 1.3 进程控制
-
- 1.3.1 进程的创建
- 1.3.2 进程的终止
- 1.3.3 进程的阻塞和唤醒
- 1.3.4 进程切换
- 1.4 进程的通信
- 1.5 线程概念和多线程模型
-
- 1.5.1 线程的概念
- 1.5.2 线程与进程的比较
- 1.5.3 线程的属性
- 1.5.4 线程的实现方式
- 1.5.5 多线程模型
- 2. 处理机调度
-
- 2.1 调度的概念
-
- 2.1.1 调度的基本概念
- 2.1.2 调度的层次
- 2.1.3 三级调度的联系与对比
- 2.2 调度的时机、切换与过程
- 2.3 进程调度方式
- 2.4 调度的基本准则
- 2.5 典型的调度算法
- 3. 进程同步
-
- 3.1 进程同步的基本概念
- 3.2 实现临界区互斥的基本方法
-
- 3.2.1 软件实现方法
- 3.2.2 硬件实现方法
- 3.2.3 管程
- 4. 死锁
-
- 4.1 死锁的概念
-
- 4.1.1 死锁的定义
- 4.1.2 死锁产生的原因
- 4.1.3 死锁产生的必要条件
- 4.2 死锁预防的策略
- 4.3 死锁预防
- 4.4 死锁避免
- 4.5 死锁的检测与解除
1 进程与线程
1.1 进程的概念和特征
1.1.1 进程的概念
引入进程的概念,更好地描述和控制程序的并发执行,实现操作系统的并发性和共享性。
PCB:进程控制块,使参与并发执行的程序能独立运行。是进程存在的唯一标志。
系统利用PCB来描述进程的基本情况和运行状态,进而控制和管理进程。相应地,由程序段、相关数据段和PCB三部分构成了进程实体(进程映像)。
进程定义:进程使进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
1.1.2 进程的特征
- 动态性:进程是程序的一次执行,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。
- 并发性:指多个进程实体同时存在于内存中,能在一段时间内同时运行。引入进程的目的就是为了使程序能与其他进程的程序并发执行,以提高资源利用率。
- 独立性:指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。
- 异步性:进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性。
- 结构性:从结构上看,进程是由程序段、数据段和进程控制块三部分组成。
1.2 进程的状态与转换
- 运行态:进程正在处理机上运行。
- 就绪态:进程获得了除处理机外的一切资源,一旦获得处理机,便可立即运行。
- 阻塞态:进程正在等待某一事件而暂停运行。如等待除处理机外的某一资源或等待输入/输出完成。
- 创建态:进程正在被创建,尚未转到就绪态。
- 结束态:进程正从系统中消失,可能是进程正常结束或其他原因中断退出运行。
进程状态的转换:
1.3 进程控制
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。在操作系统中,一般把进程控制用的程序段称为原语。
1.3.1 进程的创建
允许一个进程创建另一个进程。此时创建者称为父进程,被创建者称为子进程。子进程可以继承父进程所拥有的资源。当子进程被撤销时,应将其从父进程那里获得的资源归坏给父进程。在撤销父进程时,必须同时撤销其所有的子进程。
在操作系统中,终端用户登录系统、作业调度、系统提供服务和用户程序的应用请求等都会引起进程的创建。(注:设备分配不会导致创建新进程)
1.3.2 进程的终止
引起进程终止的事件主要有:
- 正常结束:表示进程的任务已经完成并准备退出运行
- 异常结束:表示进程在运行时,发生了某种异常事件,使程序无法继续运行
- 外界干预:指进程应外界的请求而终止运行
1.3.3 进程的阻塞和唤醒
- 正在执行的进程,由于期待的某些事件尚未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作可做等,由系统自动执行阻塞原语,使自己由运行态变为阻塞态。
- 当被阻塞进程所期待的事件出现时,如它所启动的I/O操作已完成或其他所期待的数据已到达,由有关进程调用唤醒原语,将等待该事件的进程唤醒。
1.3.4 进程切换
进程切换是指处理机从一个进程的运行转换到另一个进程上运行,在这个过程中,进程的运行环境发生了实质性的变化。
调度与切换的区别:调度是指决定资源分配给哪个进程的行为,是一种决策行为;切换是指实际分配的行为,是执行行为。一般来说,先有资源的调度,再有进程的切换。
1.4 进程的通信
进程通信是指进程之间的信息交换。PV操作是低级通信方式,高级通信方式是指以较高的效率传输大量数据的通信方式。
进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相对独立。为了保证进程的安全,一个进程不能直接访问另一个进程的地址空间,但是进程之间的信息交换又是必须实现的,为了保证进程间的安全通信,操作系统提供了共享存储、消息传递和管道通信等一些方法。
高级通信方式主要有以下三类:
- 共享存储:在通信的进程之间存在一块可以直接访问的共享空间,通过对这片共享空间进行读/写操作实现进程之间的信息交换。共享存储又分为两种:低级方式的共享是基于数据结构的共享,高级方式的共享是基于存储区的共享。
- 消息传递:在消息传递系统中,进程间的数据交换是以格式化的消息为单位的。若通信的进程之间不存在可以直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信。进程通过系统提供的发送消息和接收消息两个原语进程数据交换。
- 管道通信:管道是指用于连接一个读进程和一个写进程以实现他们之间的通信的一个共享文件。
- 管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
- 各进程要互斥地访问管道。
- 数据以字符流的形式写入管道,当管道写满时,写进程的write()系统调用将被阻塞,等待读进程将数据取走。当读进程将数据全部取走后,管道变空,此时读进程的read()系统调用将被阻塞。
- 如果没写满,就不允许读。如果没读空,就不允许写。
- 数据一旦被读出,就从管道中被抛弃,这就意味着读进程最多只能有一个,否则可能会有读错数据的情况。
1.5 线程概念和多线程模型
1.5.1 线程的概念
引入进程是为了更好地使多道程序并发执行,提高资源利用率和系统吞吐量;而引入线程的目的则是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
线程时轻量级进程,它是一个基本的CPU执行单元,也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。线程不拥有资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程,同一个进程间的多个线程也可以并发执行。线程也有就绪、阻塞和运行三种基本状态。
引入线程机制后,带来的变化如下图所示:
1.5.2 线程与进程的比较
- 调度。在引入线程的操作系统中,线程是独立调度的基本单位,进程是拥有资源的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,会引起进程切换。
- 拥有资源。进程是拥有资源的基本单位,而线程不拥有系统资源(也有一点儿必不可少的资源),但线程可以访问其隶属进程的系统资源。若线程也是拥有资源的单位,则切换线程就需要较大的时空开销,线程这个概念的提出就没有意义。
- 并发性。不仅进程之间可以并发执行,而且多个线程之间也可以并发执行,从而使操作系统具有更好的并发性,提高了系统的吞吐量。
- 系统开销。由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、IO设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程CPU环境的保存及新调度到进程CPU环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此这些线程之间的同步与通信非常容易实现,甚至无须操作系统的干预。
- 地址空间和其他资源(如打开的文件)。进程的地址空间之间互相独立,同一进程的各线程间共享进程的资源,某进程内的线程对于其他进程不可见。
- 通信方面。进程间通信需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以直接读/写进程数据段来进行通信。
1.5.3 线程的属性
1.5.4 线程的实现方式
线程的实现方式可分为两类:用户级线程和内核级线程。
-
用户级线程由应用程序通过线程库实现。所有的线程管理工作都由应用程序负责(包括线程切换)。在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。
优点:用户及线程的切换在用户空间即可完成,不用切换到核心态,线程管理系统的开销小,效率高。
缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理器上并行运行。
-
内核级线程的管理工作由操作系统内核完成。线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成。
优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可以在多核处理器上并行执行。
缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统完成,需要切换到核心态,成本高,开销大。
组合方式如下:
1.5.5 多线程模型
- 多对一模型:多个用户及线程映射到一个内核级线程。每个用户进程只对应一个内核级线程。
- 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高
- 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行
- 一对一模型:一个用户及线程映射到一个内核级线程。每个用户进程有与用户级线程同数量的内核级线程。
- 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。
- 缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。
- 多对多模型: n用户及线程映射到m个内核级线程(n >= m) 。每个用户进程对应m个内核级线程。
克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点。
2. 处理机调度
2.1 调度的概念
2.1.1 调度的基本概念
处理机调度是对处理机进行分配,即从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行,以实现进程并发地执行。
2.1.2 调度的层次
由于内存空间有限,有时无法将用户提交的作业全部放入内存,因此就需要确定某种规则来决定将作业调入内存的顺序。
一个作业从提交开始直到完成,往往要经历以下三级调度:
- 作业调度:又称高级调度,按一定的原则从外存上处于后备队列的作业中挑选一个或多个作业,给他们分配内存等必要资源,并建立相应的进程,以使它们获得竞争处理机的权利。
- 中级调度:又称内存调度,其作用是提高内存利用率和系统吞吐量。应将那些暂时不能运行的进程调至外存等待,把此时的进程称为挂起态。当他们已具备运行条件且内存又稍有空闲时,由中级调度来决定把外存上的那些已具备运行条件的就绪进程再重新调入内存,并修改其状态为就绪态,挂在就绪队列上等待。
- 进程调度:又称低级调度,其主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。进程调度频率很高,一般几十毫秒一次。
七状态模型如下所示:
2.1.3 三级调度的联系与对比
2.2 调度的时机、切换与过程
进程调度与切换程序是操作系统内核程序。请求调度的事件发生后,才可能运行进程调度程序,调度了新的就绪进程后,才会进行进程间的切换。
现代操作系统中,不能进行进程的调度与切换的情况有以下几种:
- 在处理中断的过程中
- 进程在操作系统内核程序临界区中
- 其他需要完全屏蔽中断的原子操作过程中
应当进行进程调度与切换的情况如下:
- 发生引起调度条件且当前进程无法继续运行下去时,可以马上进行调度与切换。
- 中断处理结束或自陷处理结束后,返回被中断进程的用户态程序执行现场前,若置上请求调度标志,即可马上进行进程调度与切换。
2.3 进程调度方式
进程调度方式是指当某个进程正在处理机上执行时,若有某个更为重要或紧迫的进程需要处理,即有优先权更高的进程进入就绪队列,此时应如何分配处理机。
- 非剥夺调度方式,又称非抢占方式。只允许进程主动放弃处理机,在运行过程中即便有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态。
- 剥夺调度方式,又称抢占方式。当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程。
2.4 调度的基本准则
- CPU利用率:指CPU忙碌的时间占总时间的比例
- 系统吞吐量:单位时间内CPU完成作业的数量
- 周转时间:指从作业提交到作业完成所经历的时间,是作业等待、在就绪队列中排队、在处理机上运行及进行输入/输出操作所花费时间的总和。
周转时间=作业完成时间 - 作业提交时间
带权周转时间=作业周转时间/作业实际运行时间
- 等待时间:进程处于等待处理机状态的时间之和
- 响应时间:从用户提交请求到系统首次产生相应所用的时间
2.5 典型的调度算法
- 先来先服务调度算法(FCFS):选择最先进入队列的,属于不可剥夺算法。
特点:算法简单,但效率低;对长作业有利,但对短作业不利;有利于CPU繁忙型作业,而不利于I/O繁忙型作业。
- 短作业优先调度算法(SJF):选择完成时间最短的,存在饥饿问题。
特点:算法的平均等待时间、平均周转时间最少;对长作业不利;未考虑作业的紧迫程度,不能保证紧迫型作业会得到及时处理。
- 优先级调度算法(PR):选择优先级别最高的。根据新的更高优先级进程能否抢占正在执行的进程,调度算法分为非剥夺式优先级调度算法和剥夺式优先级调度算法;根据进程创建后其优先级是否可以改变,进程优先级分为静态优先级和动态优先级。
优先级的设置参考如下:
系统进程 > 用户进程
交互型进程 > 非交互型进程
I/O型进程 > 计算型进程
- 高响应比优先调度算法:选择响应比最高的,不会发生饥饿现象。响应比 = (等待时间 + 要求服务时间)/ 要求服务时间
- 时间片轮转调度算法:总是选择就绪队列中的第一个进程,但仅能运行一个时间片。若时间片足够大,以至于所有进程都能在一个时间片内执行完毕,则时间片轮转调度算法就退化为先来先服务调度算法;若时间片很小,则处理机就在进程间过于频繁的切换,使处理机开销增大。
- 多级反馈队列调度算法:时间片轮转调度算法和优先级调度算法的综合和发展。通过调整进程优先级和时间片大小,多级反馈队列调度算法可以兼顾多方面的系统目标。例如,为提高系统吞吐量和缩短平均周转时间而照顾短进程;为获得较好的I/O设备利用率和缩短响应时间而照顾I/O型进程;同时,也不必事先估计进程的执行时间。
3. 进程同步
3.1 进程同步的基本概念
在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入进程同步的概念。
将一次仅允许一个进程使用的资源称为临界资源,访问临界资源的那段代码称为临界区。
- 同步:协调进程的执行次序,使并发进程间能有效地共享资源和相互合作,保证数据一致性
- 互斥:当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一个进程才允许区访问此临界资源。
为禁止两个进程同时进入临界区,同步机制应遵循以下准则:
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待
3.2 实现临界区互斥的基本方法
3.2.1 软件实现方法
在进入区设置并检查一些标志来标明是否有进程在临界区中,若已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。
算法一:单标志法
算法二:双标志先检查法
算法三:双标志后检查法
算法四:皮特森算法(Peterson’s Algorithm)
3.2.2 硬件实现方法
- 中断屏蔽方法
- 硬件指令方法,如TestAndSet指令,Swap指令
- 硬件方法的优点:对于任意数目的进程,而不管是单处理机还是多处理机;简单、容易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量。
- 硬件方法的缺点:进程等待进入临界区时要耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致“饥饿”现象。
3.2.3 管程
管程定义了一个数据结构和能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。
引入管程更方便地实现进程同步和互斥,解决信号量机制编程麻烦、易出错的问题。
管程解决的问题:
- 同步:设置条件变量及等待/唤醒操作以解决
- 互斥:由编译器负责保证
管程的组成:
- 管程的名称
- 局部于管程内部的共享结构数据说明
- 对该数据结构进程操作的一组过程
- 对局部于管程内部的共享数据设置初始值的语句
管程的基本特性:
- 局部于管程的数据只能被局部于管程的过程访问
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
- 每次仅允许一个进程进入管程
4. 死锁
4.1 死锁的概念
4.1.1 死锁的定义
死锁是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都将无法向前推进。
死锁与饥饿的区别如下:
4.1.2 死锁产生的原因
- 系统资源的竞争
- 进程推进顺序不当
- 信号量的使用不当也会造成死锁
4.1.3 死锁产生的必要条件
- 互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有,此时若有其他进程请求该资源,则请求进程只能等待。
- 不可剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程己获得的资源同时被下一个进程所请求。发生死锁时一定有循环等待,但是发生循环等待时未必死锁。
产生死锁必须满足如上四个条件,只要其中任意一个条件不成立,死锁就不会发生。
4.2 死锁预防的策略
为使系统不发生死锁,必须设法破坏产生死锁的4个必要条件之一,或允许死锁产生,但当死锁产生时能检测出死锁,并有能力实现恢复。
4.3 死锁预防
防止死锁的发生,只需要破坏死锁产生的4个必要条件之一即可。
- 破坏互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。
如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。
- 破坏不可剥夺条件:
方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。
方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。
破坏不可剥夺条件的缺点如下:
- 实现起来比较复杂。
- 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如CPU。
- 反复地申请和释放资源会增加系统开销,降低系统吞吐量。
- 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。
- 破坏请求并保持条件:可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。
破坏请求并保持策略的缺点:
- 有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。
- 该策略也有可能导致某些进程饥饿。
- 破坏循环等待条件:采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源一次申请完。
破坏循环等待条件的缺点:
- 不方便增加新的设备,因为可能需要重新分配所有的编号;
- 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费;
- 必须按规定次序申请资源,用户编程麻烦。
4.4 死锁避免
避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在资源动态分配的过程中,防止系统进入不安全状态,以避免发生死锁。
所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个。
如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。
如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁,即处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态。
因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是“银行家算法”的核心思想。
4.5 死锁的检测与解除
死锁检测算法:依次消除与不阻塞进程相连的边,直到无边可消。其中不阻塞进程指其申请的资源数还足够的进程。
死锁定理:简化资源分配图可检测系统状态是否为死锁状态。
死锁的条件是当且仅当其状态的资源分配图是不可完全化简的。
死锁解除:
- 资源剥夺法。挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但是应防止被挂起的进程长时间得不到资源而饥饿。
- 撤销进程法。强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。这种方式的优点是实现简单,但所付出的代价可能会很大。
- 进程回退法。让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息,设置还原点。