这里先看两个例子:
1) 源码如下:
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char *argv[])
{
pid_t pid;
fprintf (stderr, "this is stderr string!\n");
printf ("this is stdout string!\n");
if ((pid = fork()) == 0)
{
}
else if (pid > 0)
{
}
else
{
fprintf (stderr, "fork error! errno: %d errstr: %s\n", errno, strerror(errno));
}
exit(0);
}
运行:
问:这里 printf ("this is stdout string!\n");
为什么会在文件中打印两句出来?而在不重定向的情况下只打印一次;
2) 这是一个进程间的 pipe
通信,代码如下:
// main_process.c
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int pipe_fd[2];
pid_t pid;
char pipe_buf[1024];
int ret;
if (pipe(pipe_fd) < 0)
fprintf(stderr, "pipe failed!\n");
if ((pid = fork()) == 0) /* child */
{
close(pipe_fd[0]);
dup2(pipe_fd[1], STDOUT_FILENO);
close(pipe_fd[1]);
if (execl("./child_process", "./child_process", NULL) == -1)
fprintf (stderr, "execl faild!\n");
exit(0);
}
else if (pid > 0) /* parent */
{
close(pipe_fd[1]);
while ((ret = read(pipe_fd[0], pipe_buf, sizeof(pipe_buf) - 1)) > 0)
{
pipe_buf[ret] = '\0';
printf ("%s\n", pipe_buf);
fflush(stdout);
}
close(pipe_fd[0]);
}
else
fprintf (stderr, "fork failed!\n");
exit(0);
}
// child_process.c
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int i;
for (i=0; i<100; i++)
{
printf ("counter: %d\n", i);
sleep(1);
}
exit(0);
}
我们的本意是将 child 进程的输出重定向,然后从 main
进程中接收并打印出来,根据程序也就是 1s 打印一次计数,但这个程序在运行中会出现什么问题呢?main
进程的 read
会一直阻塞,直到 child 进程将 pipe_fd[1]
写满以后才会有返回打印出。
其实,这两个问题都源于一个原因,那就是缓冲的原因。
在输入输出的缓冲中,我们知道有三种:无缓冲、行缓冲和块缓冲(也叫全缓冲)。
对于标准输出/标准错误输出,默认的缓冲方式是:标准错误输出是无缓冲输出,也就是只要给stderr写数据,马上就会送出;标准输出分两种情况,如果输出定向的是到设备终端(tty),那么是行缓冲,如果是其他设备/文件,则是块缓冲。
这就可以解释上面两种情况了:
- 当重定向到文件输出时,printf 的内容会在缓冲中,fork 时会将当前的缓冲区 clone 一个,而且内容不变,但进程结束的时候,系统会自动 flush 缓冲区,所以子进程和服进程均将缓冲区的内容输出,所以出现了两次同样的 printf 的内容;
- 主进程将子进程的 stdout 重定向到 pipe 输出,所以 child 就会块缓冲,所以并不是每秒去 flush 一下 buffer,而是当 buffer 满或者 child 进程关闭时 flush,故 main 进程的 read 执行情况就不是我们的本意了。
弄清楚了原因,解决这个问题也有两种方法:
- 在
printf
后,使用fflush(stdout)
手动 flush 缓冲区;
- 使用
setbuf
或setvbuf
设置stdout
的缓冲方式;
小结:
由于 stdout
在定向的位置不一样会有不一样的缓冲方式,所以在写被调用的子进程的时候一定要注意这个问题,最好在使用 stdout
的地方使用 fflush
进行手动 flush。