在APUE中的fork章节,有个实例,之前看半天不理解,现在讲解一下
例程:
-
#include
-
#include
-
#include
-
-
int globVar = 6;
-
char buf[] = "a write to stdout\n";
-
int main(void)
-
{
-
int var;
-
pid_t pid;
-
-
var = 88;
-
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1) {
-
perror("write");
-
exit(-1);
-
}
-
printf("before fork\n");
-
-
if ( (pid = fork()) < 0 ) {
-
perror("fork");
-
exit(-1);
-
}
-
else if ( 0 == pid ) { //chlid
-
globVar++;
-
var++;
-
}
-
else { //parent
-
sleep(2);
-
}
-
-
printf("pid=[%ld], globVar=[%d], var=[%d]\n", (long)getpid(), globVar, var);
-
-
exit(0);
-
}
执行程序得到:
$ ./a.out
a write to stdout
before fork
pid=[7285], globVar=[7], var=[89]
pid=[7284], globVar=[6], var=[88]
$ ./a.out > temp.out
$ cat temp.out
a write to stdout
before fork
pid=[7287], globVar=[7], var=[89]
before fork
pid=[7286], globVar=[6], var=[88]
注意点1:
改程序fork与I/O函数之间的交互关系。
文中提了几点:
1 write函数是不带缓冲的。因为在fork之间调用write,所以其数据写到标准输出一次。但是,标准I/O库是带缓冲的。
2 如果标准输出链接的是终端设备,则是行缓冲(换行符);否则是全缓冲。
所以当以交互方式运行该程序时,只得到该printf输出的行 一次,其原因是标准输出缓冲区由换行符冲洗。
但当将标准输出重定向到一个文件时,却得到printf输出行 两次,其原因是在fork之前调用了printf一次,但当调用fork时,该行数据仍在缓冲区中,然后再讲父进程数据空间复制到子进程中时,该缓冲区数据也被复制到子进程中,此时 父进程和子进程各自有了带该行内容的缓冲区。在exit之前的第二个printf将其数据追加到已有的缓冲区中。当每个进程终止时,其缓冲区中的内容都被写到相应文件中。
这个是官方的版本,其实如果你明白后反过来看其实还是说的很清楚的,但如果第一次看,再加上对翻译人员的表述方式不适应的话,很可能看晕。
所以,下面是简单的大白话版本:
同样首先是write的问题,目的地是标准I/O,也就是个池子,带缓冲的。
write就是个进入池子的方式,方式本身不带缓冲。可以看做是缓冲区的输入。
其次,数据已经进入池子了,此时,缓冲区的输出的也要分目的地:
如果是终端设备的交互方式,则为行缓冲,也就是一行1024个字节,遇换行符就输出。
如果是其它地方,这里是文件,则为全缓冲,全缓冲也就是4096个字节。
P.S. : 这个1024byte和4096byte怎么来的?可以参见APUE的5.12章节
接下来说重点了,那么问题来了:
首先fork的时候,子进程才会复制父进程的一些东西。显然before这行是在fork之前,理论上是不会复制printf这个调用本身的,那为什么输出为文件的时候会有两个before呢?
第一次打印只有一个before的原因是,因为是终端所以是行缓冲,before这行的printf中是有\n的,所以既然是行缓冲,理所应当输出的终端,所以大家看到的这个before出现的运行时的时刻是执行完printf后直接输出的。
第二次打印在子进程整体打印完后的父进程又打印一遍的原因是,正如问题描述的本身所讲,其实此时的before并不是printf这个调用本身的,而是因为输出的目的地不是终端所以为全缓冲,也就是before这行打印包括换行符一起还在缓冲区的池子中,并没有输出出来,所以之后运行到fork的时候,子进程复制父进程的时候,当然缓冲区中的before也被复制了一份,所以大家从文件里看到的before并不是在fork之前的printf结束后立即输出的,而是在每个进程终止时,缓冲区冲洗的时候父子进程分别输出的。
阅读(364) | 评论(0) | 转发(0) |