Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

7.1 进程的概念

进程(Process)是操作系统进行资源分配和调度的基本单位,是程序运行的实例。我们平时打开的应用程序、运行的服务都是进程。理解进程的基本概念,是理解整个操作系统和并发编程的基础。

什么是进程

进程是程序的一次执行过程,是操作系统分配资源(内存、CPU时间、文件句柄等)的基本单位。

进程和程序的区别

  • 程序:是静态的指令和数据的集合,存储在磁盘上,是永久的
  • 进程:是程序的动态执行过程,有生命周期,是暂时的,同一个程序可以对应多个进程(比如打开多个浏览器窗口,就是同一个浏览器程序的多个进程)

进程的组成

一个进程通常包含以下内容:

  1. 代码段:程序的可执行代码
  2. 数据段:全局变量和静态变量
  3. :动态分配的内存(malloc/new分配的内存)
  4. :函数调用栈,存储局部变量、函数参数、返回地址等
  5. 进程控制块(PCB):操作系统管理进程的数据结构,存储进程的所有信息
  6. 打开的文件描述符:进程打开的文件、Socket等资源
  7. 信号处理函数:进程对各种信号的处理方式
  8. 地址空间:独立的虚拟地址空间

进程的特征

进程具有以下基本特征:

  1. 动态性:进程是程序的一次执行过程,有生命周期,状态会不断变化
  2. 并发性:多个进程可以同时在系统中运行,宏观上并行,微观上交替执行
  3. 独立性:每个进程有独立的地址空间,进程之间互相隔离,一个进程的崩溃不会影响其他进程
  4. 异步性:进程以不可预知的速度向前推进,操作系统需要保证进程执行的结果是确定的
  5. 结构性:每个进程都有自己的代码段、数据段、栈、PCB等结构

进程的状态与生命周期

一个进程在其生命周期中会处于不同的状态,通常有五种基本状态:

1. 新建状态(New)

进程正在被创建,操作系统正在为其分配资源和初始化PCB,还没有准备好运行。

2. 就绪状态(Ready)

进程已经准备好运行,获得了除CPU之外的所有资源,等待被调度器分配CPU时间。

  • 系统中会有一个就绪队列,存放所有处于就绪状态的进程

3. 运行状态(Running)

进程正在CPU上执行指令,单核CPU同一时间只能有一个进程处于运行状态,多核CPU可以有多个进程同时运行。

4. 阻塞状态(Blocked / Waiting)

进程正在等待某个事件发生(比如等待IO完成、等待信号、等待锁),暂时无法运行,即使CPU空闲也不能执行。

  • 等待的事件发生后,进程会进入就绪状态,等待调度

5. 终止状态(Terminated)

进程执行完成或者被异常终止,等待操作系统回收资源。

状态转换

新建 → 就绪 → 运行 → 终止
        ↓↑   ↓↑
        阻塞 ←
  • 就绪 → 运行:调度器选择一个就绪进程分配CPU
  • 运行 → 就绪:进程时间片用完,或者被更高优先级的进程抢占
  • 运行 → 阻塞:进程等待某个事件(IO、锁、信号等)
  • 阻塞 → 就绪:等待的事件发生了
  • 运行 → 终止:进程执行完成,或者被杀死

进程控制块(PCB)

进程控制块(Process Control Block)是操作系统中用来描述和管理进程的数据结构,每个进程对应一个唯一的PCB,存储了进程的所有信息。

PCB通常包含以下信息:

  1. 进程标识信息:进程ID(PID)、父进程ID(PPID)、用户ID(UID)、组ID(GID)等
  2. 进程状态信息:当前状态(就绪、运行、阻塞等)、优先级、调度相关信息
  3. 进程控制信息:程序计数器(PC,下一条要执行的指令地址)、寄存器的值(上下文切换时保存)
  4. 内存管理信息:页表指针、虚拟地址空间信息、内存使用情况
  5. IO信息:打开的文件描述符列表、IO设备使用情况
  6. 统计信息:CPU使用时间、内存使用情况、IO操作统计等

操作系统通过PCB来管理所有进程,进程切换时,操作系统会保存当前进程的上下文到PCB中,然后加载下一个进程的上下文。

进程的创建与销毁

进程的创建

操作系统创建进程的通常步骤:

  1. 分配一个唯一的PID,创建PCB
  2. 为进程分配地址空间和资源
  3. 初始化PCB,设置默认的进程状态、优先级等
  4. 将进程加入就绪队列,等待调度执行

常见的创建进程的系统调用:

  • Unix/Linux:fork()创建子进程,exec()系列函数加载新的程序
  • Windows:CreateProcess()函数创建新进程

fork()的特点

Unix/Linux中的fork()系统调用会创建一个和父进程几乎完全一样的子进程:

  • 子进程获得父进程地址空间的副本,父子进程有独立的地址空间,修改各自的数据不会互相影响
  • fork()调用一次返回两次:父进程返回子进程的PID,子进程返回0
  • 子进程会继承父进程的打开的文件描述符、信号处理方式、优先级等
  • 通常fork()之后会调用exec()加载新的程序,替换子进程的地址空间

进程的销毁

进程终止的方式:

  1. 正常终止:进程执行完成,调用exit()系统调用主动退出,返回退出状态码
  2. 异常终止:进程运行时出现错误(比如段错误、除零错误),被操作系统终止
  3. 被其他进程杀死:其他进程调用kill()系统调用发送终止信号

进程终止后,操作系统会回收进程占用的资源(内存、文件句柄等),但PCB会保留一段时间,保存退出状态码,直到父进程调用wait()或者waitpid()获取退出状态,这时候PCB才会被完全回收。如果父进程没有调用wait(),已经终止的子进程会变成僵尸进程(Zombie Process)。

进程的层次结构

进程之间有父子关系,形成树形的层次结构:

  • 每个进程都有一个父进程,除了系统启动时创建的init进程(PID=1)
  • 父进程创建子进程,子进程可以再创建自己的子进程
  • 父进程终止时,子进程会被init进程收养

常见的特殊进程

  1. init进程(PID=1):系统启动后运行的第一个用户态进程,负责启动其他系统服务,是所有进程的祖先。
  2. 僵尸进程:子进程已经终止,但父进程没有调用wait()回收它的PCB,子进程的PCB仍然存在,占用PID资源,需要避免僵尸进程。
  3. 孤儿进程:父进程已经终止,子进程还在运行,会被init进程收养,由init进程负责回收。
  4. 守护进程(Daemon Process):在后台运行的服务进程,没有控制终端,通常系统服务都是守护进程,比如httpd、sshd等。

进程的并发执行

多个进程可以同时在系统中运行,宏观上是并行的,用户感觉多个程序同时在执行;微观上,单核CPU上进程是交替执行的,通过快速的上下文切换,让用户感觉是同时运行的。

进程并发执行的好处:

  1. 提高CPU利用率,避免CPU等待IO时空闲
  2. 提高系统吞吐量,同时处理多个任务
  3. 改善用户体验,用户可以同时运行多个程序,不会因为一个程序卡住而影响其他程序

思考问题

  1. 进程和程序有什么区别?同一个程序可以对应多个进程吗?举例说明。
  2. 进程有哪几种基本状态?它们之间是如何转换的?
  3. 什么是PCB?它存储了哪些信息?为什么需要PCB?
  4. 什么是僵尸进程和孤儿进程?它们有什么危害?如何避免僵尸进程?