在这里插入图片描述
在这里插入图片描述

❤️@燃于AC之乐 来自重庆 计算机专业的一枚大学生
专注 C/C++ Linux 数据结构 算法竞赛 AI
🏞️志同道合的人会看见同一片风景!

👇点击进入作者专栏:

《算法画解》

《linux系统编程》

《C++》

🌟《算法画解》算法相关题目点击即可进入实操🌟
感兴趣的可以先收藏起来,请多多支持,还有大家有相关问题都可以给我留言咨询,希望希望共同交流心得,一起进步,你我陪伴,学习路上不孤单!

📖前言

在Linux系统编程中,进程管理是并发与并行的基石。进程的生命周期包含创建、执行、终止与回收,每个环节都直接影响程序的健壮性与系统效率。本文聚焦进程控制的三大核心:进程创建剖析fork的"分身"机制,进程终止详解程序退出与资源清理,进程等待解析僵尸进程防治与状态获取。通过代码示例与原理结合,助你深入理解父子进程协作,掌握编写稳定高效多进程程序的必备技能。无论你是系统编程新手还是经验开发者,本文都将为你提供实用的技术指南和最佳实践。

🏠 一、进程创建——fork函数解析

1.1 fork 函数基础

fork() 是 Linux 中创建新进程的核心函数,它会从现有进程(父进程)复制出一个新进程(子进程)。

#include <unistd.h>
pid_t fork(void);

返回值特性:

子进程返回 0, 父进程返回子进程的 PID,出错返回 -1。

1.2 fork 的工作原理

为什么有两个返回值? fork() 的特殊之处在于它"返回两次":

内核完成进程复制后,系统中存在两个几乎相同的进程,两个进程都从 fork() 调用处继续执行,内核通过设置不同的返回值来区分父子进程。
返回值设计的合理性 子进程返回 0:子进程可以轻松判断自己的身份(if (pid == 0))父进程返回子进程 PID:父进程需要管理子进程,必须知道其标识符,这种设计让两个进程都能获得必要的信息,且代码逻辑清晰。

内核执行步骤 分配新的内存块和内核数据结构给子进程, 复制父进程的数据结构内容至子进程,将子进程添加到系统进程列表,fork() 返回,调度器开始调度。

1.3 写时拷贝技术

关键技术特点:

父子进程代码共享,初始数据也共享。

当任一进程试图写入数据时,触发写时拷贝。

操作系统为写入方创建数据副本,实现进程隔离。

优势:

提高内存使用效率(延迟分配)。

保证进程独立性。

减少不必要的内存复制。

1.4 fork 的典型应用场景

任务并行处理:父进程创建子进程处理不同任务。

服务器编程:父进程监听连接,子进程处理请求。

程序替换:子进程调用 exec 执行新程序。

1.5 fork 失败原因

系统进程数达到上限。

用户进程数超过限制。

内存资源不足。

⏹️ 二、进程终止机制

2.1 进程退出场景

代码正常执行完毕,结果正确;代码正常执行完毕,结果错误;代码异常终止(如信号中断)

2.2 退出状态码

0:执行成功
非0:执行失败(具体数值表示错误类型)
使用 echo $? 查看上一个命令的退出码

2.3 退出函数对比

_exit() 函数:

#include <unistd.h>
void _exit(int status);

直接终止进程,立即进入内核,仅低8位状态码传递给父进程

exit() 函数:

#include <stdlib.h>
void exit(int status);

执行顺序:

1.调用用户注册的清理函数(atexit())
2.刷新所有I/O缓冲区
3.调用 _exit() 进入内核
4.return 退出main() 函数中的 return n 等价于 exit(n)
5.由运行时库处理返回值传递

在这里插入图片描述

🔍 三、进程等待的必要性

3.1 僵尸进程问题

子进程退出后,父进程不回收会产生僵尸进程

僵尸进程占用系统资源,导致内存泄漏

即使使用 kill -9 也无法清除僵尸进程

3.2 信息获取需求

父进程需要知道:

子进程是否正常结束;

子进程的退出状态;

子进程的执行结果。

✨四、进程等待方法

4.1 wait() 函数

#include <sys/wait.h>
pid_t wait(int *status);

阻塞等待任意子进程退出
通过 status 获取子进程退出信息

4.2 waitpid() 函数

pid_t waitpid(pid_t pid, int *status, int options);

参数说明:

pid:指定等待的进程ID(-1 表示任意子进程)

status:输出型参数,存储退出状态

options:等待选项(0为阻塞,WNOHANG为非阻塞)

4.3 状态信息解析

wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将自进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图:
status 参数按位图解析:

低7位:信号编号(异常终止时)

第8位:core dump 标志

高8位:退出状态码(正常终止时)

在这里插入图片描述

关键宏:

WIFEXITED(status):判断是否正常退出

WEXITSTATUS(status):提取退出码

WIFSIGNALED(status):判断是否信号终止

WTERMSIG(status):提取信号编号

4.4 阻塞与非阻塞等待

阻塞等待:
父进程暂停执行,直到子进程退出,简单易用,适合顺序执行场景。

阻塞等待方式:

int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
} else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(257);
} else{
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\n");
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is
:%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}

非阻塞等待:

使用 WNOHANG 选项,立即返回,不等待子进程,适合需要同时处理其他任务的场景,需要循环检查子进程状态。

进程的非阻塞等待方式:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
typedef void (*handler_t)(); // 函数指针类型
std::vector<handler_t> handlers; // 函数指针数组
void fun_one() {
printf("这是⼀个临时任务1\n");
}
void fun_two() {
printf("这是⼀个临时任务2\n");
}
void Load() {
handlers.push_back(fun_one);
handlers.push_back(fun_two);

}
void handler() {
if (handlers.empty())
Load();
for (auto iter : handlers)
iter();
}
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
printf("%s fork error\n", __FUNCTION__);
return 1;
} else if (pid == 0) { // child
printf("child is run, pid is : %d\n", getpid());
sleep(5);
exit(1);
} else {
int status = 0;
pid_t ret = 0;
do {
ret = waitpid(-1, &status, WNOHANG); // ⾮阻塞式等待
if (ret == 0) {
printf("child is running\n");
}
handler();
} while (ret == 0);
if (WIFEXITED(status) && ret == pid) {
printf("wait child 5s success, child return code is :%d.\n",
WEXITSTATUS(status));
} else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}

4.5 实践建议

1.及时回收子进程:避免僵尸进程积累;
2.合理选择等待方式:根据业务需求选择阻塞/非阻塞;
3.检查退出状态:不要忽略子进程的执行结果;
4.处理异常情况:考虑子进程异常终止的场景;
5.资源清理:确保子进程释放所有分配的资源。

通过合理使用 fork()、wait()/waitpid() 以及正确处理进程退出,可以构建稳定可靠的并发程序。理解进程创建、终止和等待的机制是 Linux 系统编程的重要基础。

在这里插入图片描述
加油!志同道合的人会看到同一片风景。
看到这里请点个赞关注,如果觉得有用就收藏一下吧。后续还会持续更新的。 创作不易,还请多多支持!

Logo

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

更多推荐