前言

呵呵 这是一个 之前在一个 linux 讨论群里面看到的一个问题 

还是 挺有意思的 当时就记录了一下 

然后 准备 后面调试一下 记录一下 

大致的情况就是 程序 fork 之前有一个输出, fork 之后有一个输出, 然后 主要的情况是 在 fork 之前的这个输出是否有 回车, 居然 极大的影响到了 最终程序的输出 

测试用例 

测试用例 很简单, 就是 一个 fork, fork 一前一后 一个输出 

#include "stdio.h"

int main(int argc, char** argv) {

    printf(" before fork %d", getpid());

    int childPid = fork();

    printf(", after fork %d \n", getpid());

    return 0;

}

如果是 before fork 的输出 带回车, 输出如下 

按照我们正常的人脑去想, 这个输出 还是比较符合 程序的代码逻辑 

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_子进程

如果是 before fork 的输出 不带回车, 输出如下 

可以明显看到的是 和上面不同?, 主进程 输出还算是比较正常 

但是 子进程的输出 为什么 也带了一个 "before fork" ?? 呵呵 这就和 printf 的缓冲 + linux 的 cow 有关系了 

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_c_02

问题的调试 

首先分析一下 两种情况 

带换行回车的情况, printf 中如果发现带换行回车 会直接输出这一行的数据, 父进程跑到 "before fork" 的时候讲缓冲区的数据已经输出输出了, 然后 之后进行 fork, 之后 父子进程 分别输出 "after fork" 

不带换行回车的情况, printf 中如果发现不带换行回车, 并且又没有超出缓冲区 此时缓冲区的数据为 "before fork", 然后 之后进行 fork, 之后 父子进程 分别往缓冲区 增加了 "after fork", 然后 最后的时候一起输出, 得到的就是 我们看到的 奇怪的现象 

然后 我们这里 来调试一下, 看一下 真实的情况 和 我们分析的逻辑情况 

带回车的情况

将 "before fork" 添加到缓冲区, 因为携带了 回车, 因此 这次会将缓冲区的待输出的数据输出到设备 

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_glibc_03

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_数据_04

fork 之后, 缓冲区为空, 重新将 "after fork" 输出到缓冲区, 然后 这次携带的有 回车, 也会输出到设备 

然后 子进程的情况 和父进程的情况一样, 因为 printf缓冲区 均是空的, fork 之前 printf 的输出已经输出到了设备, 清空了缓冲区 

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_c_05

不带回车的情况

将 "before fork" 添加到缓冲区, 因为不携带 回车, 并且缓冲区 还装得下, 因此数据没有 刷到设备 

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_c_06

fork 了之后, 将 "after fork" 输出到缓冲区, 然后 "after fork" 中携带了 回车, 开始将 数据输出到设备 

然后 子进程这边, 这个缓冲区对应的页面 需要产生写操作, 然后 linux 中物理页的 cow 拿到一个自己独享的可写的 物理页, 然后 将 "after fork" 输出到缓冲区, 然后 "after fork" 中携带了 回车, 开始将 数据输出到设备 

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_glibc_07

46 printf 标准输出是否有回车 影响 fork 之后子进程的输出_glibc_08