前言
一、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