嵌入式Linux:如何监视子进程

devtools/2025/1/30 18:47:40/

目录

1、wait()函数

2、waitpid()函数

3、SIGCHLD信号


在嵌入式Linux系统中,父进程通常需要创建子进程来执行特定任务,例如处理网络请求、执行计算任务等。监视子进程的状态不仅可以确保资源的合理利用,还能防止僵尸进程的产生,从而提升系统的稳定性和性能。wait()waitpid()是用于监视和管理子进程的关键系统调用,而SIGCHLD信号则提供了一种异步通知机制,以便父进程在子进程状态发生变化时采取相应的措施。

1、wait()函数

wait()系统调用用于让父进程等待任意一个子进程的终止,并获取该子进程的终止状态信息。它执行以下功能:

  • 等待子进程终止:父进程在调用wait()后会阻塞,直到其任意一个子进程终止为止。
  • 回收子进程资源:当子进程终止时,操作系统需要回收它占用的资源,这一过程称为“收尸”。如果不进行回收,子进程会变为僵尸进程,占用系统资源。

僵尸进程是已经终止,但父进程尚未读取其终止状态的子进程。通过调用wait()可以避免系统中积累僵尸进程,影响性能和稳定性。

函数原型如下:

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

参数与返回值:

  • status:这是一个指向int的指针,用于存储子进程的退出状态。父进程可以通过这个状态了解子进程是正常退出还是被信号中止的。如果传入NULL,则表示不关心子进程的退出状态,仅仅是等待它终止。

  • 返回值:返回已终止的子进程的进程ID;如果调用时没有子进程存在,wait()返回-1,并将errno设为ECHILD表示没有子进程可等待。

函数行为:

  • 阻塞等待wait()会阻塞调用进程,直到任意一个子进程终止。如果所有子进程都还在运行,wait()将持续阻塞。
  • 资源回收:当子进程终止时,wait()除了获取子进程的终止状态,还会回收子进程的资源,避免产生僵尸进程。
  • 处理已终止的子进程:如果wait()调用时有子进程已终止,函数将立即返回,而不会阻塞。

状态检查:

使用宏可以检查和处理status参数中存储的子进程终止状态:

  • WIFEXITED(status):如果子进程是通过exit()_exit()正常终止的,则返回true

  • WEXITSTATUS(status):当WIFEXITED(status)true时,可以通过该宏获取子进程的退出状态,通常是子进程在调用exit()_exit()时的退出码。

  • WIFSIGNALED(status):如果子进程因接收到某个信号而异常终止,则返回true

  • WTERMSIG(status):当WIFSIGNALED(status)true时,可以通过该宏获取导致子进程终止的信号编号。

  • WIFSTOPPED(status):如果子进程处于暂停状态,则返回true

  • WSTOPSIG(status):当WIFSTOPPED(status)true时,可以获取导致子进程暂停的信号编号。

  • WCOREDUMP(status):如果子进程终止时生成了核心转储文件,则返回true

以下示例展示了如何使用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 == -1) {// fork()失败perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程执行代码printf("Child process running (PID: %d)...\n", getpid());sleep(2);  // 模拟子进程的执行exit(42);  // 正常退出,并返回状态码42} else {// 父进程执行代码int status;pid_t child_pid = wait(&status);  // 等待任一子进程终止if (child_pid > 0) {// 子进程终止后的处理if (WIFEXITED(status)) {printf("Child process %d terminated with status: %d\n", child_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child process %d was terminated by signal: %d\n", child_pid, WTERMSIG(status));}} else {perror("wait failed");}}return 0;
}

在这段代码中,父进程创建了一个子进程并等待其终止,同时通过status宏获取子进程的退出状态。

wait()函数的局限性:

  • 无法指定特定子进程wait()无法让父进程选择等待某个特定的子进程,它只能按顺序等待下一个终止的子进程。如果父进程同时拥有多个子进程,wait()将随机处理任意一个子进程的终止。

  • 阻塞等待wait()始终是阻塞的,直到有子进程终止为止。如果父进程需要继续处理其他任务,则wait()的阻塞可能导致父进程效率低下。

2、waitpid()函数

waitpid()函数提供了更多的控制选项,使得父进程可以选择性地等待某个特定子进程,或进行非阻塞的等待。

函数原型如下:

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

参数:

  • pid:指定需要等待的子进程:
    • > 0:等待指定PID的子进程。
    • = 0:等待与调用进程同一进程组的任意子进程。
    • < -1:等待进程组ID等于pid绝对值的所有子进程。
    • = -1:等待任意子进程,与wait()等价。
  • status:与wait()status参数相同。
  • options:可以设置为0或包含以下标志:
    • WNOHANG:非阻塞模式。如果没有子进程终止,立即返回0
    • WUNTRACED:返回因信号停止的子进程的状态。
    • WCONTINUED:返回收到SIGCONT信号后恢复运行的子进程的状态。

返回值:

  • 成功时,返回已终止或状态已改变的子进程的PID。
  • 如果没有符合条件的子进程,且设置了WNOHANG选项,返回0
  • 失败时返回-1,并设置errno

waitpid()wait()的区别:

  • 等待特定子进程waitpid()允许父进程通过pid参数指定特定的子进程,而wait()只能等待任意子进程。
  • 非阻塞模式waitpid()支持非阻塞模式(通过WNOHANG),使父进程可以立即返回,而不必等待子进程终止。
  • 支持更多状态监控waitpid()可以监视子进程暂停(WUNTRACED)或恢复运行(WCONTINUED)的状态,而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 == -1) {// fork()失败perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程执行代码printf("Child process running (PID: %d)...\n", getpid());sleep(2);  // 模拟子进程工作exit(42);  // 正常退出,返回状态码42} else {// 父进程执行代码int status;pid_t child_pid;// 非阻塞等待子进程do {child_pid = waitpid(pid, &status, WNOHANG);  // 非阻塞模式if (child_pid == 0) {printf("No child process terminated yet. Doing other work...\n");sleep(1);  // 模拟其他工作} else if (child_pid > 0) {if (WIFEXITED(status)) {printf("Child process %d terminated with status: %d\n", child_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child process %d was terminated by signal: %d\n", child_pid, WTERMSIG(status));}} else {perror("waitpid failed");exit(EXIT_FAILURE);}} while (child_pid == 0);printf("Parent process continues...\n");}return 0;
}

在这个示例中,父进程可以继续处理其他任务,而不必一直阻塞等待子进程的终止。waitpid()的非阻塞模式使得程序更为灵活和高效。 

3、SIGCHLD信号

SIGCHLD是父进程在子进程状态发生变化(如终止或暂停)时收到的信号。通过捕获SIGCHLD信号,父进程可以实时地检测到子进程的状态变化,并采取相应的行动(例如回收资源)。

在POSIX标准下,sigaction()系统调用被广泛用于设置信号处理程序。相比于传统的signal()函数,sigaction()提供了更多的选项和更好的控制。

示例代码:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>void sigchld_handler(int signum) {int status;pid_t pid;// 循环调用waitpid,以确保处理多个已终止的子进程while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {if (WIFEXITED(status)) {printf("Child process %d terminated with status: %d\n", pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child process %d was terminated by signal: %d\n", pid, WTERMSIG(status));}}
}int main() {struct sigaction sa;sa.sa_handler = sigchld_handler;  // 指定信号处理函数sigemptyset(&sa.sa_mask);  // 清空阻塞信号集sa.sa_flags = SA_RESTART;  // 自动重启被中断的系统调用sigaction(SIGCHLD, &sa, NULL);  // 安装信号处理程序for (int i = 0; i < 3; i++) {pid_t pid = fork();  // 创建多个子进程if (pid == 0) {// 子进程代码printf("Child process %d running...\n", getpid());sleep(2);exit(42);}}// 父进程的其他工作while (1) {printf("Parent process working...\n");sleep(1);}return 0;
}

使用sigaction()的优点:

  • 自动重启:通过设置SA_RESTART标志,能够在信号处理完成后自动重启被中断的系统调用(如read()write())。
  • 可靠的信号处理sigaction()避免了传统signal()函数的缺陷,确保了信号处理的可靠性和可移植性。

SIGCHLD信号的常见问题与解决方案:

  • 丢失信号:在同时终止多个子进程时,可能会丢失一些SIGCHLD信号。为解决这一问题,可以在信号处理程序中循环调用waitpid(),直到没有子进程终止为止。
  • 阻塞的系统调用:信号处理可能会中断一些阻塞的系统调用(如read()sleep()),导致它们返回错误。通过使用sigaction()SA_RESTART标志可以自动重启被中断的调用。

通过以上内容,开发者可以根据实际需求选择合适的方法来监视和管理子进程,确保程序运行的稳定性和资源的有效利用。

  • 如果只需等待任意一个子进程终止且不关心特定子进程,使用wait()是最简单的选择。
  • 如果需要非阻塞地等待特定子进程或需要获取更多子进程状态信息,waitpid()则更为灵活。
  • 在处理多个子进程时,捕获SIGCHLD信号可以让父进程更加实时地处理子进程的终止,并在不中断父进程正常操作的情况下回收子进程资源。

无论是使用wait()waitpid()还是SIGCHLD信号处理,确保及时回收子进程的资源是避免僵尸进程的关键。


http://www.ppmy.cn/devtools/154676.html

相关文章

(一)QT的简介与环境配置WIN11

目录 一、QT的概述 二、QT的下载 三、简单编程 常用快捷键 一、QT的概述 简介 Qt&#xff08;发音&#xff1a;[kjuːt]&#xff0c;类似“cute”&#xff09;是一个跨平台的开发库&#xff0c;主要用于开发图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;…

Excel 技巧20 - 在Excel中输入内容时自动添加边框(★★)

本文将如何在Excel中输入内容时自动添加边框。 1&#xff0c;在Excel中输入内容时自动添加边框 要点就是要在Excel中新建规则。 1-1&#xff0c;新建规则 选中对象列&#xff0c;然后点 Menu > 开始 > 条件格式 > 新建规则 - 规则类型&#xff1a;使用公式确定要设置…

Java Swing 基础组件详解 [论文投稿-第四届智能系统、通信与计算机网络]

大会官网&#xff1a;www.icisccn.net Java Swing 是一个功能强大的 GUI 工具包&#xff0c;提供了丰富的组件库用于构建跨平台的桌面应用程序。本文将详细讲解 Swing 的基础组件&#xff0c;包括其作用、使用方法以及示例代码&#xff0c;帮助你快速掌握 Swing 的核心知识。 一…

深入理解Pytest中的Setup和Teardown

关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理&#xff0c;构建成功的基石 在自动化测试工作之前&#xff0c;你应该知道的10条建议 在自动化测试中&#xff0c;重要的不是工具 对于简单程序而言&#xff0c;使用 Pytest 运行测试直截了当。然而&#xff0c;当你…

【数据结构】动态内存管理函数

动态内存管理 为什么存在动态内存管理动态内存函数的介绍&#x1f38a;malloc补充&#xff1a;perror函数&#x1f38a;free&#x1f38a;calloc&#x1f38a;realloc 常见动态内存错误对空指针的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一…

关于产品和技术架构的思索

技术架构或者设计应该和产品设计分离&#xff0c;但是又不应该和产品架构独立。 听起来非常的绕并且难以理解。 下面我们用一个例子来解读这两者的关系 产品&#xff08;族谱图&#xff09; 如果把人类当作产品&#xff0c;那设计师应该是按照上面设计的(当然是正常的伦理道德)…

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理

WPF基础 | 深入 WPF 事件机制&#xff1a;路由事件与自定义事件处理 一、前言二、WPF 事件基础概念2.1 事件的定义与本质2.2 常见的 WPF 事件类型 三、路由事件3.1 路由事件的概念与原理3.2 路由事件的三个阶段3.3 路由事件的标识与注册3.4 常见的路由事件示例 四、自定义事件处…

docker desktop使用ollama在GPU上运行deepseek r1大模型

一、安装docker 安装WSL打开Hyper V 可以参考&#xff1a;用 Docker 快速安装软件_哔哩哔哩_bilibili 二、拉取ollama镜像 在powershell中运行如下命令&#xff0c;即可拉取最新版本的ollama镜像&#xff1a; docker pull ollama/ollama 如果需要指定版本&#xff0c;可以…