1. 引言

进程是 Linux 系统中最重要的抽象概念之一,也是操作系统课程与面试中的高频考点。本文系统梳理 Linux 进程的核心概念、生命周期、控制块、调度算法、进程间通信机制以及常用管理命令,并结合实例说明,帮助读者在复习时建立完整的知识框架。


2. 进程的定义与特性

2.1 什么是进程

进程是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。程序是静态的指令集合,而进程是动态的,包含程序代码、数据、堆栈以及执行状态等信息。

进程与程序的主要区别如下:

  • 动态性:进程有创建、运行、阻塞、终止等生命周期,程序是静态文件。
  • 并发性:多个进程可以同时存在于内存中并交替执行。
  • 独立性:每个进程拥有独立的地址空间和系统资源。
  • 结构性:进程由程序段、数据段和进程控制块(PCB)三部分组成。

2.2 进程与线程的关系

线程是进程内的执行单元,一个进程可以包含多个线程。进程是资源分配的基本单位,线程是 CPU 调度的基本单位。同一进程内的线程共享地址空间和资源,而进程之间相互独立。


3. 进程的生命周期

3.1 进程状态

Linux 进程在生命周期中会经历多种状态转换,主要包括:

  • 创建态(New):进程正在被创建,尚未就绪。
  • 就绪态(Ready):进程已具备运行条件,等待 CPU 调度。
  • 运行态(Running):进程正在 CPU 上执行。
  • 阻塞态(Blocked/Wait):进程因等待某事件(如 I/O 完成)而暂停执行。
  • 终止态(Terminated):进程执行完毕或被强制结束,等待系统回收资源。

3.2 状态转换过程

进程状态转换的核心路径如下:

  • 创建态 → 就绪态:进程创建完成后进入就绪队列。
  • 就绪态 → 运行态:调度器选中该进程,分配 CPU。
  • 运行态 → 就绪态:时间片用完或被更高优先级进程抢占。
  • 运行态 → 阻塞态:进程发起 I/O 请求或等待资源。
  • 阻塞态 → 就绪态:等待的事件完成,进程重新进入就绪队列。
  • 运行态 → 终止态:进程正常退出或被信号终止。

3.3 进程创建与终止

在 Linux 中,进程通过 fork() 系统调用创建子进程,子进程是父进程的副本。随后通常调用 exec() 系列函数加载新的程序映像。进程终止可通过 exit() 正常退出,或接收信号(如 SIGKILL)强制终止。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 PID: %d\n", getpid());
return 0;
} else if (pid > 0) {
// 父进程
wait(NULL);
printf("父进程等待子进程结束\n");
}
return 0;
}

4. 进程控制块(PCB)

4.1 PCB 的作用

进程控制块(Process Control Block,PCB)是操作系统用于描述和管理进程的数据结构,是进程存在的唯一标志。内核通过 PCB 感知进程的存在,并据此进行调度、资源分配和状态管理。

4.2 PCB 的核心内容

  • 进程标识符(PID):唯一标识进程的数字。
  • 进程状态:记录进程当前处于就绪、运行还是阻塞状态。
  • 程序计数器:保存下一条要执行指令的地址。
  • CPU 寄存器:保存进程切换时的现场信息。
  • 调度信息:优先级、时间片、调度队列指针等。
  • 内存管理信息:页表指针、代码段和数据段地址。
  • I/O 状态信息:进程打开的文件描述符、I/O 设备等。
  • 记账信息:CPU 使用时间、内存占用等统计信息。

4.3 Linux 中的 task_struct

在 Linux 内核中,PCB 对应 task_struct 结构体,定义在 include/linux/sched.h 中。每个进程对应一个 task_struct,内核通过双向链表管理所有进程。


5. 进程调度算法

5.1 先来先服务(FCFS)

FCFS 按进程到达就绪队列的顺序分配 CPU,实现简单但容易产生「长任务饥饿」问题,平均等待时间较长。

5.2 时间片轮转(RR)

RR 将 CPU 时间划分为固定时间片,按就绪队列顺序轮流分配。每个进程运行一个时间片后回到队尾,适合交互式系统,响应时间较短。

5.3 优先级调度

每个进程分配一个优先级,调度器优先选择优先级最高的进程运行。Linux 采用动态优先级机制,结合 nice 值和运行历史动态调整,避免低优先级进程长期得不到 CPU。

5.4 多级反馈队列

多级反馈队列将就绪队列分为多个优先级层级,新进程进入最高优先级队列,时间片用完后降入下一级。兼顾响应时间和吞吐量,是 Linux 实际采用的调度策略基础。

5.5 调度算法对比

算法优点缺点适用场景
FCFS实现简单、公平平均等待时间长批处理系统
RR响应快、公平时间片设置影响性能交互式系统
优先级调度灵活、可区分任务可能产生饥饿实时系统
多级反馈队列兼顾响应与吞吐参数调优复杂通用操作系统

6. 进程间通信机制

6.1 管道(Pipe)

管道是最基本的 IPC 方式,分为匿名管道和命名管道(FIFO)。匿名管道用于父子进程间通信,数据单向流动;命名管道可用于无亲缘关系的进程间通信。

# 匿名管道示例:统计文件行数
cat /etc/passwd | wc -l
命名管道示例
mkfifo mypipe
echo "hello" > mypipe &
cat mypipe

6.2 信号(Signal)

信号用于通知进程发生了异步事件,是一种软件中断机制。常见信号包括 SIGINT(Ctrl+C)、SIGKILL(强制终止)、SIGTERM(正常终止)等。

# 向进程发送信号
kill -9 1234        # 强制终止 PID 为 1234 的进程
kill -TERM 1234     # 发送 SIGTERM 信号

6.3 消息队列(Message Queue)

消息队列是内核维护的消息链表,进程可以向队列写入或读取消息,支持按类型读取,适合进程间传递结构化数据。

#include <sys/msg.h>
// 创建消息队列
int msgid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
// 发送消息
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
// 接收消息
msgrcv(msgid, &msg, sizeof(msg.mtext), 0, 0);

6.4 共享内存(Shared Memory)

共享内存是最快的 IPC 方式,多个进程直接映射同一块物理内存区域,无需内核参与数据拷贝。通常需要配合信号量实现同步互斥。

#include <sys/shm.h>
// 创建共享内存
int shmid = shmget(IPC_PRIVATE, 4096, 0666 | IPC_CREAT);
// 映射到进程地址空间
void *addr = shmat(shmid, NULL, 0);
// 解除映射
shmdt(addr);

6.5 信号量(Semaphore)

信号量用于进程间同步与互斥,本质是一个计数器,支持 P(wait)和 V(signal)操作。常用于保护共享资源,防止多个进程同时访问造成数据不一致。

6.6 IPC 方式对比

方式速度适用场景特点
管道较慢父子进程、简单数据流单向、字节流
信号异步事件通知信息量小、非可靠
消息队列中等结构化消息传递按类型读取、有界
共享内存最快大量数据交换需同步机制配合
信号量同步与互斥不传数据、只做控制

7. 进程管理命令

7.1 ps:查看进程快照

ps 命令用于显示当前系统的进程状态快照,常用组合参数如下:

# 查看当前用户所有进程
ps -u
查看所有进程的详细信息
ps -ef
查看进程树
ps -ejH
结合 grep 查找特定进程
ps -ef | grep nginx

7.2 top:动态监控进程

top 命令实时显示系统资源占用和进程动态信息,按 CPU 或内存使用率排序,适合持续监控系统负载。

# 启动 top 交互界面
top
按内存使用率排序
top -o %MEM
指定进程 PID 监控
top -p 1234

7.3 kill:终止进程

kill 命令用于向进程发送信号,默认发送 SIGTERM,常用信号如下:

# 正常终止进程
kill 1234
强制终止进程
kill -9 1234
查看所有可用信号
kill -l

7.4 pstree:查看进程树

pstree 以树状结构展示进程间的父子关系,便于理解进程的继承结构。

# 显示进程树
pstree
显示 PID 信息
pstree -p
指定用户进程树
pstree -u username

7.5 其他常用命令

  • pgrep:按名称查找进程 PID。
  • nice / renice:调整进程优先级。
  • jobs / bg / fg:管理后台任务。
  • free:查看内存使用情况。
  • uptime:查看系统负载。

8. 面试常考题

8.1 基础概念题

题目 1:进程和程序有什么区别?

参考答案:程序是静态的指令集合,存储在磁盘上;进程是程序的一次动态执行过程,包含代码、数据、堆栈和 PCB 等运行时信息。程序是持久的,进程有生命周期;一个程序可以对应多个进程实例。

题目 2:fork() 之后父子进程如何区分?

参考答案:fork() 在父进程中返回子进程的 PID,在子进程中返回 0。通过返回值判断当前处于哪个进程分支,返回值小于 0 表示创建失败。

8.2 原理分析题

题目 3:进程切换时操作系统需要保存哪些信息?

参考答案:需要保存程序计数器、CPU 寄存器、栈指针、进程状态、调度信息、内存管理信息等现场数据,这些信息保存在进程的 PCB 中,切换时恢复目标进程的现场。

题目 4:共享内存为什么需要配合信号量使用?

参考答案:共享内存允许多个进程直接访问同一块内存区域,但缺少同步机制会导致数据竞争。信号量提供互斥和同步控制,确保同一时刻只有一个进程写入,避免数据不一致。

题目 5:什么是僵尸进程?如何避免?

参考答案:子进程终止后,如果父进程未调用 wait() 回收其 PCB,子进程会进入僵尸状态。避免方法:父进程调用 wait()/waitpid() 回收子进程;或采用信号处理机制,在 SIGCHLD 信号中回收子进程。

8.3 实际操作题

题目 6:如何查看系统中占用 CPU 最高的前 5 个进程?

解题思路:使用 top 命令按 CPU 排序,或使用 ps -eo pid,comm,%cpu --sort=-%cpu | head -6 查看前 5 个进程。

题目 7:如何安全地终止一个失控进程?

解题思路:先用 ps -ef | grep 进程名 找到 PID,优先使用 kill PID 发送 SIGTERM 让进程自行清理;若无效再使用 kill -9 PID 强制终止。

题目 8:如何查看某个进程的父子关系?

解题思路:使用 pstree -p 查看完整进程树,或使用 ps -ef 查看 PPID 字段定位父进程 PID。


9. 总结

Linux 进程知识体系涵盖概念、生命周期、PCB、调度、IPC 和命令操作等多个层面。复习时建议先理解进程的本质和状态转换,再掌握 PCB 的结构与调度算法,最后通过实际操作命令加深记忆。面试中既要能讲清原理,也要能熟练演示命令操作,做到理论与实践结合。

Logo

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

更多推荐