叉形过程中禁用标准输出缓冲 [英] Disabling stdout buffering of a forked process
问题描述
我的C / C ++语言派生一个子进程,复制的标准输入/输出入管端并调用execvp。写了一个code
一切工作正常(即从标准输入/ ERR /输出输出由父进程捕获)
的问题是,子标准输出进行缓冲。
所以,如果孩子code是这样的:
的printf(请输入任意键,按回车:\\ n);
与fgets(线);
的printf(读:%S \\ n,行);
出口(0);
在父进程,我不看行中输入任何键: - 这将是刷新程序调用后才能出口(其中自动刷新标准输出缓冲),或刷新(标准输出的显式调用)添加
我做了一些研究,并尝试添加一个呼叫通过添加一个调用禁用标准输出缓冲:
setvbuf用来(标准输出,NULL,_IONBF,0);
只是调用父进程execvp(...)之前
因此相关code现在看起来是这样的:
INT RC = fork()的;
如果(RC == 0){
//子进程
如果(workingDirectory.IsEmpty()==假){
wxSetWorkingDirectory(工作目录);
}
INT stdin_file =的fileno(标准输入);
INT stdout_file =的fileno(标准输出);
INT stderr_file =的fileno(标准错误); //替换标准输入/输出我们的管端
dup2(stdin_pipe_read,stdin_file);
关闭(stdin_pipe_write); dup2(stdout_pipe_write,stdout_file);
dup2(stdout_pipe_write,stderr_file);
关闭(stdout_pipe_read); setvbuf用来(标准输出,NULL,_IONBF,0); //执行过程
execvp(的argv [0],argv的);
出口(0);}
没有运气。
任何想法?
编辑:
下面是父code的样品,唯一需要改变的是路径子可执行文件:
的#include<&unistd.h中GT;
#包括LT&;&signal.h中GT;
#包括LT&; SYS / types.h中>
#包括LT&; SYS / select.h>
#包括LT&;&errno.h中GT;
#包括LT&; SYS / wait.h>
#包括LT&;串GT;
#包括LT&;&string.h中GT;
#包括LT&;&cstdio GT;静态INT read_handle(-1);
静态将为pid_t PID;布尔read_from_child(标准::字符串&安培; BUFF){
FD_SET RS;
的timeval超时; memset的(安培; RS,0,sizeof的(RS));
FD_SET(read_handle,&安培; RS);
timeout.tv_sec = 1; // 1秒
timeout.tv_usec = 0; INT RC =选择(read_handle + 1,&安培; RS,NULL,NULL,&安培;超时);
如果(RC == 0){
// 暂停
返回true; }否则如果(RC大于0){
//有东西可以读
字符缓冲区[1024 * 64]; //我们的读取缓冲区
memset的(缓冲液,0,sizeof的(缓冲液));
如果(读(read_handle,缓冲器,的sizeof(缓冲液))大于0){
buff.clear();
buff.append(缓冲液);
返回true;
} 返回false;
}其他{/ * == 0 * /
如果(RC == EINTR || RC == EAGAIN){
返回true;
} //处理终止
INT状态(0);
waitpid函数(PID,和放大器;状态,0);
返回false;
}
}无效的execute(){
的char * argv的[] = {/家/埃兰/ DEVL / TestMain /调试/ TestMain,NULL};
INT ARGC = 1; INT与filedes [2];
INT filedes2 [2]; //创建一个管道
INT D组;
D =管(与filedes);
D =管道(filedes2); INT stdin_pipe_write =与filedes [1];
INT stdin_pipe_read =与filedes [0]; INT stdout_pipe_write = filedes2 [1];
INT stdout_pipe_read = filedes2 [0]; INT RC = fork()的;
如果(RC == 0){ //子进程
INT stdin_file =的fileno(标准输入);
INT stdout_file =的fileno(标准输出);
INT stderr_file =的fileno(标准错误); //替换标准输入/输出我们的管端
dup2(stdin_pipe_read,stdin_file);
关闭(stdin_pipe_write); dup2(stdout_pipe_write,stdout_file);
dup2(stdout_pipe_write,stderr_file);
关闭(stdout_pipe_read); setvbuf用来(标准输出,NULL,_IONBF,0); //执行过程
execvp(的argv [0],argv的); }否则如果(RC℃,){
PERROR(叉);
返回; }其他{
//父
标准::字符串BUF;
read_handle = stdout_pipe_read;
而(read_from_child(BUF)){
如果(buf.empty()==假){
的printf(收到:%S \\ n,buf.c_str());
}
buf.clear();
}
}
}INT主(INT ARGC,字符** argv的){
执行();
返回0;
}
其实,它挣扎了一下之后,好像对这个问题唯一的办法就是通过使'父'的过程pretending是一个终端使用的操作系统伪终端API调用。
每个人都应该称之为openpty()'叉()之前,孩子code里面,他应该叫'login_tty(奴隶),那么从正在成为标准输入/输出和标准错误。
通过pretending到终端,标准输出的缓冲自动设置为行模式(遇到的\\ n发生时即刷新)。家长应使用主描述符看书/与子进程写作。
修改父code(万一有人永远都需要这一点):
的#include<&unistd.h中GT;
#包括LT&;&signal.h中GT;
#包括LT&; SYS / types.h中>
#包括LT&; SYS / select.h>
#包括LT&;&errno.h中GT;
#包括LT&; SYS / wait.h>
#包括LT&;串GT;
#包括LT&;&string.h中GT;
#包括LT&;&cstdio GT;
#包括LT&;&pty.h GT;
#包括LT&;&utmp.h GT;
静态INT read_handle(-1);
静态将为pid_t PID;布尔read_from_child(标准::字符串&安培; BUFF){
FD_SET RS;
的timeval超时; memset的(安培; RS,0,sizeof的(RS));
FD_SET(read_handle,&安培; RS);
timeout.tv_sec = 1; // 1秒
timeout.tv_usec = 0; INT RC =选择(read_handle + 1,&安培; RS,NULL,NULL,&安培;超时);
如果(RC == 0){
// 暂停
返回true; }否则如果(RC大于0){
//有东西可以读
字符缓冲区[1024 * 64]; //我们的读取缓冲区
memset的(缓冲液,0,sizeof的(缓冲液));
如果(读(read_handle,缓冲器,的sizeof(缓冲液))大于0){
buff.clear();
buff.append(缓冲液);
返回true;
} 返回false;
}其他{/ * == 0 * /
如果(RC == EINTR || RC == EAGAIN){
返回true;
} //处理终止
INT状态(0);
waitpid函数(PID,和放大器;状态,0);
返回false;
}
}无效的execute(){
的char * argv的[] = {/家/埃兰/ DEVL / TestMain /调试/ TestMain,NULL};
INT ARGC = 1; INT主,从;
openpty(安培;主,和放大器;奴隶,NULL,NULL,NULL); INT RC = fork()的;
如果(RC == 0){
login_tty(奴隶);
接近(主); //执行过程
如果(execvp(argv的[0],argv的)!= 0)
PERROR(execvp); }否则如果(RC℃,){
PERROR(叉);
返回; }其他{
//父
标准::字符串BUF;
接近(从); read_handle =主;
而(read_from_child(BUF)){
如果(buf.empty()==假){
的printf(收到:%S,buf.c_str());
}
buf.clear();
}
}
}INT主(INT ARGC,字符** argv的){
执行();
返回0;
}
I wrote a code in C/C++ which forks a child process, duplicates the stdin/stdout into a pipe ends and calls execvp.
Everything is working fine (i.e. the output from stdin/err/out is captured by the parent process)
The problem is that the child stdout is buffered.
so if the child code looks like this:
printf("Enter any key and hit ENTER:\n");
fgets(line);
printf("read: %s\n", line);
exit(0);
In the parent process I don't see the line 'Enter any key:' - it will be "flushed" only after the program calls exit (which auto flushes the stdout buffer) or an explicit call to 'flush(stdout)' is added
I did some research and tried adding a call to disable the stdout buffering by adding a call to:
setvbuf(stdout, NULL, _IONBF, 0); just before calling execvp(...) in the parent process
so the relevant code looks now like this:
int rc = fork();
if ( rc == 0 ) {
// Child process
if(workingDirectory.IsEmpty() == false) {
wxSetWorkingDirectory( workingDirectory );
}
int stdin_file = fileno( stdin );
int stdout_file = fileno( stdout );
int stderr_file = fileno( stderr );
// Replace stdin/out with our pipe ends
dup2 ( stdin_pipe_read, stdin_file );
close( stdin_pipe_write );
dup2 ( stdout_pipe_write, stdout_file);
dup2 ( stdout_pipe_write, stderr_file);
close( stdout_pipe_read );
setvbuf(stdout, NULL, _IONBF, 0);
// execute the process
execvp(argv[0], argv);
exit(0);
}
With no luck.
Any ideas?
EDIT:
here is a sample of the parent code, the only thing needs changing is the path to the child executable:
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/wait.h>
#include <string>
#include <string.h>
#include <cstdio>
static int read_handle(-1);
static pid_t pid;
bool read_from_child(std::string& buff) {
fd_set rs;
timeval timeout;
memset(&rs, 0, sizeof(rs));
FD_SET(read_handle, &rs);
timeout.tv_sec = 1; // 1 second
timeout.tv_usec = 0;
int rc = select(read_handle+1, &rs, NULL, NULL, &timeout);
if ( rc == 0 ) {
// timeout
return true;
} else if ( rc > 0 ) {
// there is something to read
char buffer[1024*64]; // our read buffer
memset(buffer, 0, sizeof(buffer));
if(read(read_handle, buffer, sizeof(buffer)) > 0) {
buff.clear();
buff.append( buffer );
return true;
}
return false;
} else { /* == 0 */
if ( rc == EINTR || rc == EAGAIN ) {
return true;
}
// Process terminated
int status(0);
waitpid(pid, &status, 0);
return false;
}
}
void execute() {
char *argv[] = {"/home/eran/devl/TestMain/Debug/TestMain", NULL};
int argc = 1;
int filedes[2];
int filedes2[2];
// create a pipe
int d;
d = pipe(filedes);
d = pipe(filedes2);
int stdin_pipe_write = filedes[1];
int stdin_pipe_read = filedes[0];
int stdout_pipe_write = filedes2[1];
int stdout_pipe_read = filedes2[0];
int rc = fork();
if ( rc == 0 ) {
// Child process
int stdin_file = fileno( stdin );
int stdout_file = fileno( stdout );
int stderr_file = fileno( stderr );
// Replace stdin/out with our pipe ends
dup2 ( stdin_pipe_read, stdin_file );
close( stdin_pipe_write );
dup2 ( stdout_pipe_write, stdout_file);
dup2 ( stdout_pipe_write, stderr_file);
close( stdout_pipe_read );
setvbuf(stdout, NULL, _IONBF, 0);
// execute the process
execvp(argv[0], argv);
} else if ( rc < 0 ) {
perror("fork");
return;
} else {
// Parent
std::string buf;
read_handle = stdout_pipe_read;
while(read_from_child(buf)) {
if(buf.empty() == false) {
printf("Received: %s\n", buf.c_str());
}
buf.clear();
}
}
}
int main(int argc, char **argv) {
execute();
return 0;
}
Actually, after struggling with it a bit, it seems like the only solution to this problem is by making the 'parent' process pretending to be a terminal using the OS pseudo terminal API calls.
One should call 'openpty()' before the fork(), and inside the child code, he should call 'login_tty(slave)' the slave is then becoming the stdin/out and stderr.
By pretending to a terminal, the buffering of stdout is automatically set to 'line mode' (i.e. flush occurs when \n is encountered). The parent should use the 'master' descriptor for readin/writing with the child process.
The modified parent code (in case anyone will ever need this):
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/wait.h>
#include <string>
#include <string.h>
#include <cstdio>
#include <pty.h>
#include <utmp.h>
static int read_handle(-1);
static pid_t pid;
bool read_from_child(std::string& buff) {
fd_set rs;
timeval timeout;
memset(&rs, 0, sizeof(rs));
FD_SET(read_handle, &rs);
timeout.tv_sec = 1; // 1 second
timeout.tv_usec = 0;
int rc = select(read_handle+1, &rs, NULL, NULL, &timeout);
if ( rc == 0 ) {
// timeout
return true;
} else if ( rc > 0 ) {
// there is something to read
char buffer[1024*64]; // our read buffer
memset(buffer, 0, sizeof(buffer));
if(read(read_handle, buffer, sizeof(buffer)) > 0) {
buff.clear();
buff.append( buffer );
return true;
}
return false;
} else { /* == 0 */
if ( rc == EINTR || rc == EAGAIN ) {
return true;
}
// Process terminated
int status(0);
waitpid(pid, &status, 0);
return false;
}
}
void execute() {
char *argv[] = {"/home/eran/devl/TestMain/Debug/TestMain", NULL};
int argc = 1;
int master, slave;
openpty(&master, &slave, NULL, NULL, NULL);
int rc = fork();
if ( rc == 0 ) {
login_tty(slave);
close(master);
// execute the process
if(execvp(argv[0], argv) != 0)
perror("execvp");
} else if ( rc < 0 ) {
perror("fork");
return;
} else {
// Parent
std::string buf;
close(slave);
read_handle = master;
while(read_from_child(buf)) {
if(buf.empty() == false) {
printf("Received: %s", buf.c_str());
}
buf.clear();
}
}
}
int main(int argc, char **argv) {
execute();
return 0;
}
这篇关于叉形过程中禁用标准输出缓冲的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!