【问题标题】:Why does my code using piping hang?为什么我使用管道的代码会挂起?
【发布时间】:2014-11-12 04:44:45
【问题描述】:

我有以下代码:

 switch(fork())
    {
        case -1:
           /*error case*/
           error = errno;
           printf("fork error(1): %s\n", strerror(error));
           break;

        case 0: //child case: execute remove_non_alpha and send result to pfd write end
            remove_non_alpha(arg_str);

            res = write(pfd[1], arg_str, arg_str_size);
            if(res != arg_str_size)
            {
                return -1;
            }

            char *arg_str2 = NULL;
            res = read(pfd[0], &arg_str2, 1); //hang happens right here

            break;

        default:
            /*parent case-- fall through*/ 
            break;
    }

pfd 是使用 pipe() 创建的。 arg_str 是一个非空的 char * 字符串,arg_str_size 是一个等于 arg_str 大小的 int。我添加了 read() 调用作为调试语句,以确保我的数据已成功写入管道。但是,当我调用它时,调用会无限期挂起。有人可以帮我解释一下我在这里做错了什么吗?

【问题讨论】:

  • 如果这是一种人们可以玩的形式,那将是一个更好的问题。理想情况下可以按原样编译。
  • 如果你想读写,你应该做两个管道。尝试将read() 值转换为指针绝对不会达到您希望的效果。您需要分配一些内存并读入,而不是读入指针。
  • @Paul: pipe(2) 制作单个 单向 管道。你可以从一端写到另一端,但另一端不能写回第一端。如果你想要一个双向管道,你需要两个管道(每个管道都有自己的读写端)。
  • @Paul:好的,我想我们在同一页上。目前尚不清楚 OP 到底打算在他的代码中做什么,因为从子进程读取和写入管道是一件不寻常的事情,而且很少是正确的事情。

标签: c linux pipe posix freeze


【解决方案1】:

你需要分配一些内存给read()到。现在你正试图read() 进入指针,而不是读入指针指向的一些内存。

话虽如此,虽然你正在做的事情不会起作用,但它不应该导致read() 阻止。您只是想read() 1 个字节,并且指针是有效内存,即使像您正在做的那样将read() 放入它没有意义。您没有显示很多代码,因此您的问题可能出在其他地方。原则上,你正在做的应该是这样的:

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    char mystring1[] = "Some output";
    char mystring2[100] = {0};

    int pfd[2];
    if ( pipe(pfd) == -1 ) {
        perror("error calling pipe");
        return EXIT_FAILURE;
    }

    if ( write(pfd[1], mystring1, sizeof mystring1) == -1 ) {
        perror("error calling write()");
        return EXIT_FAILURE;
    }

    if ( read(pfd[0], mystring2, 100) == -1 ) {
        perror("error calling read()");
        return EXIT_FAILURE;
    }

    printf("Read '%s' from pipe.\n", mystring2);

    if ( close(pfd[0]) == -1 || close(pfd[1]) == -1 ) {
        perror("error calling close");
        return EXIT_FAILURE;
    }

    return 0;
}

哪个输出:

paul@thoth:~/src/sandbox$ ./sillypipe
Read 'Some output' from pipe.
paul@thoth:~/src/sandbox$ 

您最好的策略是简化您的程序,直到遇到问题为止。例如,尚不清楚您如何证明 read() 确实是导致程序挂起的原因,而不是它稍后正在执行的操作,并且您没有显示父进程可能对该管道执行的操作,因为实例。

【讨论】:

    【解决方案2】:

    只要稍加努力,就可以制作一个独立的示例,该示例适用于通常仅在一个进程中读取和写入管道的无用情况。

    我说无用是因为拥有管道的目的通常是在不同进程之间进行通信。

    除了细微的差别,它类似于上面的@Paul Griffiths。

    工作.c

    #include <stdio.h>
    #include <stdlib.h>
    
    int go(){
    
      int pfd[2];
      const char *arg_str = "Can you read it now\n"; 
      int arg_str_size = 20;
      int res;
      char arg_str2[30];
    
      if (pipe (pfd))
        {
          fprintf (stderr, "Pipe failed.\n");
          return -999;
        }
    
      switch(fork())
        {
            case -1:
               fprintf(stderr, "fork error(1) \n");
               break;
    
            case 0: //child case: send result to pfd write end
          res = write(pfd[1], arg_str, arg_str_size);
          if(res != arg_str_size)
                {
              return -1;
                }
    
          res = read(pfd[0], &arg_str2, arg_str_size); //won't hang because unread by parent
    
          fprintf(stderr, "read: %s \n", arg_str2);
    
          break;
    
        default:
          /*parent case-- fall through*/ 
          break;
        }
     return 0;
    }
    
    void main(){
      int x = go();
    }
    

    请注意,为char arg_str2[30]; 分配了一些空间,如果相反,如在 OP 代码中,代码使用了char *arg_str2 = NULL;,这是未定义的行为,并可能导致错误信号,如 SIGSEGV(分段违规)等。

    不要做以下事情:如果父母和孩子都从同一个管道读取,一个会阻塞。

    如果您希望父母和孩子都能够读写,那么您需要两个 pipe() 调用。如果您尝试在代码中进行测试以确保正确性,请仅测试读取和写入的错误指示符,不要通过从管道读取刚刚写入的内容进行测试。 管道是一种特殊的文件,而不是普通的磁盘文件。

    写入管道的数据被写入先进先出 (FIFO) 队列,并在读取后删除。读取空管道会阻塞(等待),这看起来像是挂起。

    我们可以在这里看到这种行为。两个进程都读取管道(代码**可以但不应该**)一个进程将读取管道的内容,但另一个进程将挂起。

    hang.c

    #include <stdio.h>
    #include <stdlib.h>
    
    int go(){
    
      int pfd[2];
      const char *arg_str = "Can you read it now\n";
      int arg_str_size = 20;
      int res;
      char arg_str2[30];
    
      if (pipe (pfd))
        {
          fprintf (stderr, "Pipe failed.\n");
          return -999;
        }
    
      switch(fork())
        {
            case -1:
               fprintf(stderr, "fork error(1) \n");
               break;
    
            case 0: //child case: send result to pfd write end
          res = write(pfd[1], arg_str, arg_str_size);
          if(res != arg_str_size)
                {
              return -1;
                }
    
          res = read(pfd[0], &arg_str2, arg_str_size); 
    
          fprintf(stderr, "child read: %s \n", arg_str2);
    
          break;
    
        default:
          /*parent case-- try to read here as well (not a good idea) */ 
          res = read(pfd[0], &arg_str2, arg_str_size); 
          fprintf(stderr, "parent read: %s \n", arg_str2);
          break;
        }
     return 0;
    }
    
    void main(){
      int x = go();
    }
    

    要让孩子而不是父母阻止,请添加一些sleep() 调用,以便孩子第二次阅读,让父母在阅读后留下来。

    最后注:正如您可能想象的那样,填写示例中的其余 C 代码需要时间并且会延迟获得答案。很多时候,如果您不厌其烦地构建良好的测试用例,或者“最小完整可验证示例”MCVE,您将能够回答自己的问题!

    【讨论】:

    • 不知道为什么你一直在这个问题上被否决。
    【解决方案3】:

    您正在使内容陷入僵局,您正在做的是先阅读然后再写作。如果您将 fprintf 放在 fscanf 之前至少有一个进程,它将继续

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
    int child_to_parent[2];
    int parent_to_child[2];
    pipe(child_to_parent);
    pipe(parent_to_child);
    
    pid_t id = fork();
    
    if (id == 0) {
        close(parent_to_child[1]);
        close(child_to_parent[0]);
        FILE* out = fdopen(child_to_parent[1], "w");
        FILE* in = fdopen(parent_to_child[0], "r");
    
        char msg[7];
    
        fprintf(out, "hi\n");
        fflush(out);
    
        fscanf(in ,"%s", msg);
        printf("Child got: %s\n", msg);
    
    
        printf("Child sent: hi\n"); 
    
    } else {
        close(parent_to_child[0]);
        close(child_to_parent[1]);
        FILE* in = fdopen(child_to_parent[0], "r");
        FILE* out = fdopen(parent_to_child[1], "w");
    
        fprintf(out, "hello");
        fflush(out);
        printf("Parent sent: hello\n");
    
        char msg[4];
        fscanf(in, "%s", msg);
        printf("Parent got: %s\n", msg);
    
    }
    

    另外不要忘记将 \n 添加到 fprintf 因为 fscanf 将等待换行符。正如你看到的缓冲提到的刷新会有所帮助,

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多