1 进程描述与控制
1.1 进程的基本概念
现代操作系统必须满足的大多数需求都建立在进程这一概念的基础上:
- 操作系统必须交替执行多个进程,在合理的响应时间内使得CPU的利用率最大;
- 操作系统必须按照特定的策略(比如给进程分配不同的优先级并优先调度高优先级的进程)给进程分配资源同时避免死锁;
- 操作系统可以支持进程间的通信和用户创建进程,从而允许用户构造功能丰富的模块化程序。
通常认为进程是:
- 一个正在执行中的程序;
- 一个正在计算机上执行的程序实例;
- 能分配给处理器并由处理器执行的实体;
- 一个具有如下特征的活动单元:一组指令序列的执行、一个当前状态和相关的系统资源集
1.2 进程控制块
进程的两个基本元素是程序代码(可以被执行相同程序的其它进程共享)和代码相关联的数据集。正在执行中的进程可以由如下元素来唯一地表征:
- 标识符:进程的唯一识别码,用来区分其它进程;
- 状态: 进程有执行态、就绪态、阻塞态、挂起态、停止态等几种状态;
- 优先级
- 程序计数器
- 内存指针:包括程序代码和进程相关数据的指针,以及和其它进程共享内存块的指针
- 上下文数据:进程执行时CPU寄存器中的数据
- I/O状态信息:包括显示的I/O请求、分配给进程的I/O设备和被进程使用的文件列表等
- 记账信息:进程执行过程中收集的一些统计信息,如处理器时间总和、使用的时钟总和、时间限制、记账号等
上述信息被存放在一个称为进程控制块的数据结构中。进程控制块由操作系统创建和管理,包含了使得进程被中断后恢复执行所需要的充分多的信息(包括但不限于进程被中断时的程序计数器、程序状态控制字和处理器寄存器里的内容)。
1.3 进程状态
1.3.1 五状态模型
最常见的五状态模型如下图所示,其中值得注意的是操作系统通常会为“就绪”和“阻塞”两种状态分别维护一个进程队列,队列可以是FIFO或是基于优先级的。大型系统中“阻塞”队列通常很长,这时一个常见的改进是为每一个事件维护一个单独的队列。
1.3.2 “挂起”状态的引入
上面的五状态模型暗含了一个进程自从被创建并加装到内存中后,自始至终完全地驻留于内存中,直至它最终退出执行。由于内存资源是相对紧缺的,试想如果有多个处于阻塞态的进程在等待I/O事件,它们短期内无法被调度运行但是却一直占用着紧俏的内存资源,这样不仅会降低内存资源的利用率,而且有可能使得用户创建新的进程由于内存不足而失败。为了解决这一问题,现代操作系统大都采取了“交换”机制:把内存中某个进程的一部分或全部移到磁盘中。当内存中没有处于就绪状态的进程时,操作系统就把被阻塞的进程换出到磁盘中的“挂起队列”(suspend queue),即暂时保存从内存中“驱逐” 出来的被挂起的进程队列。操作系统在此之后取出挂起队列中的另一个进程,或者接受一个新进程的请求,将其纳入内存运行。
引入“挂起”态后的进程状态转换图如下:
1.4 进程控制
1.4.1 执行模式
处理器大多支持至少两种执行模式:特权态(又称内核态、系统态、控制态)和用户态。某些指令只能在特权态下执行,包括存取诸如程序控制字之类控制寄存器的指令、原始I/O指令和与内存管理相关的指令。部分内存区域仅在特权态下可以被访问。区分特权态和用户态可以保护操作系统和重要的操作系统表(如进程控制块)不受用户程序的干涉。程序状态字(PSW)中有某一特定位用来表示执行模式,这一位会应某些事件的要求而改变。典型情况下,当用户调用一个操作系统服务或中断触发系统例程的执行时,执行模式被设置为内核态;当从系统服务返回到用户进程时,执行模式被设置为用户态。
1.4.2 进程创建
操作系统按照以下步骤创建一个新的进程:
1) 给进程分配一个唯一的进程标识符。
2) 给进程分配空间。这包括进程映像中的所有元素,包括私有用户地址空间(程序和数据)、用户栈、指向共享地址空间的链接等
3) 初始化进程控制块。
4) 设置正确的连接。例如把新进程放置在就绪或就绪/挂起链表中
5) 创建或扩充其他数据结构。例如记账文件等
1.4.3 进程切换
进程切换可以在操作系统从当前正在运行的进程中获得控制权的任何时刻发生,中断、陷阱和系统调用是常见的可能引发进程切换的原因。完整的进程切换步骤如下:
1) 保存处理器上下文环境,包括PC和其它寄存器;
2) 更新当前进程P1的PCB,包括更新进程状态、离开运行态的原因及记账信息等;
3) 将P1的PCB移动到相应的队列(就绪、事件i的等待队列、就绪/挂起队列)
4) 操作系统选择另一个进程P2执行;
5) 更新P2的PCB;
6) 更新内存管理相关的数据结构;
7) 恢复处理器在P2最近一次切换出运行态时的上下文环境,通常是通过载入保存在P2的PCB中PC和其它寄存器的值来实现
当中断或系统调用引起进程切换时,在执行上述的进程切换步骤前需要先执行一次模式切换 -- 将CPU的执行模式由用户态切换为内核态,从而使得中断处理代码可以包含特权指令。需要注意的是,发生模式切换可以不改变正处于运行状态的进程状态,在这种情况下,保存和恢复上下文环境只需要很小的开销。进程切换由于涉及状态变化,因此需要做更多的工作,代价比较昂贵。
2 线程
2.1 线程的概念
前文所述的进程具有两个基本属性:
资源所有权:一个进程包括一个存放进程映像(程序、数据、栈和PCB中属性的集合)的虚拟地址空间。进程拥有对资源的控制或所有权,包括内存、I/O通道、I/O设备、文件等。操作系统提供了保护机制以阻止进程之间发生资源冲突。
调度/执行:一个进程具有一个执行状态和一个被分配的优先级,它是一个可被操作系统调度和分派的实体。一个进程的执行过程可能与其它进程的执行过程交替进行。
现代操作系统为了能够独立地处理这两个基本属性,将可分派的单位称为线程或轻量级进程,将拥有资源所有权的单位称为进程或任务。
2.2 多线程
多线程是指操作系统在单个进程内支持多个并发执行路径的能力。在多线程环境中,进程被定义为资源分配的单位和一个受保护的单位。与进程相关联的有:
- 存放进程映像的虚拟地址空间
- 受保护地对处理器、其它进程、文件和I/O资源的访问
一个进程中存在一个或多个线程,每个线程拥有:
- 线程执行状态(运行、就绪等);
- 线程未运行时保存的线程上下文;从某种意义上看,线程可被视为进程内的一个独立操作的程序计数器
- 一个执行栈;
- 存放线程局部变量的静态存储空间;
- 与同一进程内的其它线程共享的对进程的资源的访问
下图从进程管理的角度说明了线程和进程的区别。
线程与进程相比有如下优点:
- 创建一个新线程要比创建一个新进程快得多;
- 终止一个线程也比进程容易;
- 同一进程内的线程之间的切换比进程间切换花费的时间少;
- 线程提高了不同的执行程序间通信的效率:进程间的通信需要内核的介入,以提供保护和通信所需要的机制;同一进程中的线程无须调用内核就可以互相通信,因为它们共享内存和文件资源。
2.3 线程的分类
2.3.1 用户级线程
在一个纯粹的用户级线程软件中,线程管理的所有工作由应用程序完成,内核不知道线程的存在。通常使用线程库来设计多线程应用程序。线程库提供的功能包括创建和销毁线程、在线程间传递消息和数据、调度线程以及保存和恢复线程上下文等。
2.3.2 内核级线程
在一个纯粹的内核级线程软件中,线程管理的所有工作都是由内核完成的,应用程序本身没有进行线程管理的代码,只有一个到内核线程设施的API。Windows就是该方法的一个例子。
下表总结了用户级和内核级线程的优缺点:
优点 | 缺点 | |
用户级线程 | 线程管理由应用程序负责,无须内核态的介入; 应用程序可以定制自己的线程调度算法而不扰乱底层的操作系统调度程序; 用户级线程程序可以在任何操作系统中运行 | 不能充分利用多核的优势; |
内核级线程 | 可充分利用多核; 单个线程的阻塞不会影响同一进程内的其它线程的调度执行 | 线程间的切换需要内核的介入; 纯内核级线程程序在不同操作系统上的可移植性需要充分考虑 |
2.3.3 混合方法
鉴于纯用户级/内核级线程程序各自都有一些优缺点,某些操作系统提供了一种混合的用户级/内核级线程方案。在混合方案中,线程创建完全在用户空间完成,线程的调度和同步也在应用程序中进行。多个用户级线程被映射到一些(小于或等于用户级线程的数量)内核级线程上。内核级线程的数目可以根据特定的应用程序和处理器来调节,以达到最佳的整体性能。Solaris是使用混合方法的一个例子。