一、父进程等待子进程退出
二、为什么要等待子进程退出呢
三、wait和waitpid返回终止状态的宏
四、僵尸进程
五、wait()和waitpid()区别
六、孤儿进程(Orphan Process)
七、demo
一、父进程等待子进程退出
wait()的原型
SYNOPSIS
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
wstatus
是一个指向整数的指针,用于存储wait或waitpid函数返回的子进程退出状态信息。具体来说,wstatus参数允许你在父进程中检查和获取子进程的退出状态信息,包括以下几个方面:
- 正常终止子进程的状态:
如果子进程正常终止(即没有由信号终止),则wstatus将包含子进程的退出状态信息。你可以使用WIFEXITED(wstatus)宏来检查是否为正常终止,并使用WEXITSTATUS(wstatus)宏来获取子进程的退出状态码。
if (WIFEXITED(wstatus)) {
int exit_status = WEXITSTATUS(wstatus);
printf("子进程正常终止,退出状态:%d\n", exit_status);
}
- 由信号终止子进程的状态:
如果子进程由于接收到信号而终止,wstatus将包含有关信号的信息。你可以使用WIFSIGNALED(wstatus)宏来检查是否由信号终止,并使用WTERMSIG(wstatus)宏来获取导致子进程终止的信号编号。
if (WIFSIGNALED(wstatus)) {
int term_signal = WTERMSIG(wstatus);
printf("子进程由于信号 %d 终止\n", term_signal);
}
- 其他进程状态:
wstatus还可以包含其他信息,如是否生成了核心转储文件(使用WCOREDUMP(wstatus)宏来检查)以及是否子进程被停止(使用WIFSTOPPED(wstatus)宏来检查)等。
在使用wait或waitpid函数时,你通常会检查wstatus来确定子进程的退出状态,并采取相应的措施。这可以帮助你了解子进程的执行结果,以及在父进程中根据需要采取后续操作。
等待子进程退出
在C语言中,你可以使用系统调用(system call)来创建子进程并等待其退出。一个常用的系统调用是fork()用于创建子进程,另一个是wait()或waitpid(),用于等待子进程退出。
以下是一个简单的示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t child_pid;
// 创建子进程
child_pid = fork();
if (child_pid == 0) {
// 这是子进程
printf("子进程正在执行\n");
// 在子进程中执行你的任务
// 例如:execl("/path/to/your/executable", "executable_name", NULL);
} else if (child_pid > 0) {
// 这是父进程
printf("父进程等待子进程退出\n");
wait(NULL); // 等待子进程退出
printf("子进程已退出\n");
} else {
// 创建子进程失败
perror("fork failed");
return 1;
}
return 0;
}
在上面的示例中,fork()用于创建子进程,然后父进程使用wait(NULL)等待子进程退出。你可以在子进程中执行你需要的任务,例如使用execl()来执行外部命令。
请注意,C语言中等待子进程退出的方法会依赖于操作系统,因此在不同的操作系统上可能会有一些差异。上述示例适用于类Unix操作系统,如Linux。如果你在Windows上工作,等待子进程退出的方法可能会略有不同。
二、为什么要等待子进程退出呢
等待子进程退出的主要原因是为了确保父进程能够获取子进程的退出状态以及资源清理。
这涉及到操作系统和进程管理的一些关键概念:
-
获取退出状态信息:
当子进程退出时,它会返回一个退出状态码,通常表示子进程的执行结果。这个退出状态码可以帮助父进程确定子进程是否成功完成了其任务。这对于父进程来说很重要,因为它可能需要根据子进程的执行结果采取不同的行动。 -
资源清理:
子进程在执行期间可能会分配一些资源,如文件句柄、内存等。如果父进程不等待子进程退出,那么这些资源可能不会被正确释放,从而导致资源泄漏。等待子进程退出允许父进程在子进程退出后执行必要的资源清理操作。 -
进程同步:
在某些情况下,父进程可能需要等待子进程执行完特定的任务,然后才能继续执行自己的任务。等待子进程退出允许父进程实现进程同步,确保在必要时等待子进程完成。
总之,
等待子进程退出是一种管理和协调多个进程之间的重要机制,它有助于确保父进程能够正确处理子进程的执行结果,并且能够清理子进程使用的资源,从而提高系统的稳定性和可维护性。
三、wait和waitpid返回终止状态的宏
在C语言中,你可以使用以下宏来检查wait和waitpid所返回的终止状态:
宏 | 说明 |
---|---|
WIFEXITED(status) | 该宏用于检查子进程是否正常终止(即没有出现信号导致的终止) |
WEXITSTATUS(status) | 该宏用于获取正常终止子进程的退出状态,即子进程退出时返回的整数值 |
WIFSIGNALED(status) | 该宏用于检查子进程是否由于接收到信号而终止 |
WTERMSIG(status) | 该宏用于获取导致子进程终止的信号编号 |
WCOREDUMP(status) | 该宏用于检查是否生成了核心转储文件 |
WIFSTOPPED(status) | 该宏用于检查子进程是否处于停止状态 |
WSTOPSIG(status) | 该宏用于获取导致子进程停止的信号编号 |
这些宏可用于处理子进程的退出状态,以便根据需要采取不同的操作。以下是一些示例:
int status;
// 检查子进程是否正常终止
if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
printf("子进程正常终止,退出状态:%d\n", exit_status);
} else if (WIFSIGNALED(status)) {
int term_signal = WTERMSIG(status);
printf("子进程由于信号 %d 终止\n", term_signal);
} else if (WIFSTOPPED(status)) {
int stop_signal = WSTOPSIG(status);
printf("子进程停止,信号 %d\n", stop_signal);
}
这些宏帮助你在处理子进程的退出状态时更容易进行条件检查,以便根据不同的退出情况采取不同的措施。
四、僵尸进程
-
僵尸进程(Zombie Process)
是多进程操作系统中的一种状态,它通常是指一个子进程在执行完毕后,父进程还没有来得及获取子进程的退出状态信息,导致子进程的一些资源仍然被系统保留,但子进程本身已经不再运行。这种状态可能会导致系统中积累大量的僵尸进程,浪费系统资源。
下面是有关僵尸进程的一些关键信息: -
原因:
僵尸进程通常出现在父进程没有及时调用wait()或waitpid()来等待子进程退出状态的情况下。当子进程退出时,它的退出状态信息(包括退出码)会被保留,但父进程未能获取这些信息,从而导致子进程变成僵尸。 -
危害:
虽然僵尸进程不再执行任何代码,但它们会占用一些系统资源,如进程表中的条目。如果系统中积累了大量的僵尸进程,可能会导致系统资源的浪费,甚至影响系统的性能。 -
解决方法:
为了解决僵尸进程问题,父进程应该及时调用wait()或waitpid()来获取子进程的退出状态信息。这将使操作系统能够清理子进程的资源,并且父进程可以获取子进程的退出状态码,以便根据需要采取进一步的操作。
下面是一个示例,展示如何在C语言中处理僵尸进程问题:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t child_pid = fork();
if (child_pid == 0) {
// 这是子进程,在子进程中执行任务
printf("子进程正在执行\n");
// 一些子进程的任务
exit(0); // 子进程退出
} else if (child_pid > 0) {
// 这是父进程
int status;
wait(&status); // 父进程等待子进程退出
if (WIFEXITED(status)) {
printf("子进程正常终止,退出状态:%d\n", WEXITSTATUS(status));
}
} else {
perror("fork failed");
return 1;
}
return 0;
}
在上述示例中,父进程使用wait()等待子进程退出,以确保不会产生僵尸进程。一旦子进程退出,父进程就可以获取子进程的退出状态信息,并进行相应的处理。
系统里的僵尸进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0){
while(1){
printf("this is parent process,pid = %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("cnt = %d\n",cnt);
printf("this is child process,child pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
break;
}
}
}
return 0;
}
进程的状态
进程状态是操作系统中用于跟踪和管理进程的状态标志。在典型的类Unix操作系统中,进程可以处于不同的状态,其中包括:
-
Zombie (Z): 僵尸状态是指子进程已经终止(退出),但其父进程还没有来得及获取子进程的退出状态信息。在这个状态下,子进程的一些资源(如进程表条目)仍然被保留,但它本身不再执行任何代码。解决僵尸进程问题的一种方式是父进程调用wait()或waitpid()来获取子进程的退出状态,这将使子进程变成"Terminated"状态。
-
Terminated (T): 终止状态表示进程已经正常终止,其退出状态信息已被父进程获取。这是正常的进程结束状态。
-
Sleeping (S): 睡眠状态表示进程暂时处于休眠状态,通常是因为它在等待某些事件的发生,例如等待某个信号、I/O操作的完成等。在这种状态下,进程不会占用CPU资源,而是被操作系统挂起,直到等待的事件发生。
-
Running (S+): 运行状态表示进程当前正在执行代码,占用CPU资源。这是进程在主动执行其任务时的状态。
-
R+(Running): 表示进程正在运行或准备运行。这是指进程当前正在使用CPU资源执行任务。
这些进程状态是典型的Unix/Linux系统中的状态标志。不同的操作系统可能会有略微不同的状态标志,但这些是比较常见的状态,可以帮助操作系统管理进程的生命周期和资源分配。当进程在不同的状态之间切换时,操作系统会相应地更新其状态标志。
五、wait()和waitpid()区别
原型
SYNOPSIS
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
两个函数返回:若成功则为进程 ID,若出错则为-1
status
参数(状态码
):
是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空:不关心退出状态
wait(NULL);不关心状态码的返回值
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid > 0){
wait(NULL);
while(1){
printf("this is parent process,pid = %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("cnt = %d\n",cnt);
printf("this is child process,child pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 5){
exit(0);
}
}
}
return 0;
}
wait (&status); WEXITSTATUS(status)关心状态码的返回值
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0){
wait(&status);
// 检查子进程是否正常终止
if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
printf("子进程正常终止,退出状态:%d\n", exit_status);
} else if (WIFSIGNALED(status)) {
int term_signal = WTERMSIG(status);
printf("子进程由于信号 %d 终止\n", term_signal);
} else if (WIFSTOPPED(status)) {
int stop_signal = WSTOPSIG(status);
printf("子进程停止,信号 %d\n", stop_signal);
}
printf("child process to quit,child process status = %d\n",WEXITSTATUS(status));
while(1){
printf("this is parent process,parent pid = %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("cnt = %d\n",cnt);
printf("this is child process,child pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 5){
exit(3);
}
}
}
return 0;
}
- 如果其所有子进程都还在运行,则阻塞。
- 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
- 如果它没有任何子进程,则立即出错返回。
waitpid(pid,&status,WNOHANG);
waitpid()是一个在Linux和Unix系统中用于等待子进程退出并获取其退出状态信息的系统调用。它与wait()函数类似,但具有更多的灵活性。waitpid()的原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
- pid:指定要等待的子进程的PID。
- 如果pid为正数,表示等待具有该PID的特定子进程。
- 如果pid为负数,表示等待任何与该绝对值相等的组ID匹配的子进程。
- 如果pid为0,表示等待与调用进程组ID相同的任何子进程。
- status:
- 一个整数指针,用于存储子进程的退出状态信息。如果不需要获取退出状态信息,可以将其设置为NULL。
- options:
- 用于指定等待选项的整数值,可以是位掩码(可以使用|运算符组合多个选项)。常用的选项包括:
- WNOHANG:非阻塞等待,如果没有已终止的子进程,立即返回。
- WUNTRACED:包括已停止的子进程在内,但不包括已终止的子进程。
- WCONTINUED:包括已继续的子进程在内。
- WEXITED:只等待已经终止的子进程。
- WSTOPPED:只等待已经停止的子进程。
- WNOHANG | WUNTRACED:非阻塞地等待已终止或已停止的子进程。
waitpid()函数会阻塞调用进程,直到指定的子进程退出或符合指定的条件。然后,它将子进程的PID存储在status中,以及子进程的退出状态信息。可以使用宏如WIFEXITED()和WEXITSTATUS()来检查和获取子进程的退出状态信息。
以下是一个示例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t child_pid;
int status;
child_pid = fork();
if (child_pid == 0) {
// 子进程
printf("子进程正在执行\n");
exit(42);
} else if (child_pid > 0) {
// 父进程
waitpid(child_pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程已退出,退出状态:%d\n", WEXITSTATUS(status));
}
}
return 0;
}
在上述示例中,waitpid()被用于等待子进程退出,获取其退出状态信息,然后打印出来。
注意:虽然你父进程有调用waitpid来收集子进程的状态
但是验证发现如果你子进程非阻塞等待,那么你子进程也会变成僵尸进程
wait和waitpid的区别
- 在一个子进程终止前,
wait
使其调用者阻塞,而waitpid
有一选择项,可使调用者不阻
塞。- waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的进程。
六、孤儿进程(Orphan Process)
是指一个子进程在其父进程结束或者被终止后,仍然在系统中运行。通常,当父进程退出或被终止时,操作系统会将子进程的父进程设置为init进程(通常是PID为1的特殊进程),从而由init进程来接管孤儿进程。
孤儿进程产生的原因主要是:
-
父进程比子进程早退出:如果父进程在子进程之前退出,而子进程仍在运行,那么子进程会成为孤儿进程。
-
父进程没有等待子进程退出:父进程没有使用wait()、waitpid()或类似的函数来等待子进程的退出,从而使子进程成为孤儿进程。
孤儿进程通常是由于编程错误或不正确的进程管理导致的。操作系统会自动接管孤儿进程,并在其退出时清理相关资源,以确保系统正常运行。这种机制可以避免孤儿进程一直存在并浪费系统资源。
在Unix/Linux系统中,init进程(通常是PID 1)是用来接管孤儿进程的默认父进程。当孤儿进程退出时,init进程会等待并回收它,确保它不会一直占用系统资源。
在编写多进程程序时,需要确保适当地等待子进程退出,以避免产生孤儿进程。这通常可以通过使用wait()、waitpid()等函数来实现。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0){
printf("this is parent process,parent pid = %d\n",getpid());
}else if(pid == 0){
while(1){
printf("cnt = %d\n",cnt);
printf("this is child process,child pid = %d,parent pid = %d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 5){
exit(3);
}
}
}
return 0;
}
让父进程运行一次就退出
通过getppid()从子进程调用父进程的pid
七、demo
demo14.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t child_pid;
// 创建子进程
child_pid = fork();
if (child_pid == 0) {
// 这是子进程
printf("子进程正在执行\n");
// 在子进程中执行你的任务
// 例如:execl("/path/to/your/executable", "executable_name", NULL);
} else if (child_pid > 0) {
// 这是父进程
printf("父进程等待子进程退出\n");
wait(NULL); // 等待子进程退出
printf("子进程已退出\n");
} else {
// 创建子进程失败
perror("fork failed");
return 1;
}
return 0;
}
demo17.c
#include "apue.h"
#include <sys/waut.h>
void pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination,exit status = %d\n",WEXITSTATUS(atatus));
else if (WIFSIGNALED(status))
printf("abnormal termination,signal nunber = %d%s\n",WTERMSIG(status));
#ifdef WCOREDUMP
WCOREDUMP(status) ? "(core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped,signal number = %d\n",WSTOPSIG(status));
}
int main()
{
pid_t pid;
int status;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
exit (7);
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
abort(); /* generates SIGABRT */
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
status /= 0; /* divide by 0 generates SIGFPE */
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
exit(0);
}
运行结果
UNIX环境高级编程对信号的demo
正常退出
第一次fork = 0就进入了子进程,子进程直接退出exit()退出码为7,父进程去等待子进程的状态,调用pr_exit()来分析子进程的状态码
在pr_exit()来分析检索(子进程是正常退出,退出码是,状态码检索)
异常退出
调用abort()
除数为零导致程序奔溃异常退出
退出状态由系统定义的宏来进行判断(什么原因退出)