观海听涛
Waiting for you

对一段程序的分析

01 Apr 2014

先看一看这段程序。

#include "apue.h"
int		glob = 6;		/* external variable in initialized data */
char	buf[] = "a write to stdout\n";

int
main(void)
{
	int		var;		/* automatic variable on the stack */
	pid_t	pid;

	var = 88;
	if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
		err_sys("write error");
	printf("before fork\n");	/* we don't flush stdout */

	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid == 0) {		/* child */
		glob++;					/* modify variables */
		var++;
	} else {
		sleep(2);				/* parent */
	}

	printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
	exit(0);
}

程序效果如下图所示:

该程序可以用来演示子进程是父进程的复制品,但其拥有的是父进程所拥有的拷贝。子进程虽然获得父进程的数据空间、堆和栈,但父子进程并不共享这些存储空间(写时复制技术)。

但这个程序有意思的地方我认为不是上面的演示,而是下面出现的现象。

当我们在终端上运行这个程序的时候,输出跟上面的图片是一样的。但是当我们将标准输出重定向到一个文件时(./test > log),奇怪的现象发生了:

多了一行printf中的输出:before work。

下面就分析一下为什么会出现这样的效果。

我们应该知道write函数是文件IO,是不带缓存的,这里没它什么事。但是我们应该注意到标准IO库是带缓存的。来看看我们运行这个程序时发生了什么。第一次我们是直接在终端上输入命令:./test,也就是说程序用的是标准输出,而且标准输出直接连接到终端设备。连接到设备的标准输出采用的是行缓存,但连接到文件的标准输出采用的是全缓存。注意到程序中printf输出后面跟了一个换行符,这个换行符会刷新标准输出缓存。子进程是父进程的复制品,父进程中标准缓存已经清空了,那以子进程中的标准缓存也是空的,所以”before work”只输出了一次。

第二次实验中,我们将标准输出重定向到一个文件,那么这时候标准输出使用的是全缓存。也就是说printf里面那个换行符不能刷新标准输出缓存,父进程在创建子进程的时候,其标准输出缓存里面依然存在”before work”,于是子进程也将父进程的标准输出缓存复制了下来(子进程是父进程的复制品),在父进程与子进程执行完程序最后那一个Printf语句后,由于程序结束,缓存被自动的刷新,于是子进程也会将从父进程那儿得到的标准输出缓存打印到文件中。第2,3行是子进程打印出来的,第4,5行是父进程打印出来的(父进程睡了两秒,让子进程先执行完毕了)。