我们调用wait或waitpid来处理已终止的子程序。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
函数wait和waitpid均返回2个值,已终止的子进程的PID,以及出参返回的INT型终止状态。我们可以调用宏函数,来检查终止状态,可以辨别子进程是正常终止,由某个信号杀死,还是由作业控制停止。如果调用wait的进程,没有已终止子进程,那么wait将阻塞直到有一个子进程终止。waitpid函数给我们更多的控制,比如等待哪个进程,是否阻塞。其中,pid参数允许我们指定想等待的进程ID,-1表示等待第一个终止的子进程。option选项设置为WNOHANG
,则告诉内核在没有已终止子进程时不要阻塞。
函数wait和waitpid有什么区别?
客户端与服务器建立5个TCP连接:
#include "unp.h"
int
main(int argc, char **argv)
{
int i, sockfd[5];
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
for (i = 0; i < 5; i++) {
sockfd[i] = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd[i], (SA *) &servaddr, sizeof(servaddr));
}
str_cli(stdin, sockfd[0]); /* do it all */
exit(0);
}
当客户终止时,所有打开的描述符由内核自动关闭,且所有5个连接基本在同一时刻终止。这样引发了5个FIN报文,每个连接一个,导致服务器的5个子进程在同一时刻终止,基本在同一时刻引发了5个SIGCHLD信号递交给父进程。
- 第一次咱们跑,5个子进程终止引发的5个SIGCHLD信号都处理了
- 第二次咱们跑,5个子进程终止引发的2个SIGCHLD型号被处理,剩下3个变成僵尸进程
![031b460b2d33f8ae0a72887c2eba451c.png](https://i-blog.csdnimg.cn/blog_migrate/21ac855079a3940121bf69ce0bcc0ad0.png)
- 为什么会出现这种不确定事件呢?
因为建立一个信号处理函数并在其中调用wait并不能完全防止出现僵尸进程,这个问题在于:5个信号都是信号处理函数执行之前产生,而信号处理函数只执行一次,而产生的信号一般又不排队,第一个信号还没执行结束,第二个相同的信号就到达了,导致后面一些子进程没法wait。
- 有什么解决办法呢?
正确的解决办法是调用waitpid代替wait。我们在一个循环里调用waitpid,以获取所有已终止子进程的状态,我们必须指定WNOHANG
选项,它告诉waitpid,在有尚未终止的子进程运行时不要阻塞。
#include "unp.h"
void
sig_chld(int signo)
{
pid_t pid;
int stat;
int i = 0;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
{
printf("i = %dtchild %d terminatedn", ++i,pid);
}
return;
}
嘿嘿嘿,现在是不是输入EOF之后,所有的僵尸进程都会被干掉了呀~
![0d46e890c64b6f62b587594029a7c50d.png](https://i-blog.csdnimg.cn/blog_migrate/9b4843cc12897b440469ecd3111ffb00.jpeg)
为了更清晰的查看效果,我自己加了一个序号i,先看第一次运行结果:
- 前面2个信号,都分别触发了信号处理函数。
- 后面3个信号,只触发一次信号处理函数。
这说明什么问题了呢?简单来说,第一个信号被捕获,处理函数执行完后,第二个信号还没有被捕获到,waitpid直接返回了0。第二个信号被捕获,同第一个一样。第三个信号被捕获,处理函数执行完后,发现剩下的2个信号也被捕获了,所以while循环继续。
再看第二次运行结果,正好反过来了。正确的服务端代码如下:
#include "unp.h"
int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
Signal(SIGCHLD, sig_chld); /* must call waitpid() */
for ( ; ; ) {
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue; /* back to for() */
else
err_sys("accept error");
}
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
参考文献:《UNIX网络编程 卷1:套接字联网API》