目录
进程就是程序的一次执行过程,是系统进行资源分配和调度的基本单位。
注意:此时还未引入线程概念。
程序,是存放在硬盘中的一个可执行的文件,即,一系列的指令集合。在多道程序下,操作系统允许程序并发执行,这就导致这些程序之间失去了封闭性,为了更好地描述和控制程序的并发执行,操作系统引入了进程的概念。
当我们启动一个应用程序A后,可以在控制面板中看到有一个与之对应的进程,如果启动多个应用程序A以后,就会出现多个相对应的进程。对于被启动的多个应用程序A,其指令集合是相同的,但是其对应的每个进程,都有着各自的数据,即同一个应用程序执行多次会对应着多个不同的进程。
在介绍进程的组成之前,我们将要引入一个新的概念:进程实体(进程映像)。
进程实体是静态的,进程是动态的。我们可以把进程实体理解为进程在动态执行过程中某一时刻的快照,进程实体可以反映进程在某一时刻的状态。
如果不进行专门的区分,我们一般将进程实体就称为进程。它由进程控制块(PCB)、程序段以及数据段三部分组成。
进程控制块(Process Control Block,PCB)是一个专门为进程配置的数据结构,用于描述进程的基本情况和运行状态。
进程在创建时,操作系统会为它创建一个PCB,随后,这个PCB会被放置到内存中,在进程执行时,系统通过该进程的PCB可以了解进程的现行的状态信息,由此操作系统就可以对其进行控制和管理。当进程结束时,操作系统也会将PCB从内存中删除,进程也就随之消亡了。由此可以得知,PCB是进程存在的唯一标志。
系统是如何使用PCB控制进程的
由上述过程可知,在进程的整个生命周期中,系统都是通过PCB对进程进行控制的,即系统唯有通过PCB才能感知到该进程的存在。
PCB的组织方式
由于操作系统中,往往会存在着许多进程,有的处于就绪态、有的处于阻塞态,并且阻塞的原因各不相同。为了方便进程的调度和管理,就需要将各个进程的PCB用适当的结构组织起来。常见的方式有链接方式和索引方式两种。
大多数操作系统使用的都是链接方式
程序段就是被进程调度程序调度到CPU执行的程序代码段,即该进程在CPU中要被执行的代码段。
注意:一个程序可以被多个进程共享(程序段相同),即多个进程可以运行同一个程序。
进程的数据段包含了进程对应的程序加工处理的原始数据和进程运行过程中产生的中间/最终结果。
进程有着完整的生命周期,主要包括五种状态:创建态、就绪态、运行态、阻塞态以及结束态。
创建进程时,会分为多个步骤
运行态:进程在CPU上运行时所处的状态就是运行态。
进程PCB中,会有一个变量 state 来表示进程的当前状态。如:1表示创建态、2表示就绪态、3表示运行态
就绪态、运行态、阻塞态被称为进程的3种基本状态
注意:一个进程从运行态转为就阻塞态是进程主动的行为,从阻塞态转为就绪态是被动地行为,需要有其他进程与中断处理程序的协助。
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。即,进程控制就是实现进程状态的转换。
如何实现进程控制
进程控制即实现进程状态的转换。进程状态的转换过程需要保证“一气呵成”的完成。因此,我们可以使用原语来实现进程控制。
为什么一定要保证进程状态的转换要“一气呵成”的完成呢
假设此时有一个进程处于阻塞态(即该进程的PCB处于阻塞队列中),现在要将该进程转换为就绪态,此时负责进程控制的内核程序至少要做以下两件事:
如果不能保证上述两个步骤“一气呵成”的完成,例如在完成了第一步后,CPU收到了中断信号,进而去执行相应的中断处理程序,那么此时,该进程的PCB的状态是就绪态,但是PCB处于阻塞队列中,这就导致操作系统中的关键数据结构信息的不统一,从而影响操作系统进行后续的管理工作。
其实无论哪个进程控制原语,要做的无非三类事情:
进程通信(IPC)指进程与进程之间的信息交换(数据交换)。进程间的通信依赖于操作系统提供的支持。
比方说向微信好友分享文章,就需要进程通信
为什么进程间的通信依赖于操作系统的支持
我们知道,进程是操作系统分配系统资源的基本单位,各个进程拥有的内存地址是相互独立的。因此各个进程之间如果要进行数据交换,就必须依赖操作系统的支持。
为什么各个进程拥有的内存地址要相互独立
从系统安全的层面来理解,如果各个进程之间拥有的内存地址不是相互独立的,这就意味着,各个进程之间可以相互访问对方的数据。这将会是一个十分危险的事情,例如,你的某些APP中存储有你的私密信息(银行账户、身份证信息等等),此时,你安装了一个恶意的APP,当你同时运行这两个APP时,这个恶意的APP就可以随意的访问你的存有私密信息的APP的数据,那么你的私密信息就会存在暴露的风险。
因此,为了保证安全,一个进程不能直接访问另外一个进程的内存地址。
进程间的通信可以分为三类:共享存储、消息传递以及管道通信。
共享存储是指需要进行通讯的若干进程向操作系统申请一块共享空间,该共享空间可以由这些进程直接访问,通过对这片共享空间进行读/写操作实现进程之间的信息交换。操作系统会将这块共享空间映射到对应的进程的虚拟地址空间。
共享存储可以进一步的分为基于数据结构的共享和基于存储区的共享。
消息传递指进程间以格式化的信息为单位进行数据交换,如果通信的进程之间不存在共享空间,则可以利用操作系统提供的消息传递方法实现进程通信。
格式化的信息包括信息头和信息体两部分
消息传递依赖与操作系统提供的发送消息和接收消息的两个原语进行的数据交换。
消息传递可以进一步分为两种:直接通信方式和间接通信方式
直接通信和间接通信的区别相当于:点外卖直接送到门口与放到外卖柜里
管道通信是指通过管道这种特殊的共享文件(pipe文件)进行进程间的信息交换。
管道是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件(pipe文件)。向该文件中写入数据的进程被称为写进程,从该文件中读取数据的进程被称为读进程。
特点:
管道中的数据一旦被读取,就会彻底的消失,因此,当多个进程读取同一个管道的数据时,可能会发生错乱,此时通常有两种解决方案:
管道通信与共享存储的区别
为什么要引入线程
引入线程后的变化
线程的主要属性
线程的实现可以分为两类:用户级线程(ULT)和内核级线程(KLT)。
使用用户级线程时,有关线程管理的所有工作由应用程序通过线程库实现。线程库是指由编程语言为程序员提供的创建和管理线程的API库。
下图是自己编程模拟了一个非常弱智的“线程库”:
此时,操作系统内核无法感应到线程的存在,只能感应到进程的存在。即,在用户看来是多个线程在并发执行,但是对于操作系统内核而言,只有一个进程在运行。(只有用户能体会)
使用内核级线程时,有关线程管理的所有工作由操作系统内核程序实现,操作系统会为每个内核级线程设置一个TCB,以此来对内核级线程进行管理。
混合形式是指,将用户级线程和内核级线程组合起来使用。在该实现方式中,内核支持多个内核级线程的建立、调度和管理,也允许用户程序建立、调度和管理用户级线程。一个内核级线程可以对应多个用户级线程(这些用户级线程时分多路复用内核级线程)。使用该实现方式的系统的结构图如下图所示。
使用上述结构的操作系统,CPU的调度单位是内核级线程,但是允许应用程序通过线程库管理用户级线程
这种方式结合了用户级线程和内核级线程的优点。
当操作系统同时支持用户级线程和内核级线程,即使用的是上文介绍的混合型方式时,由于用户级线程和内核级线程的连接方式的不同,可以分为三种不同的多线程模型:一对一模型、多对一模型以及多对多模型。
这就是纯粹地使用用户线程的方式
与进程十分类似,线程也具有五种状态:新建状态、就绪状态、运行状态、阻塞状态、终止状态。由于线程可以理解为对进程进行了进一步的划分,因此这五种状态以及五种状态之间的转换,与进程基本相同。
与进程类似,操作系统为每个线程也配备了一个线程控制块TCB,用于记录控制和管理线程的信息。
当同时有多个事情要处理时,由于资源有限,无法同时处理这些事情,此时就需要通过按照某种规则来决定处理这些事情的先后顺序,这就是“调度”。
之前我们学习了进程的五种状态,此时为了学习CPU调度,我们需要额外的学习进程的一种特殊的状态挂起状态(挂起态),挂起态可以进一步的被细分为就绪挂起和阻塞挂起两种状态,因此进程的五状态模型就可以进一步延伸为七状态模型。
什么时候会出现挂起状态的进程
由于内存有限,需要将某些进程的数据调出暂存至外存,待这些进程需要运行且内存空闲时再重新调入内存,这些被暂存至外存等待的进程的状态就是挂起状态。处于挂起状态的进程的PCB会被组织成挂起队列
七状态模型的转换图如下图所示
阻塞与挂起的比较
在操作系统中将调度分为了三级,分别是高级调度,中级调度,低级调度。
高级调度也被称为作业调度,是指按照一定的原则从外存的作业后备队列中挑选一个作业调入内存,并且创建相应的进程,以便于其可以竞争CPU。每一个作业只调入一次,调出一次。当作业被调入时就会为其建立PCB,调出时就会撤销其PCB。
作业:一个具体的任务
用户向系统提交一个作业= 用户让操作系统启动一个程序(来处理一个具体的任务)
内存空间是有限的,因此当内存空间已满时,又有用户向操作系统提交了作业,此时这些作业就会被放入内存之外的作业后备队列中。
中级调度也被称为内存调度,是指按照某种规则决定将哪个处于挂起状态的进程重新调入内存,一个进程可能被多次调出和调入内存。内存调度的频次比作业调度要高。
低级调度也被称为进程调度,是指按照某种规则从就绪队列中选取一个进程,将CPU分配给该进程。
进程调度是操作系统中最基本的一种调度,频率最高,一般几十毫秒一次。
进程调度(低级调度)是通过调度程序完成的,调度程序是操作系统内核程序。当请求调度的事件发生后,才可能运行调度程序,调度了新的就绪进程后,才会发生进程的切换。然而并非所有情况下,请求调度后就一定可以发生进程的调度和切换。
可以进行进程调度与切换的情况
不可以进行进程调度与切换的情况
在进行原子性(原语)操作时,不能进行进程调度与切换。在进行原子性操作时,连中断都要屏蔽,就更不应该进行进程调度与切换了。
- 临界资源:一个时间段内只允许一个进程使用的资源,各个进程要互斥地访问临界资源。
- 临界区:访问临界资源的代码片段。
- 内核临界区:一般用于访问某种操作系统内核数据结构,例如进程的就绪队列等,因此内核临界区必须要尽快的释放锁,否则可能会影响操作系统内核的其他管理工作。
基于上述介绍,进程在操作系统内核临界区中不能进行进程调度与切换,但是进程在普通临界区(如一些IO设备等)中时,可以进行进程调度与切换。
进程调度的方式
进程调度方式是指根据既定规则,选择一个处于就绪状态的进程并将CPU资源分配给它的过程。通常情况下有两种进程调度方式:非抢占式调度和抢占式调度。
"进程调度"与"进程切换"的区别
就像每个人有每个人不同的优点一般,不同的调度算法也有着不同的特性,为了对调度算法的性能进行评价,人们提出了很多评价标准。主要的评价标准有:CPU利用率、系统吞吐量、周转时间、等待时间以及响应时间。
CPU处于忙碌状态的时间占比。
CPU利用率=CPU有效工作时间/(CPU有效工作时间+CPU空闲等待时间)
系统吞吐量指单位时间内CPU完成作业的数量。
周转时间是从提交一个进程或作业到完成其执行的总时间。它包括等待时间和执行时间。即:
后三项在一个作业的整个处理过程中,可能发生多次。
周转时间=作业完成时间-作业提交时间
对于操作系统而言,更加关心整体作业的周转表现,因此延伸出了另外一个评价标准——平均周转时间,平均周转时间是指多个作业周转时间的平均值。
均周转时间=各个作业周转时间总和/作业数
对于不同的作业,较大的作业周转时间一般较长,较小的作业周转时间一般较短,因此延伸出了一个评价指标——带权周转时间,带权周转时间是指作业周转时间与作业实际运行时间的比值。
带权周转时间=作业周转时间/作业实际运行时间
基于带权周转时间自然而然的就延伸出了另外一个评价标准——平均带权周转时间。
平均带权周转时间=各作业带权周转时间/作业数
注意:等待时间不包含等待I/O设备操作完成的时间,因为在这个时间内,进程正在被该I/O设备服务。
等待时间=周转时间-运行时间-IO操作的时间;若为纯计算型则无IO操作
响应时间是指从用户提交请求到系统至首次产生响应所用的时间,响应时间越短,用户体验越好。响应时间不考虑任务的完成,只关注系统开始处理请求的速度。
First Come First Severd, FCFS
Shortest Job First, SJF
三种算法的对比
时间片轮转(Round Robin, RR)
时间片为2举例:
以时间片为5举例:
与FCFS对比
非抢占式例子
抢占式例子
三种算法的对比总结