计算机系统009 – 操作系统之进程

一.多线程的发展历史

上一篇计算机系统008 –
操作系统概况
中讲到,计算机操作系统发展的两个主要方向是提高CPU使用率,以及降低响应时长。这两者在微观调度方法一级来看是相互违背的,但从宏观调度策略来讲,还是可以做出折中的利弊权衡。当然,并非所有计算机系统都追求所谓的平衡,很多时候特定领域的系统更重视高实时响应,而有的场景只需要尽可能利用CPU计算能力即可。

多线程的发展大约经过了三个历史阶段: 1.最早出现的计算机主要是为了解决复杂的计算问题,而早期的计算机只能够接受一些特定的指令,当用户在输入这个指令的时候,计算机才会去工作,如果不输入指令,计算机就不会工作,因为计算机本身不会存储指令,很多情况下,计算机都会处于等待状态,并没有真正利用计算机本身的资源。于是进入了批处理操作系统的演变过程。
2.批处理操作系统:用户把需要执行的多个指令写在磁带上,然后让计算机去读取这个磁带执行相应的程序,并把结果输出在另外一个磁带上。
3.虽然批处理这种方式能大大提升计算机资源的利用率,但是会遇到一些问题,比如,操作系统的一个指令阻塞了,CPU会等到这个指令执行完毕后,再去执行下一个指令,这样的话就会使CPU处于等待状态,无法提高资源的利用率。为了解决这个问题,就出现了进程和线程的概念。

方向决定方法,对于进程的理解,就从这两个目标开始。

欢迎加群 499754614学习交流,备注豆瓜。

1. 进程

进程这个概念忽然跳出来,略显突兀,可是很多资料上就这么自然地放在这里。为了更好地理解进程概念的由来,这里先对照计算机系统发展史来说明一下。

二.进程与线程

进程
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,系统会给每个进程分配独立的内存地址空间,并且每个进程的地址不会相互干扰。如果要完成CPU时间片的切换,就要保证之前的进程在执行的时候执行到某个位置,下次切换回来的时候仍然可以从这个位置开始执行。所以进程就是资源分配的最小单元。
在进程出现之前,指令是一次性加载到内存中,如果要进行指令切换的话,就要对指令进行隔离,而在批处理操作系统中是无法对指令进行隔离的。
有了进程以后,可以让操作系统从宏观上实现并发。并发是通过CPU时间片的不断切换执行的。在任意一个时刻,对于单核CPU来说,只会有一个任务去执行,只是通过切换时间片的方式完成了并行执行。
线程
线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,每个线程会负责一个独立的子任务,在配合多核处理器,去实现多个子任务并行处理的结果。线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多核CPU环境下就允许多个线程同时运行。进程在一个时间内只能干一件事情,如果想同时干多件事情的话,
就要把进程中的多个子任务划分到多个线程,通过线程的切换执行去实现任务的实时性。所以,线程是真正意义上实现了并行执行。

1.1 进程的由来

最早的时候,也就是单处理器时期,系统要执行一个计算任务的步骤是将整个程序加载到内存中,然后由CPU逐条读取并执行指令。程序包括代码指令和预定的数据,指令执行期间,肯定会产生一些计算结果,部分为临时使用。所以自然而言,寄存器中应当保存一部分临时值,举个例子,加法器执行一个加法运算,寄存器中就需要存储CF进位信息。那么总的来讲,一个程序运行后,至少会包含如下信息:

  • 程序代码
  • 数据集
  • 运行时信息

这个时期中计算机系统的计算时间主要通过预约或批处理系统监控获取,所存在的最大问题是CPU和I/O运行速率量级相差太大,CPU的速率远远高过I/O速率,导致一旦执行到I/O指令时,系统中CPU资源就处于闲置状态,而指令每次执行,又至少会涉及到一次内存地址的访问。也就是说,系统运行期间,CPU资源被大量浪费

为了提升CPU资源使用率,降低计算成本,可行的主要方法是加载更多的程序到内存中,一旦某个程序要执行I/O操作,就切换到其他程序运行。当然这里能够切换的前提是,I/O具体操作由其他单元进行控制,只需少量CPU执行前后准备、收尾工作即可,典型的有DMA直接内存访问。既然涉及到程序切换,那就势必要对一个运行中程序的所有相关信息进行统一管理,这样才能以该单位进行切换避免出错,而这里,这个单位就叫做进程。

三.多线程的使用场景

因为多线程最终解决的是“等待”的问题,所以多线程一般用于: 1.通过并行计算提高程序执行的性能,比如一个程序中的计算逻辑的执行性能可以通过多线程的技术将一个程序中的多个逻辑运算并行操作执行。
2.需要等待网络,IO响应等耗费大量的时间,可以采用异步的方式来减少总体的响应时间,也就是解决阻塞(当程序运行到某个函数时,由于一些原因导致程序要等待某个事件的发生而暂时停止占用CPU)的问题,阻塞会使CPU闲置而浪费资源。

1.2 进程状态

前面讲了将内存中运行中的程序也就是进程进行切换,来达到提升CPU利用率的目的。既然有切换,也就意味着至少有运行、非运行两种状态。在该粒度控制下,CPU可以在多个程序间进行切换,避开I/O操作的闲置指令周期,提升整体使用率。

然而上面的调度只是粗粒度的,任何时候,要想做出更精确的决定,就必须基于更多的有效信息。对于操作系统来讲,要想更精确地在不同进程间切换CPU,就必须掌握更多进程的有效信息,而增加信息的第一步,就是降低粒度,细化进程状态的分类。例如对于非运行状态而言,它难以对新创建的进程和处于I/O等待过程中的进程做区分,这样一来,将处于I/O等待过程中的重新运行就纯属对CPU资源的浪费行为。

在上述两种状态的基础上,现代操作系统通常会区分为如下几种状态:

图片 1

  • 新建态

  • 就绪态

  • 挂起态(可选)
    添加挂起态的根本原因是每多加载一个进程进入内存空间就多了一个选择,就可以更大程度地避开I/O等待,有效利用CPU资源。但由于物理内存有限,导致能加载入内存的进程数量也有限。为了拥有更多可选择进程,就通过将一部分处于I/O等待过程中的进程从内存空间写出到磁盘空间,当需要重新调度被写出进程时,再从磁盘空间重新读取。
    当然也存在其他方法去降低一个进程所需的物理内存,如基于页交换硬件支持,来实现只加载进程的必要信息而非全部内容来实现降低内存占用。这一部分会再后面的内存管理进一步介绍。

    • 阻塞/挂起态
    • 就绪/挂起态
  • 阻塞/等待态
    进程在某些事件发生前不能继续执行,如I/O操作完成事件。

  • 运行态

  • 退出态

1.3 进程调度

如下图所示,内存(虚拟内存)中进程整体构成如下。进程状态属于进程运行时信息中的一部分,现代操作系统中比较通用的运行时状态信息是一个称为进程控制块PCB(Process
Control Block)的结构体。

图片 2

PCB中主要含有三类信息:

  • 进程标识信息
    表示信息主要指标识符,包括进程ID(PID)、父进程ID、用户ID(UID)

  • 进程状态信息
    包括用户可见寄存器、控制和状态寄存器、栈指针三个子类型数据,通常CPU设计中存在一组称为程序状态字PSW(Program
    Status Word)的寄存器,它包含条件码和其他状态信息。

    以Intel X86为例:

    图片 3

  • 进程控制信息
    包括调度和状态信息(如优先级、事件等)、数据结构、进程间通信、进程特权、存储管理、资源的所有权和使用情况等。

而所谓的各种状态进程队列,实际上都是以链表形式管理各进程PCB数据结构。

至此我们知道,进程由代码、数据、栈、运行时信息组成,进程概念的提出主要是便于调度管理,其中所包含的运行时信息可以为调度方法提供有效信息。而调度本身又是通过在处于不同状态的进程间切换以避开I/O等待,从而达到提高CPU利用率的目的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注