- 这两个函数的用法可以见文章:javascript:void(0)
函数返回值利用
- 函数wait和waitpid均返回两个值:已终止子进程的进程ID号,以及通过statloc指针返回 的子进程终止状态(一个整数)
- 我们可以调用三个宏来检查终止状态,并辨别子进程是正常终 止、由某个信号杀死还是仅仅由作业控制停止而已。另有些宏用于接着获取子进程的退出状态、 杀死子进程的信号值或停止子进程的作业控制信号值。在域套接字传递描述符的文章中中,我们将为此目的使用宏 WIFEXITED和WEXITSTATUS
二、wait、waitpid使用起来的区别两个函数的区别
- 如果调用wait的进程没有已终止的子进程,不过有一个或多个子进程仍在执行,那么wait 将阻塞到现有子进程第一个终止为止
- waitpid函数就等待哪个进程以及是否阻塞给了我们更多的控制
第一步
- 为了演示,我们把TCP客户程序修改为下面代码所示
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个与服务器的连接,随后在调用str_cli函数时仅用第一个连接(sockfd[0])。建立多个连接的目的是从并发服务器上派生多个子进程,如下图
第二步
- 如果此时5个客户端同时断开
- 当客户终止时,所有打开的描述符由内核自动关闭(我们不调用close,仅调用exit),且 所有5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个,它们反过来使服务器 的5个子进程基本在同一时刻终止。这又导致差不多在同一时刻有5个SIGCHLD信号递交给父进 程
第三步
- 如果服务端使用sig_chld信号处理函数(见文章:javascript:void(0))调用wait函数来处理子进程
- 建立一个信号处理函数并在其中调用wait并不足以防止出现僵死进程。本问题在于:
- 所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为Unix信号一般是不排队的
- 更严重的是,本问题是不确定的。在我们刚刚运行的例子中,客户与服务器在同一个 主机上,信号处理函数执行1次,留下4个僵死进程。但是如果我们在不同的主机上运行客户和 服务器,那么信号处理函数一般执行2次:一次是第一个产生的信号引起的,由于另外4个信号 在信号处理函数第一次执行时发生,因此该处理函数仅仅再被调用一次,从而留下3个僵死进程。 不过有的时候,依赖于FIN到达服务器主机的时机,信号处理函数可能会执行3次甚至4次
三、最终程序第四步
- 此时我们就需要改进sig_chld函数调用waitpid来处理子进程
- 正确的解决办法是调用waitpid而不是wait,下面代码给出了正确处理SIGCHLD的sig_chld函 数
- 这个版本管用的原因在于:我们在一个循环内调用waitpid,以获取所有已终止子进程的状 态。我们必须指定WNOHANG选项,它告知waitpid在有尚未终止的子进程在运行时不要阻塞。我们前面文章介绍的sig_chld信号处理函数(见文章:javascript:void(0))不能在循环内调用wait,因为没有办法防止wait在正运行的子进程尚有未终止时阻塞
void sig_chld(int signo) { pid_t pid; int stat; while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated\n", pid); return; }
下面给出了我们的服务器程序的最终版本。它正确处理accept返回的EINTR,并建立一个给所有已终止子进程调用waitpid的信号处理函数
- 当fork子进程时,必须捕获SIGCHLD信号
- 当捕获信号时,必须处理被中断的系统调用
- SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免留下僵死进程
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 */
}
}