一、基础认知

1. 系统编程三阶段定位

进程控制承上启下,是系统编程的核心地基,系统编程学习分为三个阶段:

  1. 应用层接口:标准 IO、文件 IO 基础操作
  2. 进程与线程:系统资源调度的最小单位,进程间通信
  3. 网络通信:基于 socket 的 TCP/IP 内核间网络交互

2. 进程核心定义

进程是运行中的程序,是从创建到消亡的完整动态过程,核心由两部分组成:

  • 执行程序:待运行的代码逻辑
  • 运行数据:分布在四大内存区域(常量区、静态区、栈区、堆区)

3. 进程与程序的核心区别

维度 进程 程序
本质 动态运行实体,有完整生命周期 静态磁盘文件,持久化存储
存储 运行在系统内存,占用 CPU / 内存资源 保存在磁盘,占用磁盘存储空间
对应关系 一个进程仅对应一个程序 一个程序可对应多个进程(运行多次创建多个进程)

二、进程五大核心特性

  1. 动态性:进程有完整的生命周期(创建→运行→消亡),一次程序运行对应一个独立进程。
  2. 并发性:同一时间段内,多个进程宏观上同时运行,微观上 CPU 在多进程间快速切换时间片;需区分并行:同一时刻,多核 CPU 上多个进程真正同时运行。
  3. 独立性:进程是 Linux 系统资源分配与调度的最小单位;每个进程有唯一 PID(类型为 pid_t,无符号整数),同一次开机 PID 不重复,重启后重置。
  4. 异步性:进程间执行相互制约,执行顺序不可预知,可通过 sleep 等函数控制执行时序。
  5. 结构性:进程由「PCB(进程控制块)+ 程序 + 数据」组成;PCB 是系统感知进程存在的唯一标志,用于记录进程状态、指令地址、IO 设备信息等,是系统管理进程的核心结构体。

三、进程核心状态与切换规则

1. 三大核心状态

  • 就绪态:已获取除 CPU 外的所有资源,等待 CPU 调度即可执行。
  • 执行态:获得 CPU 使用权,代码正在 CPU 上执行。
  • 阻塞态:因等待某事件(IO 完成、sleep 延时、信号等),主动放弃 CPU,进入挂起状态。

2. 状态切换铁律与合法路径

核心铁律:阻塞态只能先切换到就绪态,不可直接进入执行态。合法切换路径:

  1. 就绪态 → 执行态:进程抢到 CPU 时间片
  2. 执行态 → 就绪态:进程时间片用完,被系统剥夺 CPU 使用权
  3. 执行态 → 阻塞态:进程等待事件,主动放弃 CPU
  4. 阻塞态 → 就绪态:进程等待的事件已完成,解除阻塞

四、进程管理核心命令

  1. ps 进程查看命令
    • ps -aux:查看系统所有进程的详细状态
    • ps -ef:查看进程的父子进程关系(PID/PPID)
    • STAT 状态标记速记:R (运行)、S (阻塞)、T (暂停)、Z (僵尸)、I (多线程)、+(后台进程组)
  2. kill 进程终止命令
    • 核心用法:kill -9 PID-9为强制终止信号,进程不可忽略,是最稳妥的杀进程方式
  3. 终端快捷键
    • ctrl+c:终止当前前台进程
    • ctrl+z:暂停当前前台进程
  4. 暂停进程恢复
    • fg:恢复最近暂停的进程
    • jobs:查看所有暂停进程编号;fg 编号:恢复指定暂停进程

五、进程核心名词解析

  1. 父子进程:进程间创建与被创建的关系,创建者为父进程,被创建者为子进程;子进程结束需父进程回收资源,否则产生僵尸进程。
  2. 祖先进程:PID=1 的 init/systemd 进程,由 0 号系统进程创建,是所有用户态进程的根进程,负责收养孤儿进程并回收资源。
  3. 守护进程:又称精灵进程,是后台运行、独立于控制终端,周期性执行任务 / 等待事件的特殊进程。
  4. 僵尸进程:子进程已终止,但父进程未调用 wait/waitpid 回收其资源,已消亡却仍占用系统资源的进程;危害:占用 PID 与内存,大量僵尸进程会导致系统无法创建新进程。
  5. 孤儿进程:子进程未结束,父进程提前终止,失去父进程的子进程;会被 1 号祖先进程收养,最终由 1 号进程回收资源,无系统危害。

六、进程控制核心函数

1. 获取进程 ID

#include <unistd.h>
pid_t getpid(void);  // 获取当前进程PID
pid_t getppid(void); // 获取当前进程的父进程PID

要点:无入参,返回值为对应进程号,无失败返回。

2. 运行进程 system ()
#include <stdlib.h>
int system(const char *string);
  • 核心功能:创建子进程执行 string 指定的 shell 命令 / 可执行程序,原进程阻塞等待子进程执行完毕。
  • 要点:入参为待执行命令字符串;成功返回 0,失败返回非 0 错误码;常用于调用外部程序。
3. 进程替换 exec 函数族

核心常用函数:

#include <unistd.h>
// 需指定程序完整路径
int execl(const char *path, char *arg, ..., NULL);
// 自动从PATH环境变量查找程序,无需完整路径
int execlp(const char *file, char *arg, ..., NULL);
  • 核心功能:用指定程序完全替换当前进程,进程 PID 不变,替换成功后,后续代码永不执行。
  • 要点:可变参数最后必须以 NULL 结尾;成功无返回,失败返回 - 1;与 system 核心区别:不创建新进程,直接替换原进程。
4.创建进程 fork ()
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
  • 核心特性:一次调用,两次返回,调用成功后生成父、子两个独立进程。
  • 返回值规则(必记):
    • 父进程中:返回新创建子进程的 PID(>0 的整数)
    • 子进程中:返回 0
    • 创建失败:返回 - 1
  • 核心规则:
    1. 子进程复制父进程的全部用户空间(堆、栈、常量区、静态区、IO 缓冲区),父子进程内存空间完全独立,修改互不影响。
    2. 子进程仅从 fork () 之后的代码开始执行,fork 前的代码不会重复执行。
    3. 父进程通常先执行,子进程创建完成后才会争抢 CPU 时间片。
5. 进程销毁 exit () /_exit ()
#include <stdlib.h>
void exit(int status); // 标准退出
#include <unistd.h>
void _exit(int status); // 系统调用退出
  • 核心功能:终止当前进程,status 为退出状态码(0 = 正常退出,非 0 = 异常退出)。
  • 核心区别(必记):
    • exit():刷新 IO 缓冲区,关闭所有打开的文件,再终止进程。
    • _exit():直接终止进程,不刷新缓冲区,不处理 IO 操作。
  • 补充:exit () 无论在哪个函数调用都会终止整个进程;return 仅在 main 函数中才会终止进程,普通函数中仅退出当前函数。

6. 进程等待 wait () /waitpid ()

核心作用:父进程等待子进程终止,回收子进程资源,从根源避免僵尸进程

wait()
#include <sys/wait.h>
pid_t wait(int *status);
  • 功能:阻塞父进程,直到任意一个子进程终止,回收其资源。
  • 参数:status 接收子进程退出状态,不关心可填 NULL。
  • 返回值:成功返回终止的子进程 PID,失败返回 - 1。
waitpid()
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

  • 功能:可指定等待的子进程,支持阻塞 / 非阻塞模式,灵活性高于 wait ()。
  • 参数要点:
    • pid:-1 = 等待任意子进程(效果同 wait);>0 = 等待指定 PID 的子进程
    • status:接收子进程退出状态,不关心填 NULL
    • options:0 = 阻塞等待;WNOHANG = 非阻塞等待
  • 返回值:成功返回终止的子进程 PID;失败返回 - 1;非阻塞模式下无已终止子进程返回 0。

七、僵尸进程核心处理

  • 产生条件:子进程终止,父进程存活且未回收子进程资源。
  • 规避方案:
    1. 父进程调用 wait ()/waitpid () 等待子进程终止,回收资源(最常用)。
    2. 父进程提前退出,使子进程成为孤儿进程,由 1 号进程回收资源。
    3. 通过信号机制,子进程终止时触发信号,父进程在信号处理函数中回收资源。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐