调用fork函数创建的子进程的终止方式:1)调用exit函数并使用exit函数传递参数 ;2)main函数中调用return语句并返回值。
fork函数创建的子进程虽然终止了,但是操作系统不会销毁子进程,这样的进程就是僵尸进程。
向exit函数传递的参数值和main函数中return语句的返回值都会传递给操作系统,只有这些值被传递给创建该子进程的父进程,这些终止的子进程才会被销毁。
如果父进程没有主动要求(通过函数调用)获得子进程的结束状态值,操作系统会一直保存,并让子进程长期处于僵尸进程的状态。
如果父进程终止,处于僵尸状态的子进程将同时被销毁。
销毁僵尸进程:
1) pid_t wait(int *statloc);
wait函数将子进程终止时传递的参数值保存到statloc所指向的内存空间。然后通过宏WIFEXITED(status)确定子进程是否正常终止,通过宏WEXITSTATUS获得子进程的返回值。
wait函数的缺点:如果子进程没有终止,wait函数将阻塞直到子进程终止。
如下代码所示,子进程会休眠15s,在这段时间中父进程会阻塞在wait函数上直到子进程返回并终止。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid == 0) {
sleep(15);
return 3;
} else {
int status;
pid = wait(&status);
printf("child process exited\n");
if (WIFEXITED(status)) {
printf("child process return : %d\n", WEXITSTATUS(status));
}
}
return 0;
}
2) pid_t waitpid(pid_t pid, int *status, int options);
waitpid(-1, &status, WNOHANG)
其中,-1 表示可以等待任意子进程终止,WNOHANG表示子进程没有终止也不会进入阻塞状态,而是返回0并退出函数。
如下代码所示,子进程会休眠15s,而在这段时间中,父进程并没有阻塞在waitpid函数上,会不断运行while循环中的代码直到子进程终止后才退出循环。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid == 0) {
sleep(15);
return 3;
} else {
int status;
while (!waitpid(-1, &status, WNOHANG)) {
printf("child process in running\n");
printf("do someting\n");
}
printf("child process exited\n");
if (WIFEXITED(status)) {
printf("child process return : %d\n", WEXITSTATUS(status));
}
}
return 0;
}
3)借助信号处理机制,当子进程终止后,会产生一个SIGCHLD信号,注册SIGCHLD信号的处理函数,在处理函数中调用wait/waitpid函数获取子进程的返回值,从而使操作系统销毁子进程。
signal/sigaction函数是信号注册函数。发生注册的信号时,操作系统将调用相应的信号处理函数。信号处理函数的参数应为int型,返回值类型为void 。操作系统调用信号处理函数时,传递给该函数的参数是信号值。
当子进程终止时,操作系统会产生一个SIGCHLD信号。注册一个SIGCHLD信号的处理函数。处理函数调用wait/waitpid函数获取子进程的返回值来销毁子进程。
sigaction函数解决了signal的可移植问题。