⟅UNIX网络编程⟆⦔wait和waitpid函数

说在前面

数据类型说明

数据类型 说明 头文件
pid_t 一般为int,见这里 <unistd.h>

基本说明

用于处理已终止的子进程。

wait, waitpid — wait for a child process to stop or terminate

  • 定义
    #include <sys/wait.h>
    
    pid_t wait(int *stat_loc);
    pid_t waitpid(pid_t pid, int *stat_loc, int options);
    
    参数说明:
    • pid
      指定要wait的进程的ID;(若为 -1,表示wait第一个终止的子进程,该说明来自unix网络编程一书;但在linux manual page中,-1表示wait所有子进程实验证明在ubuntu下后者是对的,可能书上的表述不是很准确,或者我没理解

    • stat_loc
      作为返回值,指向子进程终止状态(一个整数)。

      说明
      WIFEXITED Evaluates to a non-zero value if status was returned for a child process that terminated normally.
      正常终止
      WEXITSTATUS If the value of WIFEXITED(stat_val) is non-zero, this macro evaluates to the low-order 8 bits of the status argument that the child process passed to _exit() or exit(), or the value the child process returned from main().
      WIFSIGNALED Evaluates to a non-zero value if status was returned for a child process that terminated due to the receipt of a signal that was not caught
      信号杀死
      WTERMSIG If the value of WIFSIGNALED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the termination of the child process.
      WIFSTOPPED Evaluates to a non-zero value if status was returned for a child process that is currently stopped.
      由作业控制停止
      WSTOPSIG If the value of WIFSTOPPED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the child process to stop.
      WIFCONTINUED Evaluates to a non-zero value if status was returned for a child process that has continued from a job control stop.
      栗子:
      do {
                 wpid = waitpid(child_pid, &status, WUNTRACED
         #ifdef WCONTINUED       /* Not all implementations support this */
                     | WCONTINUED
         #endif
                 );
                 if (wpid ==1) {
                     perror("waitpid");
                     exit(EXIT_FAILURE);
                 }
      
                 if (WIFEXITED(status)) {
                     printf("child exited, status=%d\n", WEXITSTATUS(status));
      
                 } else if (WIFSIGNALED(status)) {
                     printf("child killed (signal %d)\n", WTERMSIG(status));
      
                 } else if (WIFSTOPPED(status)) {
                     printf("child stopped (signal %d)\n", WSTOPSIG(status));
      
         #ifdef WIFCONTINUED     /* Not all implementations support this */
                 } else if (WIFCONTINUED(status)) {
                     printf("child continued\n");
         #endif
                 } else {    /* Non-standard case -- may never happen */
                     printf("Unexpected status (0x%x)\n", status);
                 }
             } while (!WIFEXITED(status) && !WIFSIGNALED(status));
      
    • options
      指定附加选项。
      在调用wait时若没有已终止的子进程,但是有一个或多个运行的子进程,那么wait将阻塞到有子进程终止为止

      说明
      WCONTINUED The waitpid() function shall report the status of any continued child process specified by pid whose status has not been reported since it continued from a job control stop.
      WNOHANG The waitpid() function shall not suspend execution of the calling thread if status is not immediately available for one of the child processes specified by pid.
      在没有已终止进程时不要阻塞
      WUNTRACED The status of any child processes specified by pid that are stopped, and whose status has not yet been reported since they stopped, shall also be reported to the requesting process.
    返回值:
    • 已终止子进程的进程ID(以及作为参数的stat_loc)

wait与waitpid区别

  • 问题提出

    • client
      客户建立5个与服务器的连接(目的是让并发服务器派生多个子进程),随后在调用str_cli函数时仅用第一个连接。
      在这里插入图片描述
      int
      main(int argc, char **argv)
      {
      	int					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], (struct sockaddr *) &servaddr, sizeof(servaddr));
      	}
      
      	str_cli(stdin, sockfd[0]);		/* do it all */
      	exit(0);
      }
      
    • 1) 当客户端终止时(即运行到exit),所有打开的描述符由内核关闭,且5个连接基本在同一时刻终止。2) 这就引发5个FIN,导致服务器5个子进程基本在同一时刻终止(该过程详见⟅UNIX网络编程⟆⦔TCP客户/服务器程序实例(二)正常终止部分)。3) 这又导致差不多同一时刻5个SIGCHLD信号递交给父进程。
      在这里插入图片描述
    • 运行
      1) 运行server(这里的server中的signal函数需要将上一节中注释的部分取消注释,或者使用系统提供的signal函数)
      2) 运行client
      3) client端输入hi
      4) client端输入Ctrl+D,终止
      5) server端输出如下
      在这里插入图片描述
      6)在另一个窗口中使用 p s e f g r e p s e r v e r ps -ef|grep server 命令
      在这里插入图片描述
    • 可以看到,预期的五个子进程全部回收并没有出现,在server端只有两个子进程的终止信息出现,使用ps命令看到有三个子进程变成了僵尸进程
  • 解释

    • 建立一个信号处理函数并在其中调用wait函数并不足以防止出现僵死进程
    • 5个信号都在信号处理函数执行之前产生,而信号处理函数执行了两次,一次是第一个产生的信号到达引起的(这时该信号是阻塞的),由于另外4个信号在信号处理函数第一次执行时发生,所以在第一次信号处理函数调用完成(并解除信号阻塞,关于阻塞见⟅UNIX网络编程⟆⦔POSIX信号处理)后,只递交一次,又调用一次信号处理函数。
      但是有的时候,由于不同FIN的到达时机不同,可能会执行3或4次(例如某个信号在第一次信号处理函数调用完成时到达)
      (书上的栗子和这里有些不同,书上的栗子在同一主机上运行时产生了四个僵尸进程,原因是“5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次”)
  • 解决方式

    • 使用waitpid
      void
      sig_child(int signo)
      {
      	pid_t	pid;
      	int		stat;
      
      	while( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
      		printf("child %d terminated\n", pid);
      	return;
      }
      
    • 原因
      在一个循环中调用waitpid,以获取所有已终止子进程的状态。同时使用WNOHANG标志告知waitpid在有尚未终止的子进程在运行时不要阻塞。
    • 详细解释
      1) 以上面的栗子为例,五个SIGCHLD信号同时到达,第一个信号导致调用信号处理函数,在循环中,waitpid就会依次处理五个终止的子进程。处理完成后,剩余四个信号只留下一个,再次导致调用,但是这一次没有要处理的了,返回。(如下图,in sig_child表示进入sig_child函数)
      在这里插入图片描述
      2) 再举个栗子,假设某个信号到达时,已经有子进程变成僵尸进程了,那么在循环调用时,还是会将该僵尸进程清理掉。
    • 为啥不能在循环中使用wait?
      已知wait在没有已终止子进程且存在运行中的子进程时会阻塞。
      考虑下面这个栗子:五个子进程,其中一个终止,进入信号处理函数后,循环wait,第一次循环清理了已终止的那个进程第二次循环时发现没有已终止子进程且有运行中的子进程,wait阻塞,程序出问题。
    • 考虑到的一个问题
      ⟅UNIX网络编程⟆⦔wait和waitpid函数-补充

总结

  • 处理的三个问题
  1. 当fork子进程时,必须捕获SIGCHLD信号(即使用signal函数)
  2. 当捕获信号时,必须处理被中断的系统调用(其实是上一节的内容,即处理EINTR错误)
  3. SIGCHLD信号处理函数必须编写正确,使用waitpid函数

代码

  • github
    代码中sig_child两个版本都有,被注释的是错误的版本。
发布了106 篇原创文章 · 获赞 41 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_33446100/article/details/103829553