++++++APUE读书笔记-08进程控制(03)++++++
4、vfork
================================================
vfork函数源于2.9BSD。有些人认为这个函数是多余的,但是本文讨论的系统都支持这个函数。实际上,BSD把这个函数从4.4BSD中删除了,但是所有从4.4BSD继承过来的开源BSD,又在它们的release中把这个函数添加进来了。vfork函数在Single UNIX Specification的version3中被标记成将被废弃的接口。
函数vfork和fork一样,返回值也一样,但是这两个函数有所不同。
vfork函数一般用于创建一个子进程,并且这个子进程的目的是进行exec(关于exec参见后面).
vfork和fork一样,创建新的进程,并且它不会复制父进程的地址空间的内容到子进程中,因为子进程是将要调用exec的而不是引用父进程地址空间的数据。子进程会一直在父进程的地址空间运行,直到它调用了exit或者exec.这样的优化是为了提高一些用虚拟内存页实现的unix系统的效率。(通过前面对fork的解释我们可以发现,实际上fork现在已经使用了copy-on-write技术来提高效率,但是没有拷贝始终是要比有一些拷贝的快一些)
fork和vfork另一个不同的地方是vfork保证了子进程先运行,直至子进程调用了exec或者exit.当子进程调用了exec或者exit的时候,父进程才重开始执行(如果子进程在调用这两个函数之前需要父进程的一些操作的话,这里可能会导致死锁)。
总之,就两点不同:
a)vfork的子进程不拷贝父进程空间数据而是直接运行在父进程空间内(直到调用了exec或exit)。fork的子进程拷贝一份父进程的数据。
b)vfork之后,子进程会先继续运行,直到子进程调用exec或者exit父进程才开始运行。fork的子进程不能确定谁先继续运行。
下面是使用vfork的一个例子:
int glob = 6; /* external variable in initialized data */
int main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
printf("before vfork\n"); /* we don't flush stdio */
if ((pid = vfork()) < 0) {
err_sys("vfork error");
} else if (pid == 0) { /* child */
glob++; /* modify parent's variables */
var++;
_exit(0); /* child terminates */
}
/*
* Parent continues here.
*/
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
}
从例子可以知道,子进程修改了变量,也反应到父进程中了,因为两者共享进程空间而不是拷贝关系;另外子进程会先于父进程运行。需要注意的地方是:
我们结束子进程调用的是_exit而不是exit。因为,_exit不会做任何刷新标准输入/输出库的刷新操作;如果我们调用了exit那么,输出结果是不确定的,这依赖标准I/O库的实现,我们可能会看到和使用_exit没有什么区别,或者看到父进程printf的内容没有了。
如果子进程调用了exit,这样会对标准I/O流进行flush操作。如果这是标准I/O库的唯一动作,那么我们不会看到和_exit有什么不同;但是如果也关闭了标准I/O流,那么内存中表示标准输出的FILE对象会被清空,由于子进程和父进程共享空间,所以当父进程再次继续运行并执行printf的时候,将会不输出任何东西,同时printf会返回-1。注意,这时候父进程的STDOUT_FILENO还是合法的,就像子进程获得了父进程的文件描述符的副本。
大多数现代对exit的实现都不会自找麻烦地去关闭streams.因为如果进程将要退出的时候,内核会关闭所有在进程中打开的文件描述符号。如果在库中关闭他们,会增加额外的开销,没有一点好处。
参考:
阅读(528) | 评论(0) | 转发(0) |