什么是进程
进程是由一组元素组成的实体,基本元素包括程序代码和与代码相关的数据集(set of data),另外还包括
- 标识符:一个进程用于和其他进程区分的唯一标识。
- 状态:进程所处的状态。
- 优先级:进程需要被执行的紧急程度。
- 程序计数器
- 内存指针:指向内存中程序代码地址、与程序相关的数据的地址、与其他进程共享的内存地址的指针。
- 上下文数据:进程执行时寄存器中的数据。
- I/O 状态信息
- accounting information:包括处理器时间总和、使用的时钟数总和、account numbers 等。
以上列出的各种类型的数据存放在由 OS 创建和管理的一个数据结构中,这个数据结构称为进程控制块(process control block, PCB)。
进程中断时,OS 会将程序计数器和寄存器中的值存放到 PCB 中,改变进程状态的值。当 OS 切换到另一进程时,将该进程的状态改为运行态、进程的 PCB 中的程序计数器和寄存器中的值加载到处理器对应的寄存器中。
进程由程序代码、相关数据和 PCB 组成。
进程状态
分派器(dispatcher):使处理器切换进程的程序。
假设有 A、B、C 三个进程在内存中执行,时间片可用于执行 s 条指令。处理器的某种可能的运行情况为:A 进程运行了 s 条指令,时间片用完,发生时钟中断;此时,分派器执行切换进程的 s 条指令,执行完成后,处理器切换到 B 进程;B 进程执行的指令数量不到 s 时,需要请求 I/O 设备,停止执行(简单起见忽略中断处理程序的执行);此时分派器再次运行 s 条指令,执行完成后切换到 C 进程;C 进程执行 s 条指令后再次执行分派器...
总之,上述执行过程为:A \(\Rightarrow\) dispatcher \(\Rightarrow\) B \(\Rightarrow\) dispatcher \(\Rightarrow\) C \(\Rightarrow\) dispatcher \(\Rightarrow\) ...
根据上述处理器执行过程可知,所有进程处于两种状态:运行态和非运行态。运行态表示进程正在被处理器执行,非运行态表示进程目前没有被处理器执行。处于非运行态的进程位于队列中,等待着被处理器选择执行,处于运行态的进程因某种原因改变状态时,将其调入非运行态的队列中等待。具有单个处理器的计算机执行时,最多仅有一个进程处于运行态。
进程创建:一个新进程被添加到正在管理的进程集时,需要建立管理该进程的数据结构,并为其分配内存空间。
创建的原因
- 批处理环境响应作业提交
- 交互式系统用户登录
- 为应用程序提供的服务只能由 OS 创建
- 进程派生(process spawning)
进程终止:进程因某种异常原因或者执行完成,释放其所占用的资源的过程。
终止的原因
- 正常完成
- 超出时间限制
- 无可用内存
- 访问不允许的内存空间
- 使用不允许的资源
- 进行被禁止的计算
- I/O 失败
- 执行不存在的指令
- 执行未授权的指令
- 数据误用
- OS 干涉
- 父进程终止
- 父进程请求终止子进程
将进程状态分为非运行态和运行态的前提下,分派器需要选择一个非运行态的进程运行时,需要扫描非运行态进程组成的队列,因为这个队列中的每个进程可能可以被执行,也可能暂时不能被执行,如正在等待 I/O 操作的完成。为了避免这项工作,可以将非运行态划分为就绪态和阻塞态。加上前面讨论的新建态、终止态共构成了 5 种状态。
新建态:PCB 已经创建还未加载到内存中的进程。
就绪态:只要获得处理器控制权便可以执行的进程。
运行态:正被处理器执行的进程。
阻塞态(等待态):正在等待某一事件发生的进程,该事件发生进程才能被执行。
终止态:刚执行完成或者被取消的进程。
状态之间转换的可能原因
new \(\Rightarrow\) ready:OS 准备好再调入一个进程到内存中,将其状态从 new 转成 ready。
ready \(\Rightarrow\) running:需要选择一个新进程运行时,从所有就绪态进程中选择一个。
running \(\Rightarrow\) exit:运行中的进程运行完成或者被取消。
running \(\Rightarrow\) ready:分配的时间片用完、被优先级高的进程通过 OS 抢占。
running \(\Rightarrow\) blocked:进程请求某一需要等待的事件。
blocked \(\Rightarrow\) ready:进程等待的事件发生。
ready \(\Rightarrow\) exit 或者 blocked \(\Rightarrow\) exit:父进程终止子进程;父进程自身终止时,其所有子进程终止。
处理器对不同状态的进程进行切换有两种常见的方式。第一种是,将所有就绪态的进程放入一个队列,所有阻塞态的进程放入另一个队列,当处理器需要执行一个新进程时,从就绪态队列中选择一个执行;运行态的进程根据不同的中断原因,将其放入就绪态进程所在队列或者阻塞态进程所在队列,阻塞态进程等待的事件发生时将其调入就绪态进程队列。第二种方式是,就绪态进程放入一个队列,阻塞态进程不全部放入一个队列,根据等待的事件放入对应的队列,当等待的事件发生时,将等待这个事件发生的位于同一队列中的所有进程调入就绪态队列中;其他调入方式和第一种方式相同。
当系统 未使用虚存 时,所有的进程要么相关的全部数据及数据结构等位于内存中,要么全部不在内存中。由于处理器的速度远快于 I/O 的速度,这样可能造成内存中的进程都处于阻塞状态,处理器处于空闲状态,浪费了处理器资源。为了解决这个问题,有两种方法。其一,增大内存的容量,以便能够容纳更多的进程,内存的价格随着容量的增大而提高,代价比较昂贵。其二,当内存中所有进程处于阻塞态,剩余内存不足以调入新进程时,可以将阻塞态的进程换出到外存(如磁盘)中,使得有足够的空余内存让处理器调入其他进程。
当使用第二种方法时,处理器选择某个阻塞态的进程换出到外存中,放入外存中的队列中,该队列中的进程称为挂起态。处理器然后调入一个新进程或者从外存队列中调入另外一个进程到内存中,一般情况下选择后者。当调入另一个进程到内存中时,这个被调入的进程可能仍然处于阻塞态,无法被处理器执行,这种情况发生时则做了无用之功。为了解决这个问题,将放入外存中的进程划分为两种状态:阻塞态和就绪态。分别使用不同的队列保存。此时,外存中的进程有两种状态:阻塞/挂起态和就绪/挂起态。
阻塞/挂起态:处于外存中等待某个事件发生的进程状态。
就绪/挂起态:处于外存中调入内存可以直接被处理器执行的进程状态。
状态转换的可能原因:
blocked \(\Rightarrow\) blocked/suspend:内存中没有就绪态的进程;为了更多的内存空间满足性能需求。
blocked/suspend \(\Rightarrow\) ready/suspend:等待的事件发生了。
ready/suspend \(\Rightarrow\) ready:内存中没有就绪态进程;就绪/挂起态进程的优先级高于所有就绪态进程。
ready \(\Rightarrow\) ready/suspend:获得内存的唯一方法是挂起就绪态进程;OS 预判高优先级阻塞态很快转成就绪态。
new \(\Rightarrow\) ready/suspend || new \(\Rightarrow\) ready:一般情况下创建新进程后会调入内存,当内存空间不足时,状态转换成就绪/挂起态。尽可能推迟进程的创建,节省资源。
blocked/suspend \(\Rightarrow\) blocked:阻塞/挂起态的某进程比就绪/挂起态的所有进程优先级高,同时 OS 预判该进程等待的事件很快将会发生。
running \(\Rightarrow\) ready/suspend:阻塞/挂起态高优先级进程等待的事件发生时,会抢占运行态的进程,此时状态转换到就绪/挂起态。
进程挂起的原因:需要足够的内存空间;用户请求;定时;父进程请求;其他。
进程描述
OS 为了管理计算机资源,必须为每个实体构造和维护对应的信息表。信息表分为 4 类:内存表、I/O 表、文件表和进程表。
内存表用于跟踪内存和外存。必须具有的信息:
- 分配给进程的内存
- 分配给进程的外存
- 内存块或者虚存块的保护属性,即访问权限控制
- 管理虚存需要的任何信息
对于进程管理,有两点值得注意:上述 4 种类型的信息表以某种方式相互链接或者交叉引用,因为互相之间需要用到其他表的信息;OS 必须知道计算机资源的所有信息。
为了管理进程,OS 必须知道进程的位置和属性。
进程的物理表示称为进程映像(process image),由程序、数据、PCB 和栈组成,表示一个完整的进程。OS 维护一个主进程表,每个表项有一个包含指向进程映像所在位置的指针。
进程标识信息:进程标识符(PID)、父进程标识符、用户标识符。
每个进程都分配有唯一的一个进程标识符,可简单使用主进程表中的索引表示。可用于进程间通信、指明进程间的关系,如父进程和子进程等。用户标识符指明拥有该进程的用户。
进程状态信息由处理器状态信息指明,主要是寄存器中的内容。名为程序状态字(program status words,PSW)的寄存器(一个或者一组)包含状态信息。具体分为:用户可见寄存器,也就是在用户模式下可用的寄存器;控制和状态寄存器;栈指针。
进程控制信息有:调度和状态信息、数据结构、进程间通信、进程特权、存储管理、资源所有权和使用情况。用于管理进程的额外信息。
进程映像在虚存中的结构为
PCB 包含 OS 用到的管理进程的所有信息,且 OS 每个模块都能访问 PCB。所有 PCB 构成的集合定义了 OS 的状态。不同模块在访问 PCB 时可能造成 PCB 中的数据被破坏,为了防止这种情况的发生,需要设置一个专门访问 PCB 的进程,其他所有模块访问 PCB 必须通过该进程访问,确保 PCB 中数据的安全。
进程控制
处理器执行模式划分为用户模式(user mode)和系统模式(system mode),系统模式即特权模式。特权模式可以执行用户模式下无法执行的指令,访问用户模式下无法访问的内存空间。使用两种模式的原因是保护 OS 和 OS 管理的信息表不会受到用户程序的干扰。
处理器中的程序状态字(PSW)中有特定的位指示当前处理器处于什么模式,这个位随着事件的改变而改变。发生中断时,将该位置为 0(0表示最高优先级),中断执行完毕后,将该位恢复到发生中断时的状态。
进程的创建过程
- 为新进程分配一个进程标识符(PID)。
- 分配内存空间。
- 初始化 PCB。
- 设置正确的链接。即将 PCB 放入到正确的队列中。
- 创建或者扩充其他的数据结构。
切换进程的时机:当 OS 取得对处理器的控制权的时候都有发生切换进程的可能。
影响进程切换的事件分为 3 种类别。其中,
- 中断(interrupt),发生的事件与运行态进程执行的代码没有关联。
- 陷入(trap),运行态进程执行时发生异常,此时 OS 根据异常情况终止进程、继续执行当前进程或者切换进程。
- 系统调用(system call),运行态进程请求系统调用,此进程转换为阻塞态,处理器控制权交由 OS 执行系统调用。
任何中断发生后,会先执行中断处理器程序,做一些准备工作,再执行中断对应的中断处理程序。
指令周期分为取指令阶段、执行指令阶段和检查中断阶段。在检查中断阶段,检测到一个中断时,将程序计数器(PC)中的值改为中断处理程序的第一条指令所在的地址,执行模式从用户模式转换到内核模式,因为中断处理程序可能包含特权指令。将当前运行的程序的上下文保存到它的 PCB 中,上下文包括可能被中断处理程序改变的信息和恢复到被中断前状态需要的所有信息。
模式切换可在不改变运行态进程状态的情况下出现。
进程切换步骤
- 保存处理器上下文,包括 PC 和所有其他寄存器。
- 更新当前运行态进程的 PCB。
- 把该进程的 PCB 移到相应的队列中。
- 选择另一个进程执行。
- 更新所选进程的 PCB。
- 更新内存管理数据结构。
- 加载选择的进程之前保存的值到 PC 和其他寄存器中,使处理器上下文恢复为所选进程上次退出运行态时的上下文。
OS 的执行
在早期的 OS 设计中,OS 作为一个独立的实体在所有进程之外以特权模式运行。当运行态进程中断或者发出系统调用时,处理器使用 OS 的内核功能进行响应,然后将控制权根据中断的情况返还给中断前的运行态进程或者运行另外一个进程。
OS 作为一组例程在用户进程的环境中执行完成对应的功能。OS 的程序和数据位于共享内存空间,可以被所有用户进程使用。当用户进程因中断、陷入或者系统调用执行 OS 对应的程序时,该程序在用户进程的环境中执行,进程状态并没有改变,执行完毕后返回到用户进程暂停处的指令地址继续执行用户进程。优点是使用 OS 服务没有进程切换的开销。这种切换只是执行模式的切换,从用户模式转换到了内核模式。在一个进程中,用户程序和 OS 程序都可以执行,在不同进程中同一 OS 程序可以执行。
参考
[1] William Stallings, 操作系统——精髓与设计原理(8th), 2017.