为什么我的代码使用管道挂起?

时间:2014-11-12 04:44:46

标签: c linux pipe posix hang

我有以下代码:

 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;
    }

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

3 个答案:

答案 0 :(得分:2)

您需要为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()真正导致你的程序挂起的原因,而不是它后来做的事情,并且你没有显示你的父进程可能正在对该管道做什么,例如。

答案 1 :(得分:0)

只需付出一点努力,就可以制作一个自包含的示例,该示例适用于通常无用的只在其中一个进程中读取和写入管道的情况

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

除了微小的差异,它与上面的@Paul Griffiths类似。

working.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,您将能够回答您自己的问题!

答案 2 :(得分:0)

您正在使内容处于僵局,首先是阅读,然后是写作。如果将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[6];

    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[3];
    fscanf(in, "%s", msg);
    printf("Parent got: %s\n", msg);

}

也不要忘记将\ n添加到fprintf中,因为fscanf将等待换行符。如您所见,对于缓冲来说,刷新会有所帮助,