【问题标题】:C program using pipes hangs when trying to terminate尝试终止时使用管道的 C 程序挂起
【发布时间】:2019-12-04 06:16:36
【问题描述】:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>

#define MSGSIZE 64

char msgbuf[MSGSIZE];

int main() {
  int p1[2];
  int p2[2];
  int nread;
  int choice = 0;
  pid_t child_a, child_b;
  if (pipe(p1) == -1) {
    printf("error in creating pipe\n");
    exit(-1);
  }

  if (pipe(p2) == -1) {
    printf("error in creating pipe\n");
    exit(-1);
  }

  child_a = fork();
  if (child_a == 0) {
    while (1) {
      dup2(p1[0], STDIN_FILENO);
      read(STDIN_FILENO, msgbuf, MSGSIZE);
      printf("%d receives message: %s\n", getpid(), msgbuf);
      close(p1[0]);
      close(p1[1]);
    }
  } else {
    child_b = fork();
    if (child_b == 0) {
      while (1) {
        dup2(p2[0], STDIN_FILENO);
        read(STDIN_FILENO, msgbuf, MSGSIZE);
        printf("%d receives message: %s\n", getpid(), msgbuf);
        close(p2[0]);
        close(p2[1]);
      }
    } else {
      while (1) {
        printf("<child_to_receive_msg> <message>\n");
        scanf("%d %s", &choice, msgbuf);
        switch (choice) {
        case 1:
          usleep(250);
          write(p1[1], msgbuf, MSGSIZE);
          break;
        case 2:
          usleep(250);
          write(p2[1], msgbuf, MSGSIZE);
          break;
        default:
          printf("Process does not exist");
          break;
        case -1:
          close(p1[0]);
          close(p2[0]);
          printf("parent waiting");
          wait(NULL);
          exit(0);
        }
      }
    }
  }
  return 0;
}

在上面的程序中,我有一个父进程创建了属于同一个父进程的两个子进程。用户写入父进程,该进程通过管道传输消息以供子 1 或子 2 读取。除非用户输入 -1,否则它会一直这样做。

问题是我的 switch 语句中的 case 没有被执行,而是程序挂起。我想我在正确的地方关闭了管道。

【问题讨论】:

    标签: c operating-system switch-statement pipe fork


    【解决方案1】:

    您需要向您的子进程发送一些信号以通知然后在等待它们退出之前终止。您应该定义一些预定义的消息,这意味着孩子终止的时间。检查下面的代码。这里预定义的消息是"-1"。您应该选择自己的,它与您的应用程序的真实数据不冲突。

    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    #define MSGSIZE 64
    
    char msgbuf[MSGSIZE];
    
    int main() {
      int p1[2];
      int p2[2];
      int nread;
      int choice = 0;
      pid_t child_a, child_b;
      if (pipe(p1) == -1) {
        printf("error in creating pipe\n");
        exit(-1);
      }
    
      if (pipe(p2) == -1) {
        printf("error in creating pipe\n");
        exit(-1);
      }
    
      child_a = fork();
      if (child_a == 0) {
        while (1) {
          dup2(p1[0], STDIN_FILENO);
          read(STDIN_FILENO, msgbuf, MSGSIZE);
          printf("%d receives message: %s\n", getpid(), msgbuf);
          close(p1[0]);
          close(p1[1]);
          if (strcmp(msgbuf, "-1") == 0) { // check if time to end
            break;
          }
        }
      } else {
        child_b = fork();
        if (child_b == 0) {
          while (1) {
            dup2(p2[0], STDIN_FILENO);
            read(STDIN_FILENO, msgbuf, MSGSIZE);
            printf("%d receives message: %s\n", getpid(), msgbuf);
            close(p2[0]);
            close(p2[1]);
            if (strcmp(msgbuf, "-1") == 0) {  // check if time to end
              break;
            }
          }
        } else {
          while (1) {
            printf("<child_to_receive_msg> <message>\n");
            scanf("%d %s", &choice, msgbuf);
            switch (choice) {
            case 1:
              usleep(250);
              write(p1[1], msgbuf, MSGSIZE);
              break;
            case 2:
              usleep(250);
              write(p2[1], msgbuf, MSGSIZE);
              break;
            default:
              printf("Process does not exist\n");
              break;
            case -1:
              strcpy(msgbuf, "-1");
              write(p1[1], msgbuf, MSGSIZE); // send message to end
              close(p1[0]);
              close(p2[0]);
              printf("parent waiting\n");
              wait(NULL);
              exit(0);
            }
          }
        }
      }
      return 0;
    }
    

    【讨论】:

      【解决方案2】:

      首先,您需要开始执行错误检查。检查您拨打的电话的手册页。在代码中添加检查以检测错误。当他们返回错误时,使用perror 和exit(EXIT_FAILURE);。


      其次,您需要开始注意read 和write 返回的值,因为它们可能比预期的要少。这些需要循环调用。

      例如,对于read,您可以使用以下代码:

      #include <errno.h>
      #include <limits.h>
      
      // Returns the number of bytes read.
      // EOF was reached if the number of bytes read is less than requested.
      // On error, returns -1 and sets errno.
      ssize_t read_fixed_amount(int fd, char *buf, size_t size) {
         if (size > SSIZE_MAX) {
            errno = EINVAL;
            return -1;
         }
      
         ssize_t bytes_read = 0;
         while (size > 0) {
            ssize_t rv = read(fd, buf, size); 
            if (rv < 0)
               return -1;
            if (rv == 0)
               return bytes_read;
      
            size -= rv;
            bytes_read += rv;
            buf += rv;
         }
      
         return bytes_read;
      }
      

      它会像这样使用:

      ssize_t bytes_read = read_fixed_amount(fd, buf, size);
      if (bytes_read < 0) {
         perror("read");
         exit(EXIT_FAILURE);
      }
      
      if (bytes_read == 0) {
         printf("EOF reached\n");
         exit(EXIT_SUCCESS);
      }
      
      if (bytes_read != size) {
         fprintf(stderr, "read: Premature EOF.\n");
         exit(EXIT_FAILURE);
      }
      

      第三,只有在管道写入端的所有文件描述符都已关闭后,从管道读取才会返回 EOF。

      在fork之后,父母应该做

      close(p1[0]); 
      close(p2[0]);
      

      在叉子之后,孩子 1 应该做

      close(p1[1]);
      close(p2[0]);
      close(p2[1]);
      

      在叉子之后,孩子 2 应该做

      close(p1[0]); 
      close(p1[1]);
      close(p2[1]);
      

      第四,还有这个怪物:

      while (1) {
         dup2(p1[0], STDIN_FILENO);
         read(STDIN_FILENO, msgbuf, MSGSIZE); 
         ...
         close(p1[0]); 
         close(p1[1]);
      }
      

      真的吗?无限循环。尝试反复使 STDIN 成为 p1[0] 的副本。复制一个封闭的描述符。

      这应该出现在循环之前:

      dup2(p1[0], STDIN_FILENO);
      close(p1[0]);
      

      或者您可以跳过这两个电话,直接从p1[0] 读取而不是STDIN_FILENO。

      至于无限循环,又回到了第二点。检查read返回的值。


      第五,你只等一个孩子完成,但还有两个孩子要等。您需要拨打wait 两次。

      【讨论】:

      • 谢谢我按照你的建议做了,但是当我输入 -1 时程序仍然挂起。它不会因为正确的输入而挂起,它会正确发送和检索消息。
      • read 是否重复返回 0 或 -1(无限循环),还是 read 阻塞(并非所有管道写入端的描述符都已关闭)?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-07
      • 1970-01-01
      • 2017-10-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多