前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Linux探索学习】第十八弹——进程等待:深入解析操作系统中的进程等待机制

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

作者头像
GG Bond1
发布2024-12-04 08:34:37
发布2024-12-04 08:34:37
14300
代码可运行
举报
文章被收录于专栏:C/C++葵花宝典C/C++葵花宝典
运行总次数:0
代码可运行

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

前言:

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

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

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

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

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

函数名

描述

wait()

阻塞父进程,直到任一子进程退出,返回退出的子进程 PID。

waitpid()

更灵活的等待函数,可选择等待特定子进程,支持非阻塞模式。

waitid()

类似于 waitpid(),但功能更强大,支持更详细的选项。

signal() 和 sigaction()

注册 SIGCHLD 信号处理函数,用于非阻塞地获取子进程状态。

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


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

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

代码语言:javascript
代码运行次数:0
复制
#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;
}

输出示例:

代码语言:javascript
代码运行次数:0
复制
Child process running...
Child process exiting...
Child process 12345 exited with status 42

说明:

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

补充:

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

3.2 waitpid()

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

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

函数原型:

代码语言:javascript
代码运行次数:0
复制
pid_t waitpid(pid_t pid, int *status, int options);

参数

描述

pid

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

status

存储子进程的退出状态。

options

控制等待行为(如 WNOHANG 表示非阻塞)。

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

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

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

示例代码:

代码语言:javascript
代码运行次数:0
复制
#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;
}

输出示例:

代码语言:javascript
代码运行次数: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 信号在子进程状态发生变化时(如退出)发送给父进程。父进程可以注册信号处理函数来处理此信号。

代码示例:

代码语言:javascript
代码运行次数:0
复制
#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;
}

输出示例:

代码语言:javascript
代码运行次数: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 信号处理。

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

本篇笔记:

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 父进程为什么要等待子进程
  • 2. 父进程等待子进程的常用函数
  • 3. wait() 和 waitpid() 函数详解
    • 3.1 wait()
    • 3.2 waitpid()
  • 4. 使用 SIGCHLD 信号等待子进程
  • 5. 僵尸进程与避免方法
  • 6. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档