8.4 vfork函数-进程控制
vfork函数的调用序列和返回值与fork相同,但两者的语义不同。
vfork用于创建一个新进程,而该新进程的目的是exec一个新程序(如上节(2) 中一样)。程序1 - 5中的shell基本部分就是这种类型程序的一个例子。vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec (或exit ),于是也就不会存访该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中运行。这种工作方式在某些UNIX的页式虚存实现中提高了效率(与上节中提及的,在fork之后跟随exec,并采用在写时复制技术相类似)。
vfork和fork之间的另一个区别是: vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。)
实例
在程序8 - 1中使用vfork代替fork,并做其他相应修改得到程序8 - 2。删除了对于标准输出的write调用,另外,我们也不再需要让父进程调用sleep,vfork已保证在子进程调用exec或exit之前,内核会使得父进程处于休眠状态。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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) {
perror("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);
}
运行该程序得到:
$ a.out
before vfork
pid = 2777, glob = 7, var = 89
子进程对变量glob和var做增1操作,结果改变了父进程中的变量值。因为子进程在父进程的地址空间中运行,所以这并不令人惊讶。但是其作用的确与fork不同。
注意,在程序8 - 2中,调用了_ exit而不是exit。正如8 . 5节所述,_ exit并不执行标准I / O缓存的刷新操作。如果用exit而不是_ exit,则该程序的输出是不确定的。它依赖于标准IO库的实现。可能什么也不输出:
$ a.out
before vfork
从中可见,父进程printf的输出消失了。其原因是子进程调用了exit,它刷新开关闭了所有标准I / O流,这包括标准输出。虽然这是由子进程执行的,但却是在父进程的地址空间中进行的,所以所有受到影响的标准I/O FILE对象都是在父进程中的。当父进程调用printf时,标准输出已被关闭了,于是printf返回- 1。