本文为 Linux 软件编程学习第五天的笔记整理,系统梳理进程的核心概念、常用操作命令、进程调度与状态、核心 API 函数,并结合实战中遇到的循环 fork 进程爆炸、孤儿进程、kill 命令报错等真实踩坑场景,总结易错点与最佳实践,适合入门学习者参考。
文章目录
- 一、进程基础概念
- 1.1 进程与程序的区别
- 1.2 32 位系统进程虚拟地址空间
- 二、进程常用操作命令
- 2.1 进程查看命令
- 2.2 进程终止命令
- 2.3 前后台任务控制
- 2.4 进程优先级控制
- 三、进程调度与进程状态
- 3.1 进程调度基础
- 3.2 常见调度算法
- 3.3 进程七大状态
- 四、进程核心 API 函数详解
- 4.1 fork:创建子进程
- 4.2 getpid / getppid
- 4.3 exit:进程退出
- 4.4 wait:回收子进程
- 五、经典问题:僵尸进程与孤儿进程
- 5.1 僵尸进程
- 5.2 孤儿进程
- 六、实战踩坑与易错点总结
一、进程基础概念
1.1 进程与程序的区别
- 程序:存放在外存(磁盘)中的静态代码集合,是永久存在的文件,只占用磁盘存储空间。
- 进程:是程序的一次动态执行过程,包含创建、调度、消亡完整生命周期;是操作系统进行资源分配和调度的基本单位,占用内存、CPU、文件描述符等系统资源。
| 本质 | 静态二进制文件 | 动态执行实例 |
| 存储位置 | 磁盘外存 | 内存中运行 |
| 生命周期 | 永久存在 | 有创建、运行、退出的完整周期 |
| 资源占用 | 仅占用磁盘空间 | 占用内存、CPU、PID、文件描述符等系统资源 |
| 对应关系 | 一个程序可对应多个进程 | 一个进程对应一个运行的程序 |
1.2 32 位系统进程虚拟地址空间
每个进程都拥有独立的 0~4G 虚拟地址空间,由操作系统通过页表映射到物理内存,保证进程空间相互隔离。整体可划分为三大区域:
- 字符串常量
- 未初始化的全局变量、静态变量(BSS 段,程序加载时自动初始化为 0,不占用磁盘空间)
- 已初始化的全局变量、静态变量
- 堆区:动态内存分配区,malloc/free在此管理,地址由低向高增长,由程序员手动控制。
- 栈区:存放函数局部变量、函数参数、返回地址,地址由高向低增长,由系统自动分配回收。
核心特性:每个进程的地址空间完全独立,一个进程修改自己的内存不会影响其他进程,这也是 fork 后父子进程内存隔离的底层基础。
二、进程常用操作命令
2.1 进程查看命令
- top:动态查看进程信息,默认按 CPU 占用率排序,可实时查看 PID、CPU 使用率、内存占用等核心指标。
- ps -ef:查看系统全量进程信息,展示 PID、PPID、启动用户、启动时间、执行命令等信息。
- ps -aux:查看进程运行状态,包含 CPU、内存占用率、进程状态码、虚拟内存大小等详细数据。
- pstree:以树状结构展示进程间的父子关系,直观查看进程派生层级与归属。
2.2 进程终止命令
- kill -9 进程PID:强制杀死指定 PID 的进程,9 号信号SIGKILL无法被捕获和忽略,可强制终止顽固进程。
❗ 注意:-9为连续整体,中间不能加空格,否则会报invalid signal specification语法错误。
- killall -9 进程名:按进程名批量杀死所有同名进程,适合批量终止多实例程序。
2.3 前后台任务控制
- ./a.out &:让程序在后台运行,终端可继续输入其他命令,不阻塞当前终端操作。
- jobs:查看当前终端下所有后台任务,显示任务编号、运行状态与对应命令。
- fg 任务编号:将指定后台任务切换到前台运行。
2.4 进程优先级控制
- nice -n 优先级值 程序命令:启动程序时指定优先级,优先级值范围为 – 20~19,数值越小优先级越高。
- renice -n 优先级值 进程PID:修改已运行进程的优先级,动态调整进程调度权重。
三、进程调度与进程状态
3.1 进程调度基础
多个进程需要同时在系统中运行,操作系统通过调度算法从就绪队列中选择进程分配 CPU 时间片,实现宏观并行、微观串行的执行效果。
- 时间片:CPU 分配给单个进程运行的一小段时间,时间片用完后进程让出 CPU,重新进入就绪队列排队。
3.2 常见调度算法
3.3 进程七大状态
四、进程核心 API 函数详解
4.1 fork:创建子进程
函数原型:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
- 成功:父进程中返回子进程的 PID 号;子进程中返回 0
- 失败:返回 -1,并设置 errno
执行逻辑: 调用 fork 后,操作系统创建子进程,复制父进程的地址空间、文件描述符表等资源;fork 返回后,父子两个进程从 fork 的下一行代码开始并发执行,执行顺序由操作系统调度决定,无固定先后。
实战重点:循环创建 N 个子进程的标准写法
错误写法(高频踩坑):子进程分支不加 exit,会导致子进程继续执行循环,派生大量孙进程,进程数量呈2^n指数爆炸。
// 错误示例:子进程无exit,进程指数级增长
for(int i = 0; i < 8; i++){
pid_t pid = fork();
if(pid == 0){
printf("子进程%d\\n", i);
// 没有exit!子进程会继续循环,创建孙子进程
}
}
标准正确写法:子进程执行完业务逻辑后,立即调用exit(0)退出,保证只有父进程持续循环创建子进程。
// 正确模板:循环创建N个子进程
for(int i = 0; i < 8; i++){
pid_t pid = fork();
if(pid == -1){
perror("fork fail");
return -1;
}
if(pid == 0){ // 子进程分支
printf("子进程%d, PID:%d, PPID:%d\\n", i, getpid(), getppid());
exit(0); // 子进程直接退出,不再参与后续循环
}
// 父进程跳过if,继续下一轮循环
}
4.2 getpid / getppid
pid_t getpid(void); // 获取当前进程的PID号
pid_t getppid(void); // 获取当前进程父进程的PID号
4.3 exit:进程退出
函数原型:
#include <stdlib.h>
void exit(int status);
- 功能:终止当前进程,status 为进程退出状态码,可被父进程通过 wait 函数获取。
- 与 return 的区别:
- main 函数中调用exit和return效果基本一致,都会终止进程。
- 普通函数内return仅结束当前函数,exit会直接终止整个进程。
4.4 wait:回收子进程
函数原型:
#include <sys/wait.h>
pid_t wait(int *wstatus);
核心功能:
参数解析:wstatus为传出参数,保存子进程的退出状态,需通过专用宏解析:
| WIFEXITED(wstatus) | 判断子进程是否为正常退出 |
| WEXITSTATUS(wstatus) | 获取子进程正常退出的状态码,仅 WIFEXITED 为真时有效 |
| WIFSIGNALED(wstatus) | 判断子进程是否被信号杀死 |
| WTERMSIG(wstatus) | 获取杀死子进程的信号编号,仅 WIFSIGNALED 为真时有效 |
返回值:成功返回被回收子进程的 PID,失败返回 – 1。
五、经典问题:僵尸进程与孤儿进程
5.1 僵尸进程
产生原因:子进程先于父进程退出,父进程未调用wait/waitpid回收子进程资源,子进程的 PCB 残留在系统中,成为僵尸进程。 危害:不占用用户内存等运行资源,但会占用 PID 号;系统 PID 数量有上限,大量僵尸进程会导致无法创建新进程。
避免方法:
5.2 孤儿进程
产生原因:父进程先于子进程退出,子进程失去父进程,成为孤儿进程。 系统处理:Linux 系统中,孤儿进程会被 init(或 systemd)进程自动收养,子进程的 PPID 会变为 init 进程的 PID。
实战现象:父进程创建完子进程直接 return 退出,后续调度运行的子进程 PPID 不再是原父进程 PID,就是变成了孤儿进程。 注意:孤儿进程不会造成资源泄漏,退出后由 init 自动回收,不属于严重问题,但属于不规范的编程写法。
六、实战踩坑与易错点总结
以上就是系统编程第五天进程模块的全部核心知识点。进程是 Linux 系统编程的核心基础,其中 fork 机制、地址空间隔离、僵尸 / 孤儿进程是高频考点,实战中尤其要注意循环 fork 的 exit 写法和子进程回收,避免踩坑。


