c-为什么读在管道上阻塞,直到写端关闭

  • 本文关键字:管道 c linux pipe fork stdout
  • 更新时间 :
  • 英文 :


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

// 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] = { '' };
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:%dn", __FILE__, __LINE__ );
sleep( 1 );
printf( "%s:%dn", __FILE__, __LINE__ );
sleep( 1 );
printf( "%s:%dn", __FILE__, __LINE__ );
sleep( 1 );
printf( "%s:%dn", __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()在子进程完成之前似乎是阻塞的(从而关闭它的管道末端(

这是巧合吗?你可以看到,我曾试图";制造";主进程在子进程执行CCD_ 10语句的过程中进行读取。

子进程总共向其(重定向的(stdout转储50个字符主进程是否有可能在子进程执行过程中执行read(),只读取其中50个字符中的N个,因此主进程的printf((不会完整打印子进程的所有四行

(功能方面,一切都很好——我的问题是更好地理解read()(

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

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

最新更新