4、进程与线程基础:进程的概念、进程控制块PCB、进程状态与转换、进程控制(创建、撤销、阻塞、唤醒)、进程的层次结构

操作系统作为计算机系统的资源管理者与程序运行的支撑环境,其核心任务之一便是对“正在运行的程序”进行高效、安全的管理。在《计算机考研操作系统课程》中,“进程”是贯穿全课程的第一大核心抽象概念。理解进程的本质、其唯一标识PCB、状态流转的规律以及操作系统对其进行的控制操作,是后续学习线程、调度、同步与死锁的基石。本章将系统性地拆解进程从“概念”到“控制”的完整知识链条,帮助考生建立起动态、立体的进程观。

进程管理核心知识图谱 进程 (Process) 动态执行中的程序 资源分配的基本单位 独立调度与独立收账 PCB (进程控制块) 进程存在的唯一标志 包含PID、状态、上下文 常驻内存内核区 进程状态 运行 / 就绪 / 阻塞 创建 / 终止 挂起态(可选) 标识 记录 进程控制 (Process Control) 创建 (Create) 撤销 (Destroy) 阻塞 (Block) 唤醒 (Wakeup) 切换 (Switch) 进程层次结构 (Process Hierarchy) 父进程 (Parent) 子进程 (Child) 进程树 / 进程组 并发执行 资源共享与独立

4.1 核心原理:从程序到进程的抽象跃迁

在早期的批处理系统中,内存中同时只存在一道程序,CPU 执行完该程序后才加载下一道,这种“单道顺序执行”的方式虽然简单,但资源利用率极低。为了提升系统吞吐量,引入了多道程序设计技术,即允许多个程序同时进入内存并交替执行。然而,程序本身是静态的、有序的指令集合,它无法描述“执行到哪一行”“占用了哪些资源”“等待什么事件”等动态信息。因此,操作系统引入了进程(Process)这一概念。

进程可以被定义为:一个具有独立功能的程序关于某个数据集合的一次运行活动。它不仅仅是程序代码,还包括当前的活动状态、相关的数据、堆栈以及系统资源。进程具有以下五个重要特征:

  • 动态性:进程是程序的一次执行过程,有创建、运行、消亡的生命周期;程序是静态的,可永久保存。
  • 并发性:多个进程可同时存在于内存中,并在宏观上同时运行、微观上交替执行。
  • 独立性:进程是系统进行资源分配和调度的独立单位,各进程独立拥有地址空间和资源。
  • 异步性:进程按各自独立的、不可预知的速度向前推进,可能导致结果的不确定性,因此需要同步机制。
  • 结构性:进程实体由程序段、数据段和进程控制块(PCB)三部分组成,其中PCB是进程的灵魂。
考研核心考点:进程与程序的区别是高频选择题。程序是静态指令集合,进程是动态执行过程;进程是暂时的,程序是永久的;进程可并发执行,程序不能;进程包含PCB,程序没有。此外,进程是资源分配的基本单位,而线程是CPU调度的基本单位,这一区分在后续章节尤为重要。

4.2 进程控制块 PCB:进程存在的唯一标志

操作系统为了管理进程,必须为每个进程建立一个数据结构,用于记录其全部属性和状态信息,这个数据结构就是进程控制块(Process Control Block, PCB)。PCB是进程实体的一部分,也是进程存在的唯一标志。当系统创建一个进程时,即为它分配一个PCB;当进程被撤销时,系统回收其PCB,进程也随之消亡。

PCB中通常包含以下四大类信息:

信息类别 具体内容 作用说明
进程标识符 进程内部标识符(PID)、外部标识符、父进程标识符(PPID) 唯一标识一个进程,用于系统调用和进程管理
处理机状态信息 通用寄存器值、程序计数器PC、程序状态字PSW、栈指针 保存进程被中断时的现场信息,用于恢复执行
进程调度信息 进程状态(运行/就绪/阻塞)、优先级、等待事件、调度算法所需参数 供调度程序判断何时分配CPU给该进程
资源管理信息 内存空间地址(代码段/数据段/栈)、打开文件列表、I/O设备使用情况 记录进程占用的系统资源,便于回收与共享

PCB的组织方式通常有链接方式和索引方式两种。链接方式将同一状态的PCB通过指针链接成队列(如就绪队列、阻塞队列);索引方式则通过索引表按状态分类,表中记录PCB在内存中的地址。无论哪种方式,PCB都常驻内存,由操作系统内核统一管理,用户程序无法直接访问。

记忆技巧:PCB可以类比为“进程的身份证+病历本”。身份证(PID)用于识别身份;病历本(寄存器、状态、资源)记录了进程的“健康状况”和“历史病情”,方便操作系统随时“诊断”和“治疗”。

4.3 进程状态与转换:生命周期的动态刻画

进程从创建到终止,会经历多种状态。经典的三态模型是考研的基础,而五态模型则更贴近实际系统。以下是各状态的定义与转换条件:

  • 运行态(Running):进程已获得CPU,正在执行。单核CPU下,任一时刻最多只有一个进程处于运行态。
  • 就绪态(Ready):进程已具备运行条件,等待CPU分配。就绪队列中可能有多个进程。
  • 阻塞态(Blocked/Waiting):进程因等待某事件(如I/O完成、信号量、消息)而暂时无法运行,即使分配CPU也无法执行。
  • 创建态(New):进程正在被创建,尚未进入就绪队列。系统正在为其分配PCB和资源。
  • 终止态(Terminated):进程已结束运行,系统正在回收资源,PCB即将被清除。

状态转换的四个关键路径如下:

  1. 就绪态 → 运行态:进程被调度程序选中,获得CPU。这是唯一进入运行态的路径。
  2. 运行态 → 就绪态:时间片用完,或更高优先级进程抢占CPU,进程被迫让出处理器。
  3. 运行态 → 阻塞态:进程主动请求I/O或等待某事件,如执行 wait() 系统调用。
  4. 阻塞态 → 就绪态:进程等待的事件发生(如I/O完成),被唤醒后进入就绪队列,等待重新调度。
易错警示:阻塞态不能直接转换为运行态!进程被唤醒后必须先进入就绪态,等待调度程序再次分配CPU。同理,就绪态不能直接转换为阻塞态,因为就绪态进程尚未运行,无法主动请求等待事件。此外,运行态可以转换为就绪态(被动)或阻塞态(主动),但阻塞态只能由外部事件唤醒,不能自行唤醒。

在某些操作系统中,还引入了挂起态(Suspend),用于将进程从内存交换到外存,以缓解内存不足。挂起态分为“就绪挂起”和“阻塞挂起”,其转换关系更为复杂,但考研大纲通常以三态和五态为主,挂起态作为扩展了解即可。

4.4 进程控制:操作系统对进程生命周期的干预

进程控制是操作系统通过原语(Primitive)操作来实现的。原语是由若干条机器指令构成的、用于完成特定功能的原子操作,其执行过程不可被中断。进程控制的核心原语包括创建、撤销、阻塞和唤醒。

4.4.1 进程创建

进程创建的原语流程如下:

  1. 申请空白PCB,为新进程分配唯一的PID。
  2. 为新进程分配运行所需的资源(内存空间、文件、I/O设备等)。
  3. 初始化PCB,包括设置状态为就绪态、设置优先级、初始化寄存器等。
  4. 将新进程的PCB插入就绪队列,等待调度。

触发进程创建的事件通常有:用户登录、作业调度、系统服务请求(如打印服务)、用户程序通过 fork() 或 CreateProcess() 创建子进程。

4.4.2 进程撤销

进程撤销(终止)的原语流程:

  1. 根据PID找到目标进程的PCB,读取其状态。
  2. 若进程正在运行,立即终止其执行,并设置调度标志以重新调度CPU。
  3. 回收该进程占用的所有资源(内存、文件、设备等),并撤销其所有子孙进程(防止孤儿进程)。
  4. 将该进程的PCB从所在队列中移除,并释放PCB空间。

触发撤销的事件包括:进程正常结束(执行完最后一条指令)、进程因错误异常结束(如除零、越界)、被父进程或系统强制终止(如 kill() 命令)。

4.4.3 进程阻塞与唤醒

阻塞原语是进程主动发起的操作。当进程需要等待某事件(如I/O完成)时,它调用阻塞原语:

  1. 立即停止当前进程的执行。
  2. 将进程状态从运行态改为阻塞态,并保存其现场信息到PCB。
  3. 将PCB插入到对应事件的阻塞队列中。
  4. 转调度程序,重新分配CPU给其他就绪进程。

唤醒原语是由系统或其他进程发起的被动操作。当阻塞事件发生时(如I/O完成),系统调用唤醒原语:

  1. 从阻塞队列中移出目标进程的PCB。
  2. 将进程状态从阻塞态改为就绪态。
  3. 将PCB插入就绪队列,等待调度。
关键区别:阻塞是进程主动调用原语,唤醒是系统或他进程被动调用原语。阻塞与唤醒必须成对出现,否则进程将永远处于阻塞态,导致“死锁”或“饥饿”。在考试中,常以“进程A等待打印机,打印机完成后由中断处理程序唤醒A”作为典型场景。

4.5 进程的层次结构:父子进程与进程树

在UNIX/Linux系统中,进程之间存在明确的层次关系。一个进程可以通过 fork() 系统调用创建子进程,创建者称为父进程,被创建者称为子进程。子进程可以继续创建自己的子进程,从而形成一棵进程树(Process Tree)。系统启动后,第一个进程是 init(PID为1),它是所有进程的祖先。

进程层次结构具有以下特点:

  • 资源继承:子进程可以继承父进程的部分资源(如环境变量、打开的文件描述符),但拥有独立的地址空间。
  • 独立执行:父子进程是并发执行的独立实体,父进程不会因子进程的阻塞而阻塞(除非使用 wait() 等待)。
  • 撤销传播:当父进程被撤销时,其所有子进程通常也会被撤销(级联终止),以保证系统资源不泄漏。
  • 进程组与会话:多个相关进程可组成进程组,用于信号传递和作业控制。

在Windows系统中,进程没有严格的树形结构,而是采用“句柄(Handle)”机制来管理进程间关系,父子进程的关联相对松散。但考研大纲以UNIX/Linux的进程树模型为主要考查对象。

4.6 实战场景:Linux下进程创建与状态观察

为了将抽象概念落地,我们通过一个简单的Linux C程序来观察进程的创建与状态变化。假设我们编写如下代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();  // 创建子进程
    if (pid < 0) {
        printf("Fork failed!\n");
    } else if (pid == 0) {
        // 子进程执行路径
        printf("Child process, PID=%d, PPID=%d\n", getpid(), getppid());
        sleep(5);  // 模拟I/O等待,子进程进入阻塞态
        printf("Child wakes up and exits.\n");
    } else {
        // 父进程执行路径
        printf("Parent process, PID=%d, Child PID=%d\n", getpid(), pid);
        wait(NULL);  // 父进程阻塞等待子进程结束
        printf("Parent reaps child and exits.\n");
    }
    return 0;
}

运行过程分析:

  1. 父进程执行 fork() 后,系统创建一个子进程,子进程获得父进程PCB的副本,但PID不同。
  2. 父子进程并发执行。子进程执行 sleep(5) 时,子进程从运行态转为阻塞态(等待定时器事件)。
  3. 父进程执行 wait(NULL) 时,父进程也进入阻塞态,等待子进程终止。
  4. 5秒后,子进程的定时器事件触发,子进程被唤醒,进入就绪态,随后运行并打印退出信息,进入终止态。
  5. 子进程终止后,父进程被唤醒,回收子进程的PCB(僵尸进程被清理),父进程退出。

通过 ps -l 命令可以实时查看进程状态:R(运行)、S(可中断睡眠/阻塞)、D(不可中断睡眠)、Z(僵尸)。这个案例完整展示了进程的创建、阻塞、唤醒、撤销全过程,是考研面试中常见的综合题素材。

复盘经验:在分析进程状态转换时,务必抓住“谁主动、谁被动”这一主线。阻塞是进程主动让出CPU,唤醒是外部事件触发。同时注意,wait() 调用会使父进程阻塞,而 sleep() 会使当前进程阻塞,两者都是阻塞态的典型触发条件。

4.7 常见误区与总结复盘

在学习和应试中,考生容易在以下环节出现理解偏差,需要特别警惕:

误区点 错误理解 正确认知
进程与程序 认为程序就是进程,或进程就是程序 进程是动态的,程序是静态的;一个程序可对应多个进程
PCB的作用 认为PCB只是记录PID PCB记录状态、寄存器、资源、调度信息,是进程存在的唯一标志
状态转换路径 认为阻塞态可直接转运行态 阻塞态必须先转就绪态,再由调度程序分配CPU
原语与系统调用 混淆原语和普通系统调用 原语具有原子性,不可中断;系统调用可能被中断
父子进程关系 认为父子进程共享全部内存 父子进程拥有独立地址空间,采用写时复制(COW)技术共享物理页

本章总结:进程是操作系统中最核心的抽象,PCB是进程的“身份证”,状态转换是进程的“生命轨迹”,原语操作是系统的“干预手段”,进程树是进程间的“血缘关系”。掌握这四者的内在联系,就掌握了进程管理的骨架。后续学习线程时,可以将线程视为“轻量级进程”,其PCB(TCB)更小,共享进程的地址空间,但调度和资源分配的基本逻辑与进程一脉相承。

应试提醒:本章是操作系统考研的必考章节,选择题常考状态转换、PCB内容、进程特征;简答题常考进程与程序的区别、原语操作流程;综合题常结合 fork() 分析进程树和状态变化。建议考生亲手在Linux环境中运行上述代码,观察 ps 输出,将理论与实践结合,方能牢固掌握。