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

问题描述 投票:0回答:3

我有以下代码:

 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()调用添加为调试语句,以确保我的数据已成功写入管道。但是,当我调用它时,呼叫会无限期挂起。有人可以帮我解释一下我在这里做错了什么吗?

c linux pipe posix hang
3个回答
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()确实是导致你的程序挂起的原因,而不是它后来做的事情,并且你没有显示你的父进程可能正在对该管道做什么。


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


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[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将等待换行符。正如你所看到的缓冲提到冲洗会有所帮助,

© www.soinside.com 2019 - 2024. All rights reserved.