嵌入式Linux:如何监视子进程
目录
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
信号后恢复运行的子进程的状态。
- WNOHANG:非阻塞模式。如果没有子进程终止,立即返回
返回值:
- 成功时,返回已终止或状态已改变的子进程的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
信号处理,确保及时回收子进程的资源是避免僵尸进程的关键。