为什么读取在管道上阻塞,直到关闭写入端? [英] Why does read block on a pipe until the write end is closed?

查看:110
本文介绍了为什么读取在管道上阻塞,直到关闭写入端?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过编写以下popen类型的函数来增强对与forkexecdup相关的内容的理解,并重定向stdin/stdout/stderr :

I'm trying to bolster my understanding of things related to fork, exec, dup, and redirecting stdin/stdout/stderr by writing the following popen-type function:

// main.c
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define INVALID_FD (-1)

typedef enum PipeEnd {
  READ_END  = 0,
  WRITE_END = 1
} PipeEnd;

typedef int Pipe[2];

/** Encapsulates information about a created child process. */
typedef struct popen2_t {
  bool  success;  ///< true if the child process was spawned.
  Pipe  stdin;    ///< parent -> stdin[WRITE_END] -> child's stdin
  Pipe  stdout;   ///< child -> stdout[WRITE_END] -> parent reads stdout[READ_END]
  Pipe  stderr;   ///< child -> stderr[WRITE_END] -> parent reads stderr[READ_END]
  pid_t pid;      ///< child process' pid
} popen2_t;

/** dup2( p[pe] ) then close and invalidate both ends of p */
static void dupFd( Pipe p, const PipeEnd pe, const int fd ) {
  dup2( p[pe], fd);
  close( p[READ_END] );
  close( p[WRITE_END] );
  p[READ_END] = INVALID_FD;
  p[WRITE_END] = INVALID_FD;
}

popen2_t popen2( const char* cmd ) {
  popen2_t r = { false, { INVALID_FD, INVALID_FD } };

  if ( -1 == pipe( r.stdin ) ) { goto end; }
  if ( -1 == pipe( r.stdout ) ) { goto end; }
  if ( -1 == pipe( r.stderr ) ) { goto end; }

  switch ( (r.pid = fork()) ) {
    case -1: // Error
      goto end;

    case 0: // Child process
      dupFd( r.stdin, READ_END, STDIN_FILENO );
      dupFd( r.stdout, WRITE_END, STDOUT_FILENO );
      dupFd( r.stderr, WRITE_END, STDERR_FILENO );

      {
        char* argv[] = { "sh", "-c", (char*)cmd, NULL };

        if ( -1 == execvp( argv[0], argv ) ) { exit(0); }
      }
  }

  // Parent process
  close( r.stdin[READ_END] );
  r.stdin[READ_END] = INVALID_FD;
  close( r.stdout[WRITE_END] );
  r.stdout[WRITE_END] = INVALID_FD;
  close( r.stderr[WRITE_END] );
  r.stderr[WRITE_END] = INVALID_FD;
  r.success = true;

end:
  if ( ! r.success ) {
    if ( INVALID_FD != r.stdin[READ_END] ) { close( r.stdin[READ_END] ); }
    if ( INVALID_FD != r.stdin[WRITE_END] ) { close( r.stdin[WRITE_END] ); }
    if ( INVALID_FD != r.stdout[READ_END] ) { close( r.stdout[READ_END] ); }
    if ( INVALID_FD != r.stdout[WRITE_END] ) { close( r.stdout[WRITE_END] ); }
    if ( INVALID_FD != r.stderr[READ_END] ) { close( r.stderr[READ_END] ); }
    if ( INVALID_FD != r.stderr[WRITE_END] ) { close( r.stderr[WRITE_END] ); }

    r.stdin[READ_END] = r.stdin[WRITE_END] =
      r.stdout[READ_END] = r.stdout[WRITE_END] =
      r.stderr[READ_END] = r.stderr[WRITE_END] = INVALID_FD;
  }

  return r;
}

int main( int argc, char* argv[] ) {
  popen2_t p = popen2( "./child.out" );

  {
    int status = 0;


    sleep( 2 );

    {
      char buf[1024] = { '\0' };

      read( p.stdout[READ_END], buf, sizeof buf );
      printf( "%s", buf );
    }

    //pid_t wpid = waitpid( p.pid, &status, 0 );
    //return wpid == p.pid && WIFEXITED( status ) ? WEXITSTATUS( status ) : -1;
  }
}

// child.c
#include <stdio.h>
#include <unistd.h>

int main( int argc, char* argv[] ) {
  printf( "%s:%d\n", __FILE__, __LINE__ );
  sleep( 1 );
  printf( "%s:%d\n", __FILE__, __LINE__ );
  sleep( 1 );
  printf( "%s:%d\n", __FILE__, __LINE__ );
  sleep( 1 );
  printf( "%s:%d\n", __FILE__, __LINE__ );
  sleep( 1 );
  return 0;
}

编译和执行:

$ gcc --version && gcc -g ./child.c -o ./child.out && gcc -g ./main.c && ./a.out
gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./child.c:6
./child.c:8
./child.c:10
./child.c:12
$

我的问题是关于read()的-我不太理解为什么read()在子进程完成之前似乎被阻塞了(从而关闭了管道的末端)?

My question is about the read() - I don't quite grok why does the read() is seemingly block until the child process has completed (thereby closing its end of the pipe)?

这是巧合吗?您可以看到我已经尝试过制作"主进程使用sleep( 2 )语句在子进程执行的中间进行读取.

Is it coincidence? You can see I've tried to "make" the main process do its read in the middle of the child process' execution with the sleep( 2 ) statement.

总共,子进程将50个字符转储到其(重定向的)stdout. 主进程是否有可能在孩子执行过程中执行read()并且仅读取其中50个字符中的N个,因此主进程的printf()不会全部打印整个子进程有四行?

In total, the child process dumps 50 chars to its (redirected) stdout. Isn't it possible that the main process might do its read() in the middle of the child's execution and read only N of 50 of those chars, and that therefore the main process' printf() wouldn't print all four lines from the child process in its entirety?

(从功能上讲,一切都很好-我的问题是更好地理解read())

(Functionality-wise, everything is fine - my question is to better my understanding of read())

推荐答案

默认情况下,stdout在未写入终端时会完全缓冲.因此,在刷新缓冲区之前,子对象中的printf()调用不会将任何内容写入管道.当缓冲区已满(可能是1K或4K字节)或进程退出时,就会发生这种情况.

By default, stdout is fully buffered when it's not writing to a terminal. So nothing is being written to the pipe by your printf() calls in the child until the buffer is flushed. This will happen when the buffer fills (probably 1K or 4K bytes) or the process exits.

您可以立即使用fflush(stdout);刷新缓冲区.在每个printf()调用之后添加该代码,您就可以在父级中读取它们,而无需等待进程退出.

You can flush the buffer immediately with fflush(stdout);. Add that after each of your printf() calls and you'll be able to read them in the parent without waiting for the process to exit.

这篇关于为什么读取在管道上阻塞,直到关闭写入端?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆