【操作系统原理】——进程

进程

什么是进程
  在操作系统中,操作系统需要对各种资源进行管理,大概可以分为以下几类:内存,文件,磁盘,进程。所谓进程就是操作系统有序管理应用程序的执行的方式,来保证以下几点:
  1、所有资源对多个应用程序是可用的。
  2、物理处理器在多个应用程序中切换,保证所有程序都在执行中。
  3、处理器和I/O设备都能得到充分的利用。
  因此所有现代操作系统都依赖于一个模型,在该模型中,一个应用程序对应一个或多个进程。进程的定义有以下几条:
  1、一个正在执行的程序。
  2、一个正在计算机上执行的程序实例。
  3、能分配给处理器并由处理器执行的实体。
  4、由一组执行的指令、一个当前状态和一组相关的系统资源表征的活动单元。
  简单来说什么是进程,进程就是正在执行中的程序。而在操作系统中,操作系统为了更好的描述一个进程,于是将进程视为一些元素组成的实体,而其中最重要的两个元素是程序代码和数据集。一般来说一个程序有了程序代码和数据集就可以顺利执行了,但是操作系统说还不够,为了满足操作系统对进程的控制,例如调度,中断,执行等操作,操作系统将每个进程描述为一个叫做进程控制块(PCB) 的数据结构,在PCB中存储着操作系统对控制一个进程所需要的全部信息,可以根据PCB找到程序代码,找到程序的数据,程序获得的资源等等。所以一个进程对于操作系统来说就是一个PCB。PCB中所存储的信息我们在下文中有详细介绍。
  知道了操作系统是通过PCB管理进程的后接下来讨论进程的状态。

进程状态

两状态模型
  在多道操作系统中,我们假设现在的处理器都是单核的即同时只能有一个进程正在处理器中执行,但是操作系统为了让用户看上去所有进程都在“同时”运行于是他在操作系统中设置了时间片,即一个进程可以连续执行的最大时间,并且按照调度算法快速在不同进程间进行切换执行,执行中的进程状态为运行态,而未执行的则成为非运行态,其中关系如下图。
  【操作系统原理】——进程_第1张图片
同时我们可以把非运行态的进程组织到队列中,每次切换进程从队列中调出一个进程开始运行,而切换下来的进程要么重新加入队列要么执行完毕退出,如下图。
【操作系统原理】——进程_第2张图片
 这里提一下可能导致创建新进程的事件和可能导致进程退出的事件。
  进程创建由以下4种事件触发:
  1、新的批处理作业。新的批处理作业进入操作系统肯定会创建新的进程来执行批处理作业。
  2、用户登录。用户登录往往也会创建新进程来执行用户指令,之所以使用进程是为了将用户与操作系统隔离,一个用户指令的崩溃不会影响到其他用户乃至操作系统。
  3、为提供服务由操作系统创建。有时操作系统为了提供一个服务也会创建新的进程,例如用户进程请求打印一个文件,系统可以创建一个管理打印的进程,进而使请求进程可以继续执行。
  4、由现有进程派生。当现有进程引发另一个进程的创建时,操作系统也会创建新的进程,这就是进程派生,这往往很有用,派生出的进程可以帮助主进程处理数据,组织数据等等。
  进程的终止由以下14种事件触发:
  1、正常完成。正常结束运行。
  2、超过时限。进程运行超过规定的时限。
  3、无内存可用。系统无法满足进程需要的内存。
  4、超出范围。进程试图访问非法的内存单元。
  5、保护错误。进程试图使用不允许使用的资源或文件。
  6、算术错误。进程试图进行被禁止的运算。
  7、时间超出。进程等待某一事件发生的时间超过了规定的时间。
  8、I/O失败。在输入输出期间发生错误。
  9、无效指令。进程试图执行一个不存在的指令。
  10、特权指令。进程试图使用为操作系统保留的指令。
  11、数据误用。错误类型或未初始化的一块数据。
  12、操作员或操作系统干涉。操作员或操作系统终止进程。
  13、父进程终止。在某些操作系统中,父进程终止时操作系统会自动终止该进程的所有子进程。
  14、父进程请求。父进程要求终止其子进程。

五状态模型
  如果所有进程都做好了准备,操作系统会从未运行队列中以轮转的方式调度每个进程。但是这里有个问题,如果并非所有进程都做好了准备呢?也许未运行的进程中有些进程正在等待某一事件的发生,也就是处于阻塞,因此单纯的对所有未运行的进程进行轮转是不科学的,应该对所有已经就绪的进程进行调度。解决这种问题的最好方法就是将未执行进程队列拆分为两个队列分别是就绪队列和阻塞队列,由此进程的状态由2状态变为了3状态,此外还要增加新建和退出态,这十分有必要。改进后的状态模型如下图所示。
  【操作系统原理】——进程_第3张图片   运行态:进程正在执行。
  就绪态:进程做好了准备,随时接收调度。
  阻塞态:进程在等待某些事件的发生,在事件发生前不能执行,如I/O操作。
  新建态:刚刚新建的进程,操作系统还未将其加载至内存,通常是PCB已经创建但是还并未加载到内存中的新程序。
  退出态:操作系统从可执行进程组中释放的进程。
  新建态与退出态十分有必要。在一个进程被新建时它并非绝对会被调入内存,通常是分两步,首先创建该进程的PCB,并与之关联,但是此时可能面临内存不足或者操作系统限制了最大进程数导致这个进程还无法被调入进程,因此该进程被暂时留在新建态,在这个状态的进程PCB已经创建并且加载进内存,但是进程的代码和数据往往还留在外存中等待加载。
  退出态也和新建态同理。当进程因为某些原因要被终止时,此时并不直接将其调出内存,首先操作系统会停止执行该进程的代码,但是暂时让其留在内存中,因为某些辅助程序或是支持程序会来记录该进程相关数据和信息,此时进程停留在退出态。等相关程序收集完所需信息后,再将其所有数据从内存中移除。
  关于阻塞,就绪和运行三种状态的转换更为普遍和便于理解。操作系统从就绪队列中调度某个进程进入运行态运行,当时间片结束后操作系统将其放回就绪态执行其他进程,如果在执行期间进程必须等待某些事件,便将其放入阻塞态,然后调度其他进程执行。当该进程等待的事件完成后操作系统则将其放回就绪态等待调度。
  但是此时又有一个问题,如果所有阻塞进程放在同一个阻塞队列中,当一个事件完成后操作系统不得不扫描整个队列找到那些等待该事件的进程然后将其放进就绪队列中,这样的效率十分低下,因此通常是为每一个事件创建一个阻塞队列。同理当按照优先级进行调度时,也会将优先级相同的进程放进一个就绪队列,避免扫描等低效的做法,这是典型的用空间换时间的做法。
  【操作系统原理】——进程_第4张图片
七状态模型
  在介绍七状态模型前,我们思考一个问题,三个基本状态(就绪,运行和阻塞)的所有进程都必须存储在内存中,此时就可能出现一种情况,即所有进程都处于阻塞态,没有就绪状态的进程,此时又开始了处理器的空转,处理器没办法执行进程只能开始等待进程从阻塞态恢复就绪态,并且加入此时又有新的进程处于新建态,由于内存不足,处于新建态的进程也没办法进入内存无法执行,这是一个十分致命的处理器空转问题,解决这个问题有两个方法:扩大内存,很显然成本太高了;将阻塞态的进程暂时调出内存放回磁盘,来让新建态的进程有足够内存进入就绪态开始处理器的调度和运行。
  但是在将一个阻塞态进程挂起后,操作系统可以选择接纳一个新建态进程进入就绪队列,也可以选择将一个之前挂起的进程恢复就绪态,并且为了减少操作系统的负载操作系统更倾向于后者。但是处于挂起的进程也可能还并未接触阻塞,将一个阻塞进程放回内存没有任何意义,于是更好的方法是将挂起区分为两个状态即就绪/挂起态和阻塞/挂起态,这样每次操作系统就只需要考虑是否应该把进程从就绪/挂起态换回就绪态即可。完整的七状态模型如下:
  【操作系统原理】——进程_第5张图片
阻塞/挂起态:进程在外存中并等待一个事件。
  就绪/挂起态:进程在外存中,但只要载入内存即可开始运行。
  并且操作系统允许进程从就绪变为就绪/挂起态,或从阻塞/挂起态变更为阻塞态,只是这样做的意义不大,因此并不会这样做。
  导致进程被挂起的事件有以下几种:
  1、交换。为了释放内存空间。
  2、其他OS原因。操作系统可能会挂起后台进程或者工具进程,或挂起可能会导致问题的进程。
  3、交互式用户请求。用户希望挂起一个进程来进行调试。
  4、定时。进程可被周期性的执行,并在等待下一个时间间隔时挂起。
  5、父进程请求。父进程可能希望挂起后代进程的执行,以检查或修改挂起的进程。

进程描述

进程在操作系统中的描述方式
  操作系统可以管理计算机内的任何资源,包括内存、设备、文件和进程。 但是操作系统是如何管理的呢?对于操作系统来说,所有的资源都被组织成对应的数据结构,内存对应内存表,设备对应设备表,文件对应文件表,进程自然也有进程表,如下图。接下来我们将详细介绍操作系统如何描述操作系统中的所有进程,也就是进程表的结构。
  【操作系统原理】——进程_第6张图片
如上图所示,进程表中存放着一个一个进程,而每个进程项都指向一个进程映像,什么是进程映像呢?我们说一个进程最基本的元素是用户代码以及元素集,初次之外还有若干操作系统控制进程所需的信息,这些信息都存放在进程映像中,并且还有一个进程用于存储临时数据的栈,因此进程映像中的典型元素可以概括如下:
  1、用户数据。用户空间中的可修改部分,包括程序数据、用户栈区域和可修改的程序。
  2、用户程序。待执行的程序。
  3、栈。每个进程有一个或多个后进先出栈,栈用于保存参数、过程调用地址和系统调用地址。
  4、进程描述块。操作系统控制进程所需的数据。
  有了以上信息就有了一个进程调度,运行所需的全部数据,这些数据在内存中有可能是连续的也有可能是不连续的,这根据操作系统内存管理的方式来决定,但是但从操作系统描述管理进程方式来看,操作系统通过在内存中的主进程表,每一表项都至少包含一个指向进程映像的指针,通过进程表操作系统可以找到控制进程所需的全部数据。

进程属性

我们知道了操作系统通过进程表和进程映像描述进程,进程映像中的用户数据和用户程序都是根据用户所写的程序而定的,栈也只是用来保存参数调用地址所用的临时储存空间,但是其中我们要尤为重要介绍PCB(进程描述块)。正如之前所说进程描述块中储存了操作系统控制进程所需的一切信息,对于操作系统来说拿到进程控制块就可以控制进程进行调度等操作,那么进程控制块中到底存放了进程哪些信息呢?
  不同操作系统的PCB中组织的信息是不同的,但是PCB中所有操作系统都需要的共用基础信息一共8种:
  1、标识符:PID,与进程相关的唯一标识符。
  2、状态:进程状态,状态的划分是接下来介绍的重点。
  3、优先级:与进程调度有关的优先级。
  4、程序计数器:程序中即将执行的下一条指令的地址。
  5、上下文数据:进程执行时处理器的寄存器中的数据。
  6、内存指针:包括程序代码及相关数据的指针,以及与其他进程共享内存的指针。
  7、I/O状态信息:进程的I/O请求,分配给进程的I/O设备和进程使用文件
  8、记账信息:包括处理器时间综合、使用的时钟数综合、时间限制、记帐号等。
  这些信息一共可以分为三类进程标识信息、处理器状态信息、进程控制信息。进程标识信息典型的就是标识符,他是一个操作系统中唯一标识一个进程的基本索引。处理器状态信息由处理器寄存器的内容组成,中断进程时,必须保存寄存器中的所有信息,以便进程恢复时使用,这些信息就保存在PCB中,典型的有上下文数据。进程控制信息是操作系统控制和协调各种活动进程所需的额外信息,例如进程优先级。
  根据以上的介绍,进程映像在虚存中的结构基本如下图所示,但是具体情况还得视操作系统的具体管理方案而定。
  【操作系统原理】——进程_第7张图片

进程控制

执行模式
  操作系统必须保证自己的安全性,因此再让用户进程运行时并不能将所有的权限交给用户,这样操作系统很可能会被进程搞到崩溃,最好的方式是操作系统将一些特权指令不进行公开,用户进程不能直接执行这些指令,但是操作系统允许进程发起使用特权指令的请求,然后再有操作系统自己代替用户执行指令,这样可以大大增强操作系统的健壮性,同时内存也并不会让用户进程都可以访问到,如果修改了操作系统即内核可能会发生致命错误,于是这中间操作系统加入了种种限制,先从一个进程的执行上来说,操作系统将其分为了两种模式用户模式(用户态/目态)和内核模式(内核态/管态)。
  用户进程默认是在用户模式下运行,在用户模式下进程的权限受到控制,而如果发生了一些特殊事件,例如请求系统调用模式会从用户模式转换为内核模式。说白了用户模式即处理器在执行用户代码,内核模式即处理器目前在执行内核代码。那么这样有出现两个问题,处理器如何知道它正在什么模式下执行?一般情况下,程序状态中通常存在一个指示执行模式的位,该位会因模式的变化而变化,也就是说在处理器的一个寄存器中存储了当前处理器处于什么模式下的信息。例如Intel Itanium处理器中就有一个包含2位CPL(当前特权级别)字段的处理器状态寄存器用于存储模式信息。

进程创建

操作系统在创建一个进程的时候会进行哪些工作呢?当操作系统决定创建一个进程时会执行以下操作:
  1、为新进程分配一个唯一的进程描述符。
  2、为进程分配空间。
  3、初始化PCB。
  4、设置正确的链接。例如将进程放到就绪队列中,而就绪队列是一个链表,此时就需要在数据结构上进行连接。
  5、创建或扩充其他数据结构。例如创建账单和评估性能。

进程切换和模式切换

进程切换
  进程切换在什么时候发生呢?理论上在任何时刻只要操作系统拿到控制权就可以进行进程切换,那么什么时候操作系统会重新拿到控制权呢?
  这里首先考虑中断的情况,而中断又可分为两种:中断和陷阱。中断一般是与当前正运行进程无关的某种外部事件相关,例如完成了一次I/O操作,中断处理器完成一些基本的辅助操作后将控制权转给与已发生的终端相关的操作系统历程,简单来说中断的发生属于正常的事件,不过是操作系统暂时停止执行当前进程转为处理另外一件更加紧急的事情。例如以下三种中断:
  1、时钟中断。当前进程时间片到期,转为从就绪队列中调度新的进程开始运行。
  2、I/O中断。某一I/O完成,操作系统判断是否有正在等待该I/O的进程,如果有将其放回就绪态,随后操作系统根据调度算法调度合适的进程继续运行。
  3、缺页中断。处理器遇到一个引用不存在内存中的虚存地址时,此时会发生缺页中断,然后操作系统要根据算法将访问的页调入内存,这块的处理与操作系统对内存管理有很大关系。
  除了中断,陷阱也有可能会导致进程状态的切换。所谓陷阱就是异常或者错误。即发生在程序内部的不可预期的非法错误。如果错误致命则将当前进程改为退出态,不致命时操作系统的行为决定于操作系统的设计,有可能是简单的通知用户,也有可能是尝试恢复。
  还有一种可能会导致进程切换的事件,就是系统调用。当用户进程发起一个特权指令(系统调用)时,操作系统会将当前用户进程设置为阻塞态,然后会调用系统例程执行系统调用指令,当执行完毕会在此调度用户进程开始执行。
  综上所述,可能造成进程状态切换的事件有三种中断,陷阱(异常),系统调用。

模式切换
  操作系统为了安全设置了不同的执行模式,那么操作系统何时进行模式切换呢?我们知道内核模式就是处理器在执行内核中的系统代码,那么不难得出,只要发生状态转换的事件一定会造成模式转换。例如中断,不管时哪一种中断,都少不了操作系统要根据调度算法重新调度进程开始运行,更不用说缺页中断中操作系统还需要进入内核状态执行内存置换算法换页等等;异常也是需要操作系统判断如何进行下一步处理也需要进行模式切换;系统调用就是在执行系统历程,更需要模式的切换,因此我们可以得出进程模式切换的基本事件就是中断,陷阱(异常),系统调用。
  但是要注意的时,并非模式切换一定会导致运行态进程切换,例如在中断后操作系统根据调度算法决定继续执行当前用户进程,那么当前用户进程就完全不需要改变状态,相比切换运行态进程单单切换模式,操作系统所要做的操作可要少多了。所以进程切换一定会导致模式切换,但进程模式切换并不一定会发生进程状态切换。

操作系统的组织形式

我们之前的介绍都基于操作系统是在所有进程独立外的一个大型程序,是一组进程,那么操作系统到底是进程么?如果是进程的话又要怎么控制它?
  以下是几种操作系统内核的设计方式。
  
在用户进程内运行
  较小的计算机操作系统通常采用这种设计方式,这种方式是将系统内核代码放到每个进程虚存中的共享区,这样做的好处是如果要执行系统代码不需要像无进程内核那样切换代码及数据以切换系统历程,这种方式仍然是在每个用户及进程内部执行操作系统代码,不需要切换进程,只用在同一进程中切换模式即可,所带来的系统开销更小,更加快捷。并且在一个进程内用户程序和操作系统程序都可执行,而在不同用户进程中执行的操作系统程序是相同的,这也是为什么要将系统内核放到共享地址空间的原因,在这种方式下一个进程在虚存中的映像如下:
【操作系统原理】——进程_第8张图片

基于进程的操作系统
  这种设计方式是把操作系统作为一组系统进程来实现。和其他方法一样同样是在内核模式下运行系统代码,但是在这种情况下是吧内核功能都组织为独立的进程,但同时往往也将一些进行进程切换工作的代码独立出来。这种方式的好处是使用模块化系统设计的原理,可以将一些操作系统功能作为独立进程来实现,同时这种方式在多处理或多继环境中很有用。这种设计方式的示意图如下:
  【操作系统原理】——进程_第9张图片

Linux的进程管理

Linux属于类UNIX操作系统,实现的原理与UNIX进程的实现方法类似,其实大部分的操作系统都要遵循系统设计的基本原理,但是实现细节上会有所不同。在Linux上进程状态转换如下图:

【操作系统原理】——进程_第10张图片

在Linux系统实现中最大的变化就是将阻塞态变为了可中断和不可中断两个状态,并且加入了停止态。
  1、可中断:这是一个阻塞态,进程正在等待一个事件的结束。
  2、不可中断:这是一个阻塞态,与可中断的区别是,此时进程正在等待一个硬件条件,因此屏蔽任何信号。
  3、停止:进程收到信号要求被其他进程暂停执行,并且只能由另一个进程的主动动作恢复运行。

进程和线程之间的区别

这是十分常见的问题,在此做同一归纳和梳理:
  1、进程是资源分配的基本单位,线程是处理器调度的基本单位。
  2、同一进程内线程共享进程状态和资源,例如数据段,代码段,I/O信息等。但是每个线程内也有独立的数据,每个线程都拥有属于自己的栈,线程属性信息存在线程控制块中,例如上下文数据,线程状态,调度信息等。
  3、线程是轻量级进程,因此创建和销毁所消耗的系统资源更少,更快。
  4、同一进程内线程切换所消耗的资源相比进程切换更少且更快。
  5、同一进程内线程共享大部分数据因此通信起来更加方便,无需借助内核。

你可能感兴趣的