上一篇计算机系统008 - 操作系统概况中讲到,计算机操作系统发展的两个主要方向是提高CPU使用率,以及降低响应时长。这两者在微观调度方法一级来看是相互违背的,但从宏观调度策略来讲,还是可以做出折中的利弊权衡。当然,并非所有计算机系统都追求所谓的平衡,很多时候特定领域的系统更重视高实时响应,而有的场景只需要尽可能利用CPU计算能力即可。
方向决定方法,对于进程的理解,就从这两个目标开始。
1. 进程
进程这个概念忽然跳出来,略显突兀,可是很多资料上就这么自然地放在这里。为了更好地理解进程概念的由来,这里先对照计算机系统发展史来说明一下。
1.1 进程的由来
最早的时候,也就是单处理器时期,系统要执行一个计算任务的步骤是将整个程序加载到内存中,然后由CPU逐条读取并执行指令。程序包括代码指令和预定的数据,指令执行期间,肯定会产生一些计算结果,部分为临时使用。所以自然而言,寄存器中应当保存一部分临时值,举个例子,加法器执行一个加法运算,寄存器中就需要存储CF进位信息。那么总的来讲,一个程序运行后,至少会包含如下信息:
- 程序代码
- 数据集
- 运行时信息
- 栈
这个时期中计算机系统的计算时间主要通过预约或批处理系统监控获取,所存在的最大问题是CPU和I/O运行速率量级相差太大,CPU的速率远远高过I/O速率,导致一旦执行到I/O指令时,系统中CPU资源就处于闲置状态,而指令每次执行,又至少会涉及到一次内存地址的访问。也就是说,系统运行期间,CPU资源被大量浪费。
为了提升CPU资源使用率,降低计算成本,可行的主要方法是加载更多的程序到内存中,一旦某个程序要执行I/O操作,就切换到其他程序运行。当然这里能够切换的前提是,I/O具体操作由其他单元进行控制,只需少量CPU执行前后准备、收尾工作即可,典型的有DMA直接内存访问。既然涉及到程序切换,那就势必要对一个运行中程序的所有相关信息进行统一管理,这样才能以该单位进行切换避免出错,而这里,这个单位就叫做进程。
1.2 进程状态
前面讲了将内存中运行中的程序也就是进程进行切换,来达到提升CPU利用率的目的。既然有切换,也就意味着至少有运行、非运行两种状态。在该粒度控制下,CPU可以在多个程序间进行切换,避开I/O操作的闲置指令周期,提升整体使用率。
然而上面的调度只是粗粒度的,任何时候,要想做出更精确的决定,就必须基于更多的有效信息。对于操作系统来讲,要想更精确地在不同进程间切换CPU,就必须掌握更多进程的有效信息,而增加信息的第一步,就是降低粒度,细化进程状态的分类。例如对于非运行状态而言,它难以对新创建的进程和处于I/O等待过程中的进程做区分,这样一来,将处于I/O等待过程中的重新运行就纯属对CPU资源的浪费行为。
在上述两种状态的基础上,现代操作系统通常会区分为如下几种状态:
新建态
就绪态
-
挂起态(可选)
添加挂起态的根本原因是每多加载一个进程进入内存空间就多了一个选择,就可以更大程度地避开I/O等待,有效利用CPU资源。但由于物理内存有限,导致能加载入内存的进程数量也有限。为了拥有更多可选择进程,就通过将一部分处于I/O等待过程中的进程从内存空间写出到磁盘空间,当需要重新调度被写出进程时,再从磁盘空间重新读取。
当然也存在其他方法去降低一个进程所需的物理内存,如基于页交换硬件支持,来实现只加载进程的必要信息而非全部内容来实现降低内存占用。这一部分会再后面的内存管理进一步介绍。- 阻塞/挂起态
- 就绪/挂起态
阻塞/等待态
进程在某些事件发生前不能继续执行,如I/O操作完成事件。运行态
退出态
1.3 进程调度
如下图所示,内存(虚拟内存)中进程整体构成如下。进程状态属于进程运行时信息中的一部分,现代操作系统中比较通用的运行时状态信息是一个称为进程控制块PCB(Process Control Block)的结构体。
PCB中主要含有三类信息:
进程标识信息
表示信息主要指标识符,包括进程ID(PID)、父进程ID、用户ID(UID)-
进程状态信息
包括用户可见寄存器、控制和状态寄存器、栈指针三个子类型数据,通常CPU设计中存在一组称为程序状态字PSW(Program Status Word)的寄存器,它包含条件码和其他状态信息。以Intel X86为例:
进程控制信息
包括调度和状态信息(如优先级、事件等)、数据结构、进程间通信、进程特权、存储管理、资源的所有权和使用情况等。
而所谓的各种状态进程队列,实际上都是以链表形式管理各进程PCB数据结构。
至此我们知道,进程由代码、数据、栈、运行时信息组成,进程概念的提出主要是便于调度管理,其中所包含的运行时信息可以为调度方法提供有效信息。而调度本身又是通过在处于不同状态的进程间切换以避开I/O等待,从而达到提高CPU利用率的目的。
2. 线程
上一节中我们从整个操作系统的角度来讨论了进程概念的由来和必要性,同时也提到,在具备更多有效信息的基础上,更小粒度的控制可以达到更加精确的调度结果,从而充分利用CPU。那么对于每一个进程而言,是否有方法可以达成更精细的控制?
答案就是线程。
2.1 线程概念
忽然又跳出来一个名词,线程。从字面来看,也看不出什么,只好记住一个概念,线程是对进程进行更小粒度的细分。对于单处理器单核系统来讲,每次只能运行一个进程,即使进行了更小粒度的细分,也并不能在整体上加快进程运行速度,甚至有可能由于频繁切换而引入更多的消耗。 但考虑实际应用中,每个程序代码中可能存在对于多个硬件部件的操作,如一部分进行内部数据计算、一部分负责文件读写、一部分负责显示刷新,最后一部分负责与用户交互。
这里的用户交互使用了粗体进行强调,的确,单CPU单核情况下,只能通过不断切换进程内部执行的指令来提供良好的交互响应。例如,对程序代码进行两个部分的划分,一个部分负责文件读写,一部分负责与用户交互。每个部分以一个线程形式运行,通过将CPU在进程内部的两个线程间切换,达到执行慢I/O操作的同时,也能插入执行用户交互代码。这样一来,在有限的硬件资源情况下,也一定程度降低了平均响应时长。
对于单核来讲,可能见效一般,但是对于多处理器多核来讲,将每一个线程分配各同一CPU种的多核后,那就是并行执行多个线程,整体所需时长也将下降。从用户的角度来讲,同样是降低了程序执行所需平均时长。
所以综合来看,线程在单核系统上中可以有效降低平均响应时长,在多核系统上,通过并行,可以有效降低整体时长。为了达成精确调度,与进程一样,线程也应该维护运行时信息。通常多线程技术适用于如下使用场景:
- 前台和后台工作
- 异步处理
- 提高执行速度
- 模块化程序结构
2.2 与进程关系
如上图所示,一个进程中至少含有一个线程(也称为主线程),当然也可以根据程序特性选择使用多线程技术。存在多个线程时,线程间相互共享进程用户地址空间。
什么意思?换句话就是说,假如内存是整个街道,那么进程就是一套套房子,每套房子相互之间共享道路等资源,就像进程在操作系统中共享总线一样。如果进程是房子,那么线程就是房子中的房间,各有各的户型。房间之间相互共享房子内公共资源,如厨房、卫生间等,当然每个房间本身也存在一定内部资源,仅供自己使用。
如果房子创建了,那么肯定至少会创建一个房间。如果房子销毁了,那么内部所有房间也自然被销毁。
2.3 线程支持级别
线程的实现上分为用户级(ULT, User Level Thread)和内核级(KLT, Kernel Level Thread)两种。
用户级
线程管理所有工作在应用程序内完成,内核意识不到线程的存在。即应用程序本身拥有绝对自主权,它可以根据程序特点自由使用线程管理策略,同时,也避免了由于某些操作系统内核不支持多线程而无法使用线程技术。
但由于内核未进行支持,内核仍以进程为单位对程序进行调度。因此在线程中调用操作系统API时,一旦发生阻塞,则整个进程都将被阻塞。内核级
对应的有,内核级线程中线程管理工作全部由内核完成,应用程序只需要提交任务为线程即可。与用户级线程相比,内核掌握了更多线程相关信息,可以将同一个进程调度到不同处理器中,某一个线程的阻塞也不再会阻塞整个进程。
但由于应用程序进程仍处于用户模式,而线程处于内核模式,因此再讲控制从一个线程传送到同一个进程的另一个线程时,需要进行状态切换。
由于各有优劣,某些操作系统就混合使用了用户级、内核级线程,在混合方法中,线程在用户空间中创建、调度,但也可以托管一部分给内核调度。通过在程序级别与硬件进行匹配,达到最合适的效果。
3. 总结
本篇主要从提高CPU利用率和降低平均响应时长两个角度来分析了进程、线程概念的由来和必要性,它们均只是内存中的调度单位,创建进程或线程的目的是便于进行下一步的并行执行,也就是在分配给多个CPU或多核或是单核上,按照一定策略调度执行。下一篇中将从并行设计开始说起,看看切换过程中要注意哪些事项。