Linux C并发编程基础(进程管理)
·
1.进程创建相关 API
进程创建的核心是 fork(),还有其变体 vfork(),以及用于加载新程序的 exec() 系列函数,共同完成「创建子进程 + 执行新程序」的完整流程。
1.1.fork():创建子进程(最常用)
- 函数原型:
pid_t fork(void);
- 功能:
- 从当前父进程中创建一个新的子进程,子进程是父进程的「副本」(基于写时复制 COW 机制)。
- 这是唯一返回两次的系统调用:
- 父进程中返回:返回值为子进程的 PID(大于 0 的整数),用于父进程管理子进程。
- 子进程中返回:返回值为 0,子进程可通过 getppid() 获取父进程 PID。
- 失败返回:返回 -1,并设置 errno(错误码),常见错误如 PID 耗尽、内存不足。
- 返回值:
- >0:父进程,返回子进程 PID,可通过 wait() / waitpid() 等待子进程终止
- 0:子进程,无实际返回值(仅标识),可执行父进程逻辑副本,或通过 exec() 加载新程序
- -1:创建失败,可通过 perror("fork failed") 打印错误信息
- 示例代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 1. 调用 fork() 创建子进程
pid_t pid = fork();
// 2. 处理返回值,区分父子进程
if (pid == -1) {
// 创建失败
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程执行逻辑
printf("【子进程】PID:%d,父进程 PID:%d\n", getpid(), getppid());
sleep(2); // 模拟子进程执行任务
printf("【子进程】执行完毕,即将退出\n");
exit(EXIT_SUCCESS); // 子进程正常终止
} else {
// 父进程执行逻辑
printf("【父进程】PID:%d,创建的子进程 PID:%d\n", getpid(), pid);
sleep(3); // 等待子进程执行完毕(简单方式,推荐用 wait())
printf("【父进程】执行完毕\n");
}
return 0;
}
- 编译运行结果:
gcc fork_demo.c -o fork_demo
./fork_demo
# 输出:
【父进程】PID:12345,创建的子进程 PID:12346
【子进程】PID:12346,父进程 PID:12345
【子进程】执行完毕,即将退出
【父进程】执行完毕
1.2.vfork():创建轻量级子进程(已逐步废弃)
- 函数原型:
pid_t vfork(void);
- 功能:
- 简化版 fork(),创建的子进程直接共享父进程的地址空间(无 COW 机制),且父进程会被阻塞,直到子进程调用 exec() 加载新程序或 exit() 终止。
- 优势:创建速度比 fork() 更快(无需复制 PCB 部分资源)。
- 缺点:安全性极低,子进程修改数据会直接破坏父进程数据,容易引发程序异常,目前已被 COW 优化的 fork() 替代,仅用于兼容老旧代码。
- 注意:
- 子进程必须调用 exec() 或 exit(),否则会导致父进程无法继续执行。
- 禁止子进程修改父进程的全局变量、堆 / 栈数据。
1.3.exec() 系列:子进程加载新程序(替换地址空间)
exec() 不是单个 API,而是一组函数(共 6 个),核心功能是替换子进程的当前地址空间(代码段、数据段、堆、栈),加载并执行新的可执行文件,执行成功后子进程的 PID 保持不变,仅替换内部执行逻辑。
- 函数原型(6 个变体):
#include <unistd.h> // 必需头文件
// 1. execl():参数列表以可变参数传入,以 NULL 结尾
int execl(const char *path, const char *arg, ... /*, (char *) NULL */);
// 2. execlp():自动从 PATH 环境变量查找可执行文件,参数列表以 NULL 结尾
int execlp(const char *file, const char *arg, ... /*, (char *) NULL */);
// 3. execle():指定环境变量数组,参数列表以 NULL 结尾
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
// 4. execv():参数列表以字符串数组传入
int execv(const char *path, char *const argv[]);
// 5. execvp():自动从 PATH 环境变量查找,参数列表以字符串数组传入
int execvp(const char *file, char *const argv[]);
// 6. execve():系统调用原语(其他 5 个都是基于它的封装),指定环境变量数组
int execve(const char *path, char *const argv[], char *const envp[]);
- 核心共性说明:
- 成功返回:无返回值(因为子进程地址空间已被替换,原代码逻辑不再执行)。
- 失败返回:返回 -1,并设置 errno(如文件不存在、权限不足)。
- 关键参数:
- path:可执行文件的绝对路径 / 相对路径(如 /bin/ls)。
- file:可执行文件名(如 ls),execlp()/execvp() 会从 PATH 环境变量中查找。
- arg/argv:程序运行参数,第一个参数必须是程序名本身,最后以 NULL 结尾。
- 常用示例:execlp() 执行 ls -l 命令
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程:调用 execlp() 执行 ls -l 命令
printf("【子进程】即将执行 ls -l 命令\n");
int ret = execlp("ls", "ls", "-l", NULL); // 从 PATH 查找 ls,参数以 NULL 结尾
// 若 execlp 执行成功,以下代码不会执行
perror("execlp failed"); // 仅失败时执行
exit(EXIT_FAILURE);
} else {
// 父进程
printf("【父进程】等待子进程执行 ls 命令\n");
sleep(2);
printf("【父进程】子进程执行完毕\n");
}
return 0;
}
- 编译运行结果:
gcc exec_demo.c -o exec_demo
./exec_demo
# 输出:
【父进程】等待子进程执行 ls 命令
【子进程】即将执行 ls -l 命令
total 24
-rwxr-xr-x 1 root root 8960 Oct 20 10:00 exec_demo
-rw-r--r-- 1 root root 582 Oct 20 09:58 exec_demo.c
...(其他 ls -l 输出内容)
【父进程】子进程执行完毕
2.进程等待相关 API
用于父进程等待子进程终止,回收子进程资源(避免僵尸进程),同时获取子进程的退出状态,核心是 wait() 和 waitpid()。
2.1.wait():等待任意子进程终止(阻塞式)
- 函数原型:
pid_t wait(int *wstatus);
- 功能:
- 父进程调用 wait() 后会阻塞,直到任意一个子进程终止,然后回收该子进程的资源。
- 若父进程已有子进程终止(处于僵尸态),wait() 会立即返回,回收该子进程资源。
- 关键参数:
- wstatus:输出参数,用于存储子进程的退出状态(如退出码、终止信号),可传入 NULL 表示不关心退出状态。
- 返回值:
- 成功:返回终止子进程的 PID。
- 失败:返回 -1(如无子进程,设置 errno)。
- 退出状态解析函数(配合 wstatus 使用)
wstatus 存储的状态无法直接读取,需通过以下宏函数解析:
- WIFEXITED(wstatus):判断子进程是否正常终止(如 exit()、return),是则返回非 0,否则返回 0
- WEXITSTATUS(wstatus):若 WIFEXITED为真,返回子进程的退出码(即 exit(n)中的 n,取值 0~255)
- WIFSIGNALED(wstatus):判断子进程是否被信号终止(如 kill -9),是则返回非 0,否则返回 0
- WTERMSIG(wstatus):若 WIFSIGNALED为真,返回终止子进程的信号编号
- 示例:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程:正常退出,退出码为 10
printf("【子进程】PID:%d,即将正常退出,退出码 10\n", getpid());
exit(10);
} else {
// 父进程:调用 wait() 等待子进程终止
int wstatus;
pid_t ret_pid = wait(&wstatus); // 阻塞等待,获取退出状态
if (ret_pid == -1) {
perror("wait failed");
exit(EXIT_FAILURE);
}
// 解析退出状态
printf("【父进程】回收子进程 PID:%d\n", ret_pid);
if (WIFEXITED(wstatus)) {
printf("【父进程】子进程正常终止,退出码:%d\n", WEXITSTATUS(wstatus));
} else if (WIFSIGNALED(wstatus)) {
printf("【父进程】子进程被信号终止,信号编号:%d\n", WTERMSIG(wstatus));
}
}
return 0;
}
- 编译运行结果:
gcc wait_demo.c -o wait_demo
./wait_demo
# 输出:
【子进程】PID:12347,即将正常退出,退出码 10
【父进程】回收子进程 PID:12347
【父进程】子进程正常终止,退出码:10
2.2.waitpid():等待指定子进程终止(灵活可控)
- 函数原型:
pid_t waitpid(pid_t pid, int *wstatus, int options);
- 功能:比 wait() 更灵活,支持等待指定 PID 的子进程、非阻塞等待,是生产环境中的首选。
- 关键参数:
- pid:指定等待的子进程 PID,取值含义:
- >0:等待 PID 等于该值的子进程(精准等待指定子进程)
- -1:等待任意子进程(与 wait() 功能一致)
- 0:等待与父进程同一进程组的所有子进程
- < -1:等待进程组 ID 等于 pid 绝对值的所有子进程
- wstatus:与 wait() 一致,存储子进程退出状态,可传入 NULL。
- options:可选参数(可通过 | 组合),核心取值:
- WNOHANG 非阻塞模式:若没有子进程终止,立即返回 0,不阻塞父进程
- WUNTRACED 等待子进程暂停(状态为 T),并返回其状态
- WCONTINUED 等待被暂停的子进程恢复运行,并返回其状态
- pid:指定等待的子进程 PID,取值含义:
- 返回值:
- 成功:返回终止 / 暂停子进程的 PID;若 WNOHANG 模式下无子进程终止,返回 0。
- 失败:返回 -1,并设置 errno。
- 示例代码:非阻塞等待指定子进程
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程:睡眠 3 秒后正常退出
printf("【子进程】PID:%d,睡眠 3 秒后退出\n", getpid());
sleep(3);
exit(20);
} else {
// 父进程:非阻塞等待指定子进程
int wstatus;
while (1) {
pid_t ret_pid = waitpid(pid, &wstatus, WNOHANG); // 非阻塞模式
if (ret_pid == -1) {
perror("waitpid failed");
exit(EXIT_FAILURE);
} else if (ret_pid == 0) {
// 子进程尚未终止,继续轮询
printf("【父进程】子进程尚未终止,继续等待...\n");
sleep(1);
} else {
// 成功回收子进程
printf("【父进程】成功回收子进程 PID:%d\n", ret_pid);
if (WIFEXITED(wstatus)) {
printf("【父进程】子进程退出码:%d\n", WEXITSTATUS(wstatus));
}
break;
}
}
}
return 0;
}
- 编译运行结果:
gcc waitpid_demo.c -o waitpid_demo
./waitpid_demo
# 输出:
【父进程】子进程尚未终止,继续等待...
【子进程】PID:12348,睡眠 3 秒后退出
【父进程】子进程尚未终止,继续等待...
【父进程】子进程尚未终止,继续等待...
【父进程】成功回收子进程 PID:12348
【父进程】子进程退出码:20
3.进程终止相关 API
用于进程主动终止运行,释放资源,核心是 exit()(用户态,带清理操作)和 _exit()(内核态,直接终止)。
3.1.exit():用户态正常终止(带资源清理)
- 函数原型:
void exit(int status);
- 功能:进程主动正常终止,执行用户态清理操作后,调用内核态 _exit() 终止进程:
- 刷新并关闭所有打开的标准 I/O 流(如 stdout、stderr),写入缓冲区数据。
- 调用通过 atexit() 或 on_exit() 注册的退出处理函数。
- 释放进程占用的用户态资源(堆、栈、全局变量等)。
- 调用 _exit() 进入内核态,回收内核资源(PCB、文件描述符等),并通知父进程。
- 参数:
- status:进程退出码(0 表示正常,非 0 表示异常),父进程可通过 WEXITSTATUS() 获取。
3.2._exit():内核态直接终止(无清理操作)
- 函数原型:
void _exit(int status);
- 功能说明:
- 直接进入内核态终止进程,不执行任何用户态清理操作,仅回收内核资源。
- 适用于子进程中 exec() 执行失败后,或无需清理缓冲区的场景,避免数据冗余。
- exit() 与 _exit() 的核心区别:
- 所属层级:exit()是用户态(C 标准库封装),_exit()是内核态(系统调用原语)
- 清理操作:exit()刷新 I/O 缓冲区、调用退出处理函数,_exit()无任何用户态清理操作
- 适用场景:exit()适用于普通进程正常终止,_exit()适用于子进程、无需清理缓冲区的场景
- 示例代码:对比缓冲区刷新差异
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main() {
printf("测试 exit() 与 _exit() 的缓冲区差异"); // 无 \n,数据留在缓冲区
// 注释下方一行,切换 exit() 和 _exit() 查看差异
exit(0); // 会刷新缓冲区,输出上述字符串
// _exit(0); // 不刷新缓冲区,无上述字符串输出
return 0;
}
4.其他常用进程管理 API
4.1.getpid()/getppid():获取进程 PID / 父进程 PID
pid_t getpid(void); // 返回当前进程的 PIDpid_t getppid(void); // 返回当前进程的父进程 PID
4.2.kill():向进程发送信号(终止 / 暂停 / 重启等)
int kill(pid_t pid, int sig);
- 功能:向指定 PID 的进程发送 sig 信号(如 SIGKILL(9)强制终止,SIGTERM(15)正常终止)。
- 示例:kill(pid, 9);强制终止 PID 为 pid 的进程。
5.核心总结
- 进程创建:核心 fork()(COW 机制高效复制)+ exec() 系列(加载新程序),fork() 唯一返回两次,exec() 成功无返回。
- 进程等待:wait() 阻塞等待任意子进程,waitpid() 支持指定子进程和非阻塞模式,是避免僵尸进程的关键,需配合退出状态宏解析。
- 进程终止:exit()(用户态带清理)推荐优先使用,_exit()(内核态直接终止)适用于特殊场景。
- 核心流程:fork() 创建 → exec() 加载新程序 → exit() 终止 → 父进程 waitpid() 回收,构成 Linux 进程管理的完整闭环。
- 这些 API 是 Linux 后端开发、运维工具开发的基础,掌握后可实现自定义进程管理逻辑(如守护进程、多进程服务)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)