今天在看《UNIX环境高级编程》,看到关于fork函数的时候,看到一个相应的实例,不仅可以对fork的理解加深,还可以了解到与缓冲区相关的内容,特意整理在这里,以后看看。
具体的代码如下所示。
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
-
int glob = 6;
-
char buf[] = "a wirte to stdout\n";
-
-
int main()
-
{
-
int var;
-
pid_t pid;
-
-
var = 88;
-
-
if(write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)
-
{
-
printf("write error.\n");
-
exit(1);
-
}
-
-
printf("before fork\n");
-
-
if( (pid = fork() ) < 0)
-
{
-
printf("fork error.\n");
-
exit(1);
-
}
-
else if(pid == 0)
-
{
-
glob++;
-
var++;
-
}
-
else
-
{
-
sleep(2);
-
}
-
-
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
-
exit(0);
- }

从上面的结果可以看出:
(1)子进程的结果发生了变化,而父进程的数据没有发生变化;
(2)输出到终端与输出到文件中的结果不一样;
对于(1)的解释如下:
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如子进程获取进程数据空间、堆和栈的副本。注意:这是子进程所拥有的副本。父、子进程并不共享这些存储空间。记住:不共享这些存储空间。
所以才有了上面(1)的结果:子进程的变量值改变了,而父进程的变量值没有变化。
对于(2)的解释如下:
首先,上面的关于父子进程的输出顺序是不确定的。一般来说,在fork之后是先执行父进程还是子进程是不确定的。这取决于内核所使用的调度算法。如果要求父子进程同步,则要求某种形式的进程间通信(这是后话)。在上面的程序中,父进程使自己先休眠2秒钟,以使子进程先执行。但并不保证2秒钟已经足够。
当写到标准输出时,我们将buf减1作为输出字节数,这是为了避免将终止null字节写出。strlen计算不包含终止null字节的字符串长度,而sizeof则计算包括终止null字节的缓冲区长度。两者之间的另一个差别是:使用strlen需要一次函数调用,而对于sizeof,因为缓冲区已经用已知的字符串进行了初始化,其长度是固定的,所以sizeof在编译时计算缓冲区的长度。
上面的是题外话,与上述显示结果没有关系,现在进入正题哈。
注意上面程序中fork与I/O函数之间的交互关系。write函数是不带缓冲的。因为在fork之前调用了write,所以其数据写到标准输出一次。但是,标准I/O库是带缓冲的。
如果标准输出连接到终端设备,则它是行缓冲的,否则它是全缓冲的。
当以交互的方式运行该程序时,只得到该printf输出的行一次,其原因是标准输出缓冲区由换行符冲洗。但是当标准输出重定向到一个文件时,却得到printf输出行两次。其原因是:在fork之前调用了printf一次,当调用fork时,该行数据仍然在缓冲区中,然后在将父进程数据空间复制到子进程中时,该缓冲区也被复制到了子进程中。于是,那时父、子进程各自有了带该行内容的标准I/O缓冲区。在exit之前的第二个printf将其数据添加到现有的缓冲区中。当每个进程终止时,最终会冲洗缓冲去的副本。