【Linux探索学习】第十八弹——进程等待:深入解析操作系统中的进程等待机制

news/2024/12/3 5:19:11/

Linux学习笔记:https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482

前言:

在Linux操作系统中,进程是资源的管理和执行单元,每个进程都有其自己的生命周期。在进程的执行过程中,进程可能需要等待一些资源或事件的发生,例如等待I/O操作完成、等待信号、等待其他进程的结束等,这些都叫做进程等待。这些我们在前面讲进程状态的时候基本上都提到过,今天我们重点来讲解父进程等待子进程这类等待其它进程结束的问题

目录

1. 父进程为什么要等待子进程

2. 父进程等待子进程的常用函数

3. wait() 和 waitpid() 函数详解

3.1 wait()

3.2 waitpid()

4. 使用 SIGCHLD 信号等待子进程

5. 僵尸进程与避免方法

6. 总结


1. 父进程为什么要等待子进程

在前面上篇我们已经讲过僵尸状态的问题,子进程在执行结束后,如果父进程不及时进行接受处理,子进程就会进入僵尸状态,进入僵尸状态后,从而造成内存泄漏,而且kill -9信号也不能进行处理,同时我们的父进程也需要通过进程退出的方式来回收子进程资源,获取子进程退出信息,所以说进程等待十分有必要。


2. 父进程等待子进程的常用函数

Linux 提供了多个函数用于父进程等待子进程的结束:

函数名描述
wait()阻塞父进程,直到任一子进程退出,返回退出的子进程 PID。
waitpid()更灵活的等待函数,可选择等待特定子进程,支持非阻塞模式。
waitid()类似于 waitpid(),但功能更强大,支持更详细的选项。
signal()sigaction()注册 SIGCHLD 信号处理函数,用于非阻塞地获取子进程状态。

这四个函数中我们主要用到的是前两个函数,所以我们下面对前两个函数进行详细讲解


3. wait()waitpid() 函数详解

3.1 wait()

wait() 是最简单的等待子进程的函数,用法如下:

#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 == 0) {// 子进程printf("Child process running...\n");sleep(3);  // 模拟一些操作printf("Child process exiting...\n");exit(42);  // 退出码为 42} else {// 父进程int status;pid_t child_pid = wait(&status);  // 等待任意子进程结束if (WIFEXITED(status)) {  // 检查子进程是否正常退出printf("Child process %d exited with status %d\n", child_pid, WEXITSTATUS(status));}}return 0;
}

输出示例:

Child process running...
Child process exiting...
Child process 12345 exited with status 42

说明:

  • wait(&status) 阻塞父进程,直到有子进程退出。
  • 使用宏 WIFEXITEDWEXITSTATUS 分别检查子进程是否正常退出及其退出码。

补充:

  • 上面的例子中子进程只有一个,但有些时候我们可能有多个子进程,这个时候系统采用的是循环等待的方法来回收每一个子进程
  • 父进程在回收子进程时是随机的,也就是说当我们有多个子进程执行结束的时候,父进程先回收哪个子进程并不是确定的,是随机的,这也就是我们上面采用循环等待的原因
  • 循环等待的具体方法会在文章最后面的总结图里面给出示例

3.2 waitpid()

waitpid() 是更灵活的等待函数,支持:

  • 等待特定的子进程。
  • 非阻塞模式。

函数原型:

pid_t waitpid(pid_t pid, int *status, int options);
参数描述
pid

指定子进程的 PID,若为 -1 等待任意子进程,与pid等效;若大于1则等待其进程ID与pid相同的子进程

status存储子进程的退出状态。
options控制等待行为(如 WNOHANG 表示非阻塞)。

我们先对上面的表格做一个小补充:

参数option作用是控制等待行为,常见的等待方式主要有两种:阻塞等待和非阻塞等待

阻塞等待的意思就是在我们父进程等待子进程的过程中会进入阻塞状态,不会做其它的事情,直到子进程运行结束后再继续,而非阻塞等待则是不同的方式,非阻塞状态的父进程会在运行的过程中不断询问查看子进程的运行情况,当子进程运行结束时,会将结果反馈给父进程,但是在这个过程中父进程并不会停下来,它还会继续自己的执行

示例代码:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {pid_t pid1 = fork();if (pid1 == 0) {// 第一个子进程printf("Child 1 running...\n");sleep(2);printf("Child 1 exiting...\n");exit(1);}pid_t pid2 = fork();if (pid2 == 0) {// 第二个子进程printf("Child 2 running...\n");sleep(4);printf("Child 2 exiting...\n");exit(2);}// 父进程int status;pid_t child_pid;while ((child_pid = waitpid(-1, &status, 0)) > 0) {  // 等待所有子进程if (WIFEXITED(status)) {printf("Child %d exited with status %d\n", child_pid, WEXITSTATUS(status));}}return 0;
}

输出示例:

Child 1 running...
Child 2 running...
Child 1 exiting...
Child 12345 exited with status 1
Child 2 exiting...
Child 12346 exited with status 2


4. 使用 SIGCHLD 信号等待子进程

信号的知识我们在前面还没进行讲解,这里还是了解为主,感兴趣的可以看看,不懂的地方可以去搜一下:

SIGCHLD 信号在子进程状态发生变化时(如退出)发送给父进程。父进程可以注册信号处理函数来处理此信号。

代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>void sigchld_handler(int sig) {int status;pid_t pid = wait(&status);  // 获取退出的子进程信息if (pid > 0 && WIFEXITED(status)) {printf("Child %d exited with status %d\n", pid, WEXITSTATUS(status));}
}int main() {signal(SIGCHLD, sigchld_handler);  // 注册信号处理器pid_t pid = fork();if (pid == 0) {// 子进程printf("Child process running...\n");sleep(3);printf("Child process exiting...\n");exit(42);}// 父进程printf("Parent process doing other work...\n");while (1) {  // 模拟父进程的其他工作sleep(1);}return 0;
}

输出示例:

Parent process doing other work...
Child process running...
Child process exiting...
Child 12345 exited with status 42


5. 僵尸进程与避免方法

文章开头我们就已经讲过僵尸进程了,通过上面对进程等待的学习,再来看一下僵尸进程的概念,看看能不能加深理解

僵尸进程(Zombie Process) 是指子进程退出后,其退出信息尚未被父进程读取的状态。虽然僵尸进程不会占用CPU,但其占用的进程表项资源有限。

避免僵尸进程的方法:

  1. 确保父进程读取子进程状态:使用 wait()waitpid()
  2. 忽略 SIGCHLD 信号:通过 signal(SIGCHLD, SIG_IGN) 忽略信号。
  3. 使用守护进程(init 进程回收子进程):如果父进程终止,init 进程会自动接管并回收子进程。

6. 总结

父进程等待子进程是进程管理中的关键机制。在实际应用中:

  • 简单的任务可以使用 wait()
  • 更复杂的需求(如非阻塞、多子进程等待)推荐使用 waitpid()
  • 实时应用可以结合 SIGCHLD 信号处理。

合理地使用这些机制,不仅可以有效管理资源,还能避免僵尸进程的问题,提升程序的健壮性和运行效率。

本篇笔记:


感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!


http://www.ppmy.cn/news/1551917.html

相关文章

C++关于二叉树的具体实现

目录 1.二叉树的结构 2.创建一棵二叉树 3.二叉树的先序遍历 1.借助栈的先序遍历 2.利用递归的先序遍历 4.二叉树的中序遍历 5.二叉树的后序遍历 1.借助栈的后序遍历 2.利用递归的后序遍历 6.二叉树的层序遍历 7.tree.h 8.tree.cpp 9.main.cpp 1.二叉树的结构 对于…

为什么redis用跳表不用b+树,而mysql用b+树而不是跳表?

写在前面 上一篇文章中&#xff0c;我们深度解析了redis中的跳表结构&#xff0c;而b树的结构我们很久之前就讲过了&#xff0c;那么我们知道了redis的有序集合用的是跳表&#xff0c;而mysql的innodb引擎用的是b树存储&#xff0c;但这是为什么呢&#xff1f;为什么redis用跳…

【新人系列】Python 入门(十四):文件操作

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

HCIE IGP双栈综合实验

实验拓扑 实验需求及解法 本实验模拟ISP网络结构&#xff0c;R1/2组成国家骨干网&#xff0c;R3/4组成省级网络&#xff0c;R5/6/7组成数据中 心网络。 配置所有ipv4地址&#xff0c;请自行测试直连。 R1 sysname R1 interface GigabitEthernet0/0/0ip address 12.1.1.1 255.…

Flink 离线计算

文章目录 一、样例一&#xff1a;读 csv 文件生成 csv 文件二、样例二&#xff1a;读 starrocks 写 starrocks三、样例三&#xff1a;DataSet、Table Sql 处理后写入 StarRocks四、遇到的坑 <dependency><groupId>org.apache.flink</groupId><artifactId&…

C++11新增特性2

一.lambda 1.本质&#xff1a;lambda对象是⼀个匿名函数对象&#xff0c;它可以定义在函数内部。 注&#xff1a;lambda表达式语法使⽤层⽽⾔没有类型&#xff0c;所以我们⼀般是⽤auto或者模板参数定义的对象去接收lambda对象。 2.表达式&#xff1a;[capture-list] (param…

跨平台应用开发框架(3)-----Qt(样式篇)

目录 1.QSS 1.基本语法 2.QSS设置方式 1.指定控件样式设置 2.全局样式设置 1.样式的层叠特性 2.样式的优先级 3.从文件加载样式表 4.使用Qt Designer编辑样式 3.选择器 1.类型选择器 2.id选择器 3.并集选择器 4.子控件选择器 5.伪类选择器 4.样式属性 1.盒模型 …

分布式锁的实现原理

作者&#xff1a;来自 vivo 互联网服务器团队- Xu Yaoming 介绍分布式锁的实现原理。 一、分布式锁概述 分布式锁&#xff0c;顾名思义&#xff0c;就是在分布式环境下使用的锁。众所周知&#xff0c;在并发编程中&#xff0c;我们经常需要借助并发控制工具&#xff0c;如 mu…