您的位置:首页 > 运维架构 > Linux

linux系统下 fork()系统调用: 关于父子进程缓存问题的小坑

2017-06-24 11:15 387 查看

linux系统下 fork()系统调用, 关于父子进程缓存问题的小坑

1. 首先看一个简单示例程序如下

#include <stdio.h>
#include <unistd.h>
#include <assert.h>

int main(int argc, char **argv)
{
printf("before fork: in father...\n");

pid_t child = fork();
assert( child >= 0 );

if ( 0 == child )
printf("in child...\n");

if ( child > 0 )
printf("after  fork: in father...\n");
}


在我本机测试, 输出如下:

before fork: in father...
after  fork: in father...
in child...


输出和我们预想一致. 一共输出3行. 目前一切正常.

请继续往下看.

2. 这个示例程序和第一个基本相同, 但是输出时, 换行符”\n”略有区别.

#include <stdio.h>
#include <unistd.h>
#include <assert.h>

int main(int argc, char **argv)
{
// 注意这里没有换行, 多加了几个空格, 使得打印容易识别.
printf("before fork: in father...    ");

pid_t child = fork();
assert( child >= 0 );

if ( 0 == child )
printf("in child...\n");

if ( child > 0 )
printf("after  fork: in father...\n");
}


程序输出如下:

before fork: in father...    after  fork: in father...
before fork: in father...    in child...


这次输出就不在我们意料之中了!!!

“before fork: in father… ”

是在fork()调用之前的父进程中调用输出的.

但是竟然在子进程中也输出了 !!!

感觉好像是在fork之前父进程中的printf打印的字符, 被copy到子进程中了?

没错, 正是如此.原理如下:

我们这次在fork之前printf打印语句时, 没有带”\n”参数( 终端是行缓存的. )

所以这条语句执行后, 打印信息并没有被立刻输出到屏幕.而是缓存起来了.

终端stdout是行缓存, 输出只有在遇到’\n’字符,或者输出超过缓存长度时, 才会刷新缓存–真正写入文件( 这里的写文件就是终端stdout )

这样当调用fork() 函数时, 父进程中的输出缓存还没有刷新到文件.

fork()调用除了会复制父进程的所有已打开文件描述符, 还会复制父进程的缓存到子进程中.

所以父进程的缓存 “before fork: in father… ” 就被copy到子进程中了..

缓存复制到子进程空间后, 就像子进程自己调用了printf的效果一样.

结果程序输出就出现了上面诡异的一幕.

至于程序1为什么没有出现这样的一幕, 因为在fork之前父进程printf 带了’\n’, stdout是行缓存, 遇到’\n’时就会刷新输出到stdout的缓存.

在fork时, 父进程的缓存已刷新到文件.缓存被删除. 所以不会copy到子进程空间.

3. 这次对程序1 输出重定向到文件, 查看结果.

重定向输出到fork.log中, 代码如下:

$   ./fork-out  > fork.log
$   cat fork.log


输出如下:

before fork: in father...
after  fork: in father...
before fork: in father...
in child...


咦等等, 程序1 不是正常吗, 为什么我仅仅重定向了一次, 结果就成这样了.

不是说’\n’ 在终端stdout是行缓存的吗, 遇到’\n’就会刷新输出缓存!

是的, 之前说的没错. 请注意:”终端stdout是行缓存的”, 但是文件重定向之后, 这时候输出stdout文件已经不是终端了, 而是重定向到了fork.log文件.

fork.log 是一个普通文件, 普通文件的缓存是基于长度的, 也就是说输出到普通文件的数据, 在缓存后, 只有达到了缓存上限, 或者手动调用 flush/sync 等系统调用刷新, 才会清空缓存并刷新到文件中.

而 “行缓存” 是只有终端stdout才有的.

同时注意终端stderr是没有缓存的, 因为输出到stderr的信息都是敏感信息. 所以系统并不缓存.

但是在stderr重定向到文件后, 也会遇到相同的缓存级别改变的情况. 读者感兴趣请自行尝试.

4. 所以正确做法是:

调用fork程序前, 手动调用flush/fflush函数手动刷新输出缓存.

避免重定向后出现父子进程诡异的输出copy问题.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux fork printf 缓存 拷贝