分类: 系统运维
2012-03-29 13:17:29
vfork函数和fork有相同的调用顺序和相同的返回值。然而这两个函数的语义不同。
vfork函数起源于2.9BSD。一些把它视为一个瑕疵,但本文讨论的所有平台都支持它。事实上,BSD开发者把它从4.4BSD版本移除,但继承自
4.4BSD的所有开源BSD版本都在它们自己的版本里把它支持回来。vfork函数在SUS的第三版本被标记为废弃的。
vfork函数倾向于创建一个新的进程,当这个新进程的目的是exec一个新的程序时。vfork函数创建这个进程,就像是fork一样,并不把父进程的
地址空间拷贝到子进程里,因为子进程不会引用那个地址空间。子进程在vfork后马上简单地调用exec(或exit)。相反,当子进程在运行时并直到它
调用exec或exit,子进程在它父进程的地址空间运行。这种优化在UNIX的一些换页虚拟内存实现上提供了一个效率上的收获。(如我们在上一节看到
的,实现用写时复制来提高fork之后紧接exec的效率,然而完全不拷贝仍然比做一些拷贝要快。)
两个函数之间的另一个区别是vfork保证子进程先运行,直到子进程调用exec或exit。当子进程调用这些函数的任一个时,父进程恢复执行。(如果子进程在调用这两个函数的任一个之前依赖于父进程更多的操作,这可能会造成死锁。)
下面的代码在使用vfork函数。我们不用让父进程调用sleep,因为我们被保证它会被内核进入睡眠直到子进程调用exec或exit:
这里,子进程里完成的变量增长会改变父进程里的值。因为子进程运行在父进程的地址空间时,所以这并不让我们吃惊。然而,这种行为与fork不同。
注意上面的代码里我们调用_exit而不是exit。正如我们在7.3节里描述的,_exit不会执行任何标准I/O缓冲的冲洗。如果我们调用exit,
那么结果会不确定。根据标准I/O库的实现,我们可能会在输出上看不到区别,或者我们可能会发现父进程的printf的输出消失了。
如果子进程调用exit,实现会冲洗标准I/O流。如果这是库唯一的动作,那么我们看不到和调用_exit的区别。然而,如果实现还关闭了流,表示标准输
出的FILE对象的内存会被清理。因为子进程在借用父进程的地址空间,所以当父进程恢复并调用printf时,没有输出显示而且printf会返回-1。
注意父进程的STDOUT_FILENO仍然有效,因为子进程得到了父进程的文件描述符数组的一份拷贝。
多数当代的exit的实现都不会关闭流。因为进程即将退出,所以内核将会关闭进程所有打开的文件描述符。在库里关闭它只是增加开销而没有任何好处。