进程的概念、组成、特征与状态转换

引言

打开任务管理器,你会看到几十个正在运行的程序。微信在收发消息,浏览器在渲染页面,音乐播放器在解码音频。问题是:只有一个 CPU(或几个核心),怎么同时跑这么多程序?

答案就是进程。进程是操作系统分配资源、调度运行的基本单位。理解了进程,你就理解了操作系统最核心的设计思想。这篇文章是第二章"进程与线程"系列的第一篇,聚焦进程的概念、组成、特征和状态转换——这是 408 考研中每年都会涉及的高频考点。

📌 核心要点

  • PCB(进程控制块)是进程存在的唯一标志,包含 PID、状态、资源清单等关键信息
  • 进程实体 = PCB + 程序段 + 数据段;进程是动态运行过程,进程实体是某一时刻的静态快照
  • 五状态模型是核心考点:运行态、就绪态、阻塞态、创建态、终止态,转换条件必须烂熟于心
  • 进程切换通过原语实现,原语执行期间关中断,保证操作的原子性
  • 线程是 CPU 调度的最小单位,同一进程内线程切换的开销远小于进程切换

目录


什么是进程?——从程序到进程的蜕变

程序是菜谱,进程是炒菜

程序是静态的,安静地躺在磁盘里。
比如 QQ.exe,它就是一堆指令和数据,存储在 D:\Tencent\QQ\Bin\ 目录下。
你不双击它,它什么都不是。

当你双击 QQ.exe,操作系统把它从磁盘加载到内存,
分配一块地址空间,创建一个进程控制块(PCB),
然后让 CPU 开始执行它的指令。这一连串动作完成后,进程就诞生了。

所以:进程是程序的一次执行过程,是系统进行资源分配和调度的独立单位。

[IMAGE] 程序与进程的关系示意图:左侧为磁盘中的可执行文件(静态),右侧为内存中的进程实体(动态),用箭头标注"加载→创建进程"的过程。Alt text: 程序到进程的转换——从磁盘静态文件到内存动态实体的过程。

一个程序可以对应多个进程。比如你打开了三个 Chrome 窗口——表面上是三个窗口,
底层是三个不同的进程,各自有独立的 PCB 和数据段,
但它们共享同一份程序段(Chrome 的可执行代码)。
考研选择题喜欢在这里设陷阱:同一程序多次运行,
PCB 不同、数据段不同、程序段可以相同

进程实体与进程的区别

这是一个考研中的精细考点。进程实体(也叫进程映像)包含三部分:
PCB、程序段、数据段。它是进程某一时刻的快照,是静态的。

进程则是进程实体的运行过程,是动态的。
两者的关系就像"一张照片"与"照片中的人物正在进行的活动"——照片是静态的,
活动是动态的。

🔍 考点速记:进程是动态的,进程实体是静态的。选择题里说"进程就是进程实体"——这是错的。

[CHART] 进程实体的三层结构示意图:顶部为 PCB(进程标识与控制信息),中部为程序段(可执行代码),底部为数据段(全局变量、堆、栈等运行时数据),三个模块组成一个完整的进程映像。

为什么需要"进程"这个抽象?

在早期的单道批处理系统中,内存里一次只放一个程序。
CPU 执行完了,换下一个。这很简单,但效率极低——当程序等待 I/O 时,CPU 就闲着。

操作系统工程师们面临一个问题:如何让多个程序"看起来同时"运行?
答案就是引入进程抽象。每个程序成为一个独立运行的实体,
操作系统在这些实体之间快速切换,让 CPU 始终有活干。

[PERSONAL EXPERIENCE] 我在第一次理解进程概念时犯过一个认知错误:
以为"进程 = 程序 + 运行状态"。这个理解漏掉了最关键的一环——资源分配的视角。
进程不只是"正在运行的程序",它是操作系统向一个运行实体分配 CPU 时间、
内存空间、文件句柄等资源的"账户"。
这个区分在后续学习线程时会更加重要——线程共享进程的"账户",但没有自己独立的资源"账户"。


进程的三块积木 —— PCB、程序段与数据段

每个进程在内存中的实体由三部分组成。
理解这三部分及其关系,是后续学习进程调度和内存管理的基础。

PCB(进程控制块)—— 进程的"身份证"

PCB 是进程存在的唯一标志
操作系统通过 PCB 感知进程的存在,也通过 PCB 管理进程。
创建一个进程,本质上是分配一个 PCB 并填入信息;
撤销一个进程,本质上是回收其 PCB。

PCB 中记录的信息可以归纳为四类:

信息类别 具体内容 用途
进程标识 PID(进程 ID)、UID(用户 ID)、父进程 ID 区分不同进程
处理机状态 通用寄存器、PC(程序计数器)、PSW(程序状态字) 进程切换时保存/恢复现场
进程调度 进程状态、优先级、调度队列指针、事件标识 供调度程序决策
资源清单 内存地址空间、打开文件列表、I/O 设备分配情况 资源管理

当一个进程被剥夺 CPU 时,操作系统会把它的寄存器值、PC 等"现场信息"
保存到它的 PCB 中。等它再次获得 CPU 时,操作系统从 PCB 恢复这些值,
进程就能从被打断的地方继续执行。这个过程叫上下文切换

[UNIQUE INSIGHT] 很多教材讲到 PCB 时只罗列它的字段,但最关键的设计意图在于:
PCB 把"管理一个进程所需的全部元数据"封装到了一个数据结构中。
操作系统不需要翻遍内存去找进程的信息——所有它需要的都在 PCB 里。
这是一种"元数据驱动设计",Linux 内核的 task_struct 结构体就是 PCB 的经典实现。

如果你想直观地感受 PCB,可以在 Linux 终端执行:

# 查看当前 shell 进程的信息
cat /proc/$$/status | head -20

输出中 PidStateVmRSS 等字段就是内核从该进程的 task_struct 中读取并暴露给用户的。

[IMAGE] Linux /proc 文件系统中进程信息的目录结构截图,展示 /proc/[PID]/ 目录下的 status、maps、fd 等虚拟文件。Alt text: Linux proc 文件系统中进程 PCB 信息的可视化——每个目录对应一个进程,目录中的文件对应 PCB 的各个字段。

程序段 —— 要执行的指令

程序段存放进程要执行的代码。对于同一个程序(如 QQ.exe)的多个进程实例,
它们的程序段内容相同——都是同一份可执行代码。
所以操作系统可以允许多个进程共享程序段(只读映射),节省内存。

数据段 —— 运行时数据

数据段存放进程运行过程中产生和使用的数据,包括:
全局变量、局部变量(栈区)、动态分配的内存(堆区)

即使是同一个程序的多个进程,它们的数据段也各不相同
你在两个 QQ 窗口中输入的是不同的聊天内容,这些内容就存储在各进程独立的数据段中。

🔍 考研高频陷阱:“同一程序多次执行,程序段相同、数据段相同”——这是错误的。程序段可以相同(共享),但数据段各自独立。

一段代码,理解 PCB 与 fork

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

int main() {
    pid_t pid;
    int x = 100;  // 数据段中的局部变量

    pid = fork();  // 创建子进程,底层为新进程分配 PCB + 数据段副本

    if (pid == 0) {
        // 子进程:拥有独立的 PCB 和数据段
        // 程序段与父进程相同,但 x 独立存储
        x = 200;
        printf("[子进程] PID=%d, x=%d (地址: %p)\n",
               getpid(), x, (void*)&x);
    } else if (pid > 0) {
        // 父进程:x 不受子进程修改的影响
        sleep(1);  // 等待子进程先执行
        printf("[父进程] PID=%d, 子PID=%d, x=%d (地址: %p)\n",
               getpid(), pid, x, (void*)&x);
    }
    return 0;
}

运行这段代码,你会看到父进程和子进程的 x 分别为 100 和 200,
但它们的虚拟地址相同——这是因为操作系统为每个进程维护了独立的地址空间。
同一虚拟地址映射到不同的物理内存。

说明fork() 创建子进程时,内核为新进程分配新的 PID、PCB,
并复制父进程的数据段(写时拷贝优化)。程序段由于只读,父子共享。
这段代码验证了"同一程序两次执行,PCB 和数据段不同,程序段相同"。


进程的五个基本特征是什么?

进程有五个基本特征。考试中常以选择题形式出现,判断哪个是"最基本的特征"。

特征一:动态性(最基本特征)

进程是程序的一次执行过程。它有创建、运行、消亡的生命周期。
动态性是进程最根本的特征。 程序是静态的,进程是动态的——这是两者最本质的区别。

特征二:并发性

多个进程可以在同一时间间隔内"同时"运行。
宏观上你看到微信和浏览器都在工作,微观上它们在 CPU 上交替执行。
并发性正是引入进程机制的目的。

特征三:独立性

每个进程拥有独立的 PCB、独立的地址空间。
在未引入线程时,进程既是资源分配的独立单位,也是调度的独立单位。
一个进程崩溃通常不会直接影响其他进程——这是独立性的价值。

特征四:异步性

各进程以不可预知的速度向前推进。
你无法精确预测进程 A 的下一条指令在哪个时刻执行完毕——因为它随时可能被中断。
操作系统需要保证无论进程以何种顺序执行,最终结果都正确。
这正是后续要学习的"进程同步"要解决的问题。

特征五:结构性

每个进程内部都有清晰的结构:PCB + 程序段 + 数据段。
这使操作系统能够以统一的方式管理所有进程。

🔍 考点速记:动态性是最基本的特征。并发性是引入进程的目的。
独立性是资源分配角度的特征。这三点常常混在一起考查。


进程的一生 —— 五状态模型与转换规则

进程从创建到消亡,经历若干状态。
状态的转换规则是每年 408 考研的必考点,必须做到看图就能口述每条箭头。

三种基本状态

状态 含义 关键判断标准
运行态(Running) 进程正在 CPU 上执行 占有 CPU
就绪态(Ready) 万事俱备,只欠 CPU 具备运行条件,但没有 CPU
阻塞态(Blocked/Waiting) 等待某个事件发生 即使有 CPU 也不能运行

单 CPU 系统下,同一时刻最多只有一个进程处于运行态。
多 CPU 系统下,同一时刻最多有N 个(N 为 CPU 核心数)进程处于运行态。

[CHART] 进程五状态转换图:用五个节点(创建态、就绪态、运行态、阻塞态、终止态)和六条有向边表示状态转换,每条边标注转换条件(如"被调度"、“时间片到”、“等待I/O”、"I/O完成"等)。使用 Mermaid 流程图或 SVG 示意图。Alt text: 进程五状态模型状态转换图——展示了五种状态之间所有可能的转换路径及触发条件。

五种状态的完整模型

考研中需要掌握的完整五状态模型:
创建态、就绪态、运行态、阻塞态、终止态。

创建态(New/Created):进程正在被创建。
操作系统正在为它分配 PCB、分配内存、初始化资源。
此时进程还不具备运行条件。

终止态(Terminated):进程已经结束运行。
操作系统正在回收它的资源、撤销它的 PCB。

状态转换——六条关键箭头

下面这张表完整覆盖所有可能的转换。每条都要记住方向和触发条件。

转换 触发条件 408考查频率
创建态 → 就绪态 系统完成进程创建工作,进程被插入就绪队列
就绪态 → 运行态 进程被调度程序选中,获得 CPU 极高
运行态 → 就绪态 时间片用完,或被更高优先级的进程抢占 极高
运行态 → 阻塞态 进程请求系统资源(如 I/O)而得不到满足 极高
阻塞态 → 就绪态 等待的事件发生(I/O 完成、资源可用) 极高
运行态 → 终止态 进程执行完毕或调用 exit()

🔍 高频陷阱:没有"阻塞态 → 运行态"的直接转换!
阻塞态只能先转换为就绪态,再被调度为运行态。
这和"阻塞态 → 就绪态 → 运行态"两步走是很多同学的选择题失分点。

为什么没有"就绪态 → 阻塞态"?

[UNIQUE INSIGHT] 这个问题值得停下来想一想。就绪态意味着进程已经准备好运行,
它还没有获得 CPU,也就还没机会去请求 I/O。
所以"就绪态 → 阻塞态"这个转换在逻辑上不可能发生——
进程必须先在 CPU 上运行、发出系统调用(如 read()),才会因为等待数据而阻塞。
理解这条逻辑,你就不需要死记硬背状态转换表。

Linux 中的进程状态

Linux 内核在 task_structstate 字段中记录了进程状态。
与理论模型的对应关系如下:

Linux 状态常量 含义 对应理论状态
TASK_RUNNING 正在运行或可运行 运行态 + 就绪态
TASK_INTERRUPTIBLE 可中断睡眠 阻塞态(可被信号唤醒)
TASK_UNINTERRUPTIBLE 不可中断睡眠 阻塞态(不响应信号,常用于磁盘 I/O)
TASK_STOPPED 暂停执行 暂停状态
EXIT_ZOMBIE 已结束但 PCB 未回收 终止态(僵尸进程)

TASK_RUNNING 把运行态和就绪态合并了——Linux 的设计者认为,
在调度的视角里,运行和就绪都是"可被调度的状态"。
ps 命令看到 STAT 列显示 RSDZ,对应的就是这些状态。

C 代码观察进程状态转换

下面这段代码创建子进程,展示从运行态到阻塞态再到就绪态的完整过程:

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

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程:运行态 → 阻塞态 → 就绪态 → 运行态 → 终止态
        printf("[子进程 %d] 运行态: 正在执行\n", getpid());

        // 主动 sleep,子进程进入阻塞态(等待定时器事件)
        sleep(2);
        // 定时器事件发生 → 子进程回到就绪态 → 被调度后恢复运行态

        printf("[子进程 %d] 运行态: sleep 结束, 即将退出\n", getpid());
        // exit → 进入终止态
        return 0;
    } else {
        // 父进程:观察子进程状态
        printf("[父进程 %d] 创建子进程 %d 后继续执行\n",
               getpid(), pid);
        printf("[父进程 %d] 子进程此时可能正处于阻塞态(sleep中)\n",
               getpid());

        // 父进程等待子进程退出
        wait(NULL);
        printf("[父进程 %d] 子进程已终止(终止态), 父进程回收其PCB\n",
               getpid());
    }
    return 0;
}

这段代码追踪了一个进程完整的状态转换路径:
创建态(fork)→ 就绪态 → 运行态 → 阻塞态(sleep)→ 就绪态 → 运行态 → 终止态(exit)
实际的状态切换由调度器控制,在 sleep() 调用期间由内核自动管理。


操作系统如何组织成百上千个进程?

当系统中有几百个进程时,操作系统需要高效地找到"哪些进程处于就绪态"
“哪些进程正在等待磁盘 I/O”。组织方式主要有两种。

链接方式

为每个状态维护一个队列。PCB 中包含一个指针字段,
把同状态的 PCB 串联成链表。就绪队列、阻塞队列各自成链。

就绪队列: [PCB_A] → [PCB_B] → [PCB_C] → NULL
阻塞队列: [PCB_D] → [PCB_E] → NULL

优点是简单直观,缺点是遍历时需要逐个访问,效率为 O(n)。

索引方式

为每个状态建立一个索引表(本质是一个 PCB 指针数组)。
操作系统通过索引表直接定位到任意 PCB,效率更高。

就绪索引表: [0]→PCB_A  [1]→PCB_B  [2]→PCB_C
阻塞索引表: [0]→PCB_D  [1]→PCB_E

现代操作系统多数采用索引方式或其变体。Linux 使用红黑树组织就绪进程的调度实体,
复杂度为 O(log n),比两种基础方式都更高效。


原语如何控制进程?

进程的创建、终止、阻塞和唤醒不是普通的函数调用,
而是通过原语来实现的。

什么是原语?

原语是一段程序,其执行过程不允许被中断
它通过"关中断"指令开始、通过"开中断"指令结束。
关中断期间,即使外部设备发来中断信号,CPU 也不响应。

为什么需要关中断?试想:进程创建过程中,PCB 的数据结构正处于"半成品"状态。
如果此时发生时钟中断、调度器开始运行并访问了这个半成品 PCB,
后果是数据损坏甚至系统崩溃。原语的原子性就是针对这种场景设计的。

四个核心原语

原语 操作 关键步骤
进程创建 创建 → 就绪 分配 PID→分配 PCB→初始化→插入就绪队列
进程终止 运行 → 终止 回收资源→撤销 PCB→通知父进程
进程阻塞 运行 → 阻塞 保存现场→修改 PCB 状态→移入阻塞队列→触发调度
进程唤醒 阻塞 → 就绪 修改 PCB 状态→从阻塞队列移出→插入就绪队列

🔍 考点:阻塞和唤醒必须成对出现。如果一个进程阻塞了自己,
必须有另一个进程(通常是 I/O 中断处理程序)来唤醒它。
考题经常问"进程自己能否从阻塞态变为就绪态"——不能,必须由外部事件唤醒。

需要注意的是,进程切换也是一项通过原语实现的操作。
进程切换原语将当前运行进程的 CPU 上下文保存到其 PCB 中,
然后从就绪队列中选择下一个进程,加载其上下文。
整个过程在关中断条件下完成,确保现场保存与恢复的完整性。

进程上下文切换的开销

进程切换是有代价的。每次切换需要:

  1. 保存当前进程的寄存器、PC、栈指针到 PCB
  2. 刷新 TLB(Translation Lookaside Buffer,页表缓存)
  3. 加载下一个进程的上下文
  4. 可能导致 CPU 缓存失效

在 Linux 中,一次进程切换大约消耗 1-5 微秒(视硬件和缓存命中率而定)。
这个数字看似很小,但对每秒处理几十万请求的服务器而言,累积开销不可忽视。
这也是为什么引入了更轻量的线程——线程切换只需要保存少量寄存器,
不涉及地址空间切换。


线程——比进程更轻量的执行单元

引入线程之前的问题

在没有线程的年代,一个进程只能串行执行。
如果你想写一个同时处理用户输入和网络数据的程序,
只能靠多进程——开销很大。进程间通信(IPC)复杂,切换成本高。

线程的引入解决了这个问题:一个进程内部可以有多个线程并发执行。
比如 Chrome 浏览器的渲染进程里,有负责解析 HTML 的线程、
有负责下载图片的线程、有负责执行 JavaScript 的线程。
它们共享同一个地址空间,协作效率远高于多进程。

线程的定义

线程是程序执行的最小单位,是 CPU 调度的基本单位。
引入线程后,进程的地位发生了变化:进程仍然是资源分配的基本单位,
但不再是 CPU 调度的基本单位——CPU 调度的是线程。

一句话记住二者的角色变化:

引入线程前:进程 = 资源分配单位 + 调度单位。
引入线程后:进程 = 资源分配单位(除 CPU 外),线程 = CPU 调度单位。

一个经典的类比

进程就像一栋图书馆,线程就像图书馆里的阅览座位。

一个图书馆(进程)里可以有多个座位(线程)。
每个座位都可以有读者(执行指令),读者之间共享图书馆的藏书(进程资源)。
换一个座位远比换一栋图书馆容易——这对应了线程切换远快于进程切换。

[IMAGE] 图书馆类比示意图:一栋图书馆建筑标注为"进程"(拥有藏书、设施等资源),内部多个阅览座位标注为"线程"(共享资源、独立执行),箭头表示切换座位的便捷性。Alt text: 进程与线程的图书馆类比——进程是拥有资源的图书馆,线程是共享资源但独立执行的阅览座位。

线程的属性

属性 说明
轻型实体 线程基本不拥有系统资源(仅需少量必需的:PC、寄存器、栈)
独立调度的基本单位 CPU 切换的是线程,不是进程
可并发执行 同一进程内的多个线程可以并发运行
共享进程资源 线程共享进程的地址空间、文件描述符、信号处理等

线程的实现方式 🔍

线程的实现方式分为两大类,这是选择题和高频考点。

用户级线程(ULT,User-Level Thread):线程管理在用户空间完成,内核感知不到线程的存在。
线程切换不需要内核态切换,速度快。但缺点致命——如果一个线程阻塞(比如等待磁盘读取),
内核会把整个进程挂起,导致同一进程中的其他线程也无法运行。

内核级线程(KLT,Kernel-Level Thread):线程由内核管理,每个线程对内核可见。
一个线程阻塞不会影响其他线程。但线程切换需要从用户态陷入内核态,开销较大。

多线程模型

  • 多对一模型:多个用户线程映射到一个内核线程。一个线程阻塞,全局阻塞。
  • 一对一模型:每个用户线程对应一个内核线程。并发性好,但内核线程数量有限。
  • 多对多模型:M 个用户线程映射到 N 个内核线程(M >= N)。兼顾并发性和系统开销,最灵活。

Linux 的 pthread 采用的是一对一模型(每个 pthread 对应一个内核任务)。
在 Linux 中,线程本质上是一种特殊的进程——共享地址空间的"轻量级进程(LWP)"。
所以 Linux 调度器实际调度的对象始终是 task_struct
"进程"和"线程"的区别仅在于资源共享的程度。

C 代码演示多线程

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 全局变量——线程间共享(属于进程的数据段)
int shared_counter = 0;

void* thread_work(void* arg) {
    int id = *(int*)arg;
    for (int i = 0; i < 5; i++) {
        shared_counter++;  // 多个线程修改同一变量
        printf("[线程 %d] shared_counter = %d\n", id, shared_counter);
        usleep(100000);    // 0.1 秒,模拟工作负载
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    int id1 = 1, id2 = 2;

    // 创建两个线程——它们共享 shared_counter 和进程资源
    pthread_create(&t1, NULL, thread_work, &id1);
    pthread_create(&t2, NULL, thread_work, &id2);

    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("[主线程] 最终 shared_counter = %d\n", shared_counter);
    // 注意:由于没有同步机制(如互斥锁),最终结果可能不是 10
    // 这正是后续文章要讨论的"线程同步"问题
    return 0;
}

编译命令:gcc -pthread -o thread_demo thread_demo.c

关键观察:两个线程同时修改 shared_counter 时出现了竞态条件。
最终值可能不是 10——这与进程的"独立数据段"形成鲜明对比。
进程之间不会出现这种问题(各自数据隔离),但线程共享数据带来了协作效率,
也带来了同步的复杂性。这就是本系列第三篇文章《进程同步与互斥》要解决的核心问题。


进程 vs 线程 —— 一张表看清本质区别

维度 进程 线程
定义 资源分配的基本单位 CPU 调度的基本单位
拥有资源 独立的地址空间、文件描述符、PCB 仅拥有栈、寄存器、PC 等少量上下文
地址空间 不同进程地址空间隔离 同一进程的线程共享地址空间
通信方式 IPC(管道、消息队列、共享内存、信号) 直接读写共享变量(需同步保护)
切换开销 大(切换地址空间、保存/恢复全部上下文) 小(无需切换地址空间,仅保存少量寄存器)
创建开销 大(需分配 PCB、地址空间等全套资源) 小(仅分配栈和 TCB)
独立性 强(一个进程崩溃一般不波及其他进程) 弱(一个线程崩溃可能导致整个进程终止)
系统调用 fork()exec() pthread_create()pthread_join()
并发粒度

[CHART] 进程切换 vs 线程切换开销对比柱状图(使用 SVG 或 Mermaid):横轴列出"上下文保存"、“地址空间切换”、“TLB刷新”、"总开销"四项,每项两根柱子(进程切换/线程切换),展示线程切换在各维度上的开销优势。Alt text: 进程切换与线程切换开销的定量对比——线程切换在各项指标上均显著低于进程切换。


FAQ

进程创建后,为什么不是直接进入运行态,而是先进入就绪态?

进程创建完成时,CPU 可能正被其他进程占用。操作系统不会立即抢占当前运行进程——而是把新进程放入就绪队列,等待调度器的下一次选择。所以"创建态 → 就绪态"是标准流程,跳级进运行态在设计上不合理,会破坏调度的公平性。

阻塞态和就绪态的核心区别是什么?

就绪态进程"只缺 CPU"——给它 CPU 就能执行;阻塞态进程"缺的不是 CPU"——它等待的是某个外部事件(如磁盘数据就绪、用户键盘输入)。即使 CPU 空闲,阻塞态进程也不能运行。这是判断状态的唯一标准。

进程和线程到底怎么记才不会混淆?

记住一句话:进程管资源,线程管执行。 进程是操作系统中"资源的容器",拥有地址空间和文件描述符。线程是"在容器中干活的人",共享容器里的资源,但每个线程有自己的栈。创建进程 = 建一个新容器,创建线程 = 在当前容器里多派个人。

原语为什么一定要关中断?用锁不行吗?

锁只能保护用户空间的临界区,原语的代码在内核空间执行,涉及全局数据结构(就绪队列、阻塞队列、PCB 链表)的一致性。如果在修改就绪队列中途被调度器中断,调度器会看到一个损坏的队列——系统随后崩溃。关中断是内核中实现原子性最直接也最可靠的手段。

僵尸进程是什么?对系统有什么影响?

僵尸进程是已经终止(exit),但其 PCB 尚未被父进程回收的进程。它在 Linux 中处于 EXIT_ZOMBIE 状态。僵尸进程不占用内存和 CPU,但占用 PID 资源。如果大量僵尸进程堆积,可能导致系统无法分配新 PID。解决方案是父进程及时调用 wait()waitpid() 来回收。


总结与系列导航

进程是操作系统中最核心的抽象之一。
本文按照 408 考纲的顺序,从进程的概念、组成、特征,讲到状态转换、进程控制和线程引入。
理解这些基础概念后,后续三篇文章将逐层深入:

  • 《进程调度 —— 从 FCFS 到多级反馈队列,调度算法全景解析》
    覆盖调度层次、调度时机、经典调度算法、调度算法评价指标。

  • 《进程同步与互斥 —— 信号量、管程与 PV 操作实战》
    覆盖临界区问题、信号量机制、经典同步问题(生产者-消费者、读者-写者、哲学家就餐)。

  • 《死锁 —— 四个必要条件、银行家算法与死锁检测》
    覆盖死锁成因、预防与避免策略、银行家算法、死锁检测与恢复。

这四篇文章构成"进程与线程"的完整知识体系。


Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐