APUE:fork()、exec()前用fflush()刷新缓冲区的重要性

本文详细分析了在C程序中使用fork和exec函数时,输出到控制台与文件的不同行为,重点讨论了缓冲区管理和fflush函数的作用。通过示例代码解释了如何在fork前和exec前使用fflush确保正确输出,从而避免因缓冲区未刷新导致的意外结果。
摘要由CSDN通过智能技术生成

前言

一、fork函数的使用异常

有一fork函数使用示例如下:

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

int main()
{
    pid_t pid;

    printf("[%d]:Begin!\n", getpid());

    pid = fork();
    if(pid < 0)
    {
        perror("fork()");
        exit(1);
    }
    if(pid == 0) 
    {
        
        printf("[%d]:Child is working!\n", getpid());
    }
    else
    {
        
        printf("[%d]:Parent is working!\n", getpid());
    }

    printf("[%d]:End!\n", getpid());

    exit(0);
}

根据fork函数功能,我们可以在控制台上输出以下内容:

[root@localhost process_basic]# ./fork1
[20032]:Begin!
[20032]:Parent is working!
[20032]:End!
[20033]:Child is working!
[20033]:End!

即:输出一次Begin,和两次End。这个很好理解,因为fork是在父进程输出Begin之后开启子进程的,由于子进程复制了父进程的运行进度,所以自然不会输出Begin。

但是当我们不把结果输出控制台,而是把它输出到文件中:

[root@localhost process_basic]# ./fork1 > /tmp/out

打开/tmp/out之后,显示:

[21269]:Begin!
[21269]:Parent is working!
[21269]:End!
[21269]:Begin!
[21270]:Child is working!
[21270]:End!

我们会惊讶地发现,pid为21269的父进程输出一次Begin后,后面居然又输出了一次Begin,进程号依旧是父进程的id号21269。这是为什么呢?
因为终端设备的流默认是行缓冲,而磁盘的流默认是全缓冲
当程序执行到

    printf("[%d]:Begin!\n", getpid());

的时候,如果是输出到终端设备,由于printf后面的语句有换行符,流缓冲就会被刷新,原有的缓冲区里也不会有这一条语句。当父进程fork()出子进程后,自然也不可能再多出这句。
而当我们输出到文件中,即执行的是磁盘流默认的全缓冲模式,缓冲区不会被刷新,会积累到最后一起输出。所以在这种情况下,printf语句里的换行符仅仅是一个普通的换行符而已,并不会有自动刷新缓冲区的效果,开头这条Begin语句会在父进程fork()出子进程后,连带着复制进子进程的缓冲区。所以最后当程序执行到最后统一输出的时候,会输出两条Begin,而且它的所属pid都是父进程的pid。
那么该如何修改代码呢?

二、fork前进行fllush

在程序执行fork()函数前,添加上fflush,如下所示:

int main()
{
    printf("[%d]:Begin!\n", getpid());

    fflush(NULL);

    pid = fork();

此时,我们再把程序结果输出到/tmp/out文件中,查看结果:

[root@localhost process_basic]# ./fork1 > /tmp/out
[root@localhost process_basic]# cat /tmp/out
[21659]:Begin!
[21659]:Parent is working!
[21659]:End!
[21660]:Child is working!
[21660]:End!

就和我们输出到终端命令行的结果一样了,Begin就输出了1次。

三、fflush()在exec()中的使用场景

有一程序如下:

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

int main()
{
    puts("Begin!");

    execl("/usr/bin/date", "date", "+%s", NULL);
    perror("execl()");
    exit(1);

    puts("End!");
    exit(0);
}

终端输出如下:

[root@localhost process_basic]# ./ex
Begin!
1630918521

如我们所预想的那样,函数先输出了Begin后,再输出了一串时间戳,由于新的程序替换了旧的程序,所以End字符串也不会被输出。
下面来看看输出到文件的情况:

[root@localhost process_basic]# ./ex > /tmp/out
[root@localhost process_basic]# cat /tmp/out
1630918963

可以发现程序没有输出Begin,而只输出了时间戳。
其实原因跟fork()示例中一样:
输出到终端,采用的是行缓冲模式,遇到换行符便会刷新缓冲区,输出字符;
而输出到文件,由于磁盘流默认是全缓冲模式,遇到字符串换行符后缓冲区并不会自动刷新。所以这个示例中进程还没来得及输出缓冲区中的Begin字段,老的进程映像(Process Image)就被新的进程映像所取代了。

所以在调用exec函数前,也要执行fflush()函数,如下:

int main()
{
    puts("Begin!");

    fflush(NULL);

    execl("/usr/bin/date", "date", "+%s", NULL);
    ...
}

这样就解决问题了,文件中的输出结果为:

[root@localhost process_basic]# ./ex > /tmp/out
[root@localhost process_basic]# cat /tmp/out
Begin!
1630919521

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰之行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值