关于进程的基本概念

138 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

进程和程序是操作系统领域的两个重要的概念,很多人对这两个概念比较模糊,甚至认为它们是相等的,其实进程是程序执行的一个实例。

进程的来由

IBM在20世纪设计的多道批处理程序中没有进程(process),人们使用工作(job)这个术语,后来的设计人员慢慢启用进程这个术语。顾名思义,进程是执行中的程序,即一个程序加载到内存后变成了进程,公式表达如下。

进程=程序+执行

在计算机的发展历史过程中,为什么需要进程呢? 早期,整个操作系统只运行一个程序,其CPU利用率的低下程度可想而知。为了提高CPU利用率,人们设计了在一台计算机中加载多个程序到内存中并让它们并发运行的方案。每个加载到内存中的程序称为进程,操作系统管理着多个进程并发执行。进程会认为自己独占CPU,这是很重要的抽象。

若没有进程,操作系统就会退回单道程序操作系统了。进程的抽象是为了提高CPU的利用率,任何的抽象都需要一个物理基础,进程的物理基础便是程序。程序在运行之前需要有一个安身之地,这就要求操作系统在装载程序之前要分配合适的内存。此外,操作系统还需要小心翼翼地处理多个进程共享同一块物理内存时可能引发的冲突问题。 作者在10年前购买的笔记本电脑还是奔腾单核的处理器,但依然可以很流畅地同时做很多事情,如边听音乐边使用Word软件处理文字,还可以用邮箱客户端收发邮件等。其实CPU在某一个瞬间只能运行一个进程,但是在一段时间内,它可以运行多个进程,这样就让人们产生了并行的错觉,这就是常说的“伪并行”。 假设有一个只包含3个进程的简易操作系统,这3个进程都需要装载到系统的物理内存中并运行,图7.1(a)~(c)分别从物理视角、逻辑视角和时序视角展示了进程模型。进程和程序之间的关系是比较微妙的,程序是用于描述某件事情的一些操作序列或者算法,而进程是某种类型的活动,它包含程序、输入、输出以及状态等。如果把做菜看作进程,那么做菜的工序可以被看作程序,大厨可以被看作处理器,厨房可以被看作运行环境,厨房里有需要的食材、调料以及烹饪工具等,大厨阅读菜谱、取各种原料、炒菜以及上菜等一系列动作的总和可以理解为一个进程。假设在炒菜的过程中,为了接听一个紧急的电话,大厨会先记录一下现在菜做到哪一步了(保存进程的当前状态),那么这个接听电话的动作就是另一个进程了。这相当于处理器从一个进程(做菜)切换到了另一个高优先级的进程(接电话)。等电话接完了,大厨继续执行原来做菜的工序。做菜和打电话是两个相互独立的进程,同一时刻只能做一件事情。 线程称为轻量级进程,它是操作系统调度的最小单元,通常一个进程可以拥有多个线程。线程和进程的区别在于进程拥有独立的资源空间(如进程地址空间),而线程则共享进程的资源空间。Linux内核并没有特别的调度算法或定义特别的数据结构来标识线程,线程和进程都使用相同的进程描述符数据结构。内核里使用clone()函数来创建线程,其工作方式和创建进程的fork()函数类似,但它会确定哪些资源和父进程共享,哪些资源为线程独享。在Linux内核中,线程对应一个进程描述符,而进程对应一个(单线程的进程)或者一组进程描述符(多线程的进程)。

程序、进程以及线程的定义如下。

  • 程序通常是指完成特定任务的一个有序指令集合或者一个可执行文件,包含可运行的CPU指令和相应的数据等信息,它不具有“生命力”。
  • 进程是一段执行中的程序,是一个有“生命力”的个体。一个进程除了包含可执行的代码(如代码段),还包含进程的一些活动信息和数据,如用来存放函数形参、局部变量以及返回值的用户栈,用于存放进程相关数据的数据段,用于切换内核中进程的内核栈,以及用于动态分配内存的堆等。进程是用于实现多进程并发执行的一个实体,实现对CPU的虚拟化,让每个进程都认为自己独立拥有一个CPU。实现这个CPU虚拟化的核心技术是上下文切换(context switch)以及进程调度(schedule)。
  • 线程是操作系统分配内存、CPU时间片等资源的基本单位。   在传统操作系统中,进程是管理资源的基本单位,线程是调度的基本单位。从Linux内核实现的角度看,并没有使用额外的数据结构或者调度算法来专门为线程服务,进程和线程都共用进程描述符、调度实体以及调度算法等资源。

进程描述符

进程是操作系统中调度的一个实体,需要对进程所拥有的资源进行抽象,这个抽象形式称为进程控制块(Process Control Block,PCB),本书也称其为进程描述符。进程描述符需要描述如下几类信息。

  • 进程的运行状态:包括就绪、运行、等待阻塞、僵尸等状态。
  • 程序计数器:记录当前进程运行到哪条指令了。
  • CPU寄存器:主要用于保存当前运行的上下文,记录CPU所有必须保存下来的寄存器信息,以便当前进程调度出去之后还能调度回来并接着运行。
  • CPU调度信息:包括进程优先级、调度队列和调度等相关信息。
  • 内存管理信息:进程使用的内存信息,如进程的页表等。
  • 统计信息:包含进程运行时间等相关的统计信息。
  • 文件相关信息:包括进程打开的文件等。

因此进程描述符是用于描述进程运行状况以及控制进程运行所需要的全部信息,是操作系统用来感知进程存在的一个非常重要的数据结构。任何一个操作系统的实现都需要有一个数据结构来描述进程描述符,所以Linux内核采用一个名为task_struct的结构体。task_struct数据结构包含的内容很多,它包含进程所有相关的属性和信息。在进程的生命周期内,进程要和内核的很多模块进行交互,如内存管理模块、进程调度模块以及文件系统模块等。因此,它还包含了内存管理、进程调度、文件管理等方面的信息和状态。Linux内核把所有进程的进程描述符task_struct数据结构链接成一个单链表(task_struct->tasks),task_struct数据结构定义在include/ linux/sched.h文件中。 task_struct数据结构包含的内容可以简单归纳成如下几类。

  • 进程属性相关信息。
  • 进程间的关系。
  • 进程调度相关信息。
  • 内存管理相关信息。
  • 文件管理相关信息。
  • 信号相关信息。
  • 资源限制相关信息。