fork函数:
fork函数被调用一次,返回两次,子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本,也就是说子进程获得父进程数据空间、堆和栈的副本,但是两个进程是共享正文段(通常是read-only的)的。但是在具体的系统实现中,采用写时复制技术(Copy-On-Write, COW)。fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中,也就是说父子进程的每个相同的打开描述符共享一个文件表项,而文件表项内容包含:文件状态标志、当前文件偏移量、指向该文件v节点表项的指针(在linux实现中,并没有v节点,而是使用i节点)。
vfork函数:
在子进程调用exec或exit之前,是在父进程的空间中运行的,另外,vfork保证子进程优先运行。
1 #include "apue.h"
2
3 int glob = 6;
4
5 int
6 main(void)
7 {
8 int var;
9 pid_t pid;
10
11 var = 88;
12 printf("before fork\n");
13 if((pid = fork()) < 0)
14 {
15 err_sys("fork error");
16 }
17 else if(pid == 0)
18 {
19 glob++;
20 var++;
22 }
23 printf("pid = %d, glob = %d, var = %d", getpid(), glob, var);
24 exit(0);
25 }
在这个例子中,调用了fork()和exit(),一般情况,在fork创建的子进程中调用exit是不正确的,因为exit()函数会为所有打开流调用fclose函数,当然包括标准输入输出,而fork创建的子进程和父进程共享打开的文件表项,所以标准I/O关闭,程序结果如下:
1 $ ./a.out
2 before fork
3 pid = 3535, glob = 6, var = 88pid = 3536, glob = 7, var = 89$ ./a.out > tmp
5 $ cat tmp
6 before fork
7 pid = 3156, glob = 6, var = 88before fork
8 pid = 3538, glob = 7, var = 89
需要注意的是,当标准输出重定向到文件时,它是全缓冲的,而在设计交互式设备时,是行缓冲的,所以当重定向到文件时,可以发现当遇到''\n''时,并没有输出,即缓冲并没有并刷新,之后调用fork,子进程共享父进程的资源,最后,两个进程调用exit ()时,才刷新缓冲区,将缓冲区数据输出。
当将上述代码更改如下时:
1 else if(pid == 0)
2 18 {
3 19 glob++;
4 20 var++;
5 21 _exit(0);
6 22 }
7 23 printf("pid = %d, glob = %d, var = %d", getpid(), glob, var);
8 24 _exit(0);
输出如下:
1 $ ./a.out
2 before fork
3 $ ./a.out > tmp
4 $ cat tmp
可以看到,当为行缓冲时,父进程输出before fork,缓冲被刷新,再调用fork,子进程并不输出之后退出,而后面的printf则被冲洗掉(注意:并没有换行符),因为_exit函数并不刷新缓冲区,而是直接退出。在全缓冲情况下,子进程得到父进程缓冲区的副本,不幸的是,父子进程都调用了_exit,结果可想而知。
再改代码:
1 if((pid = vfork()) < 0)
2 14 {
3 15 err_sys("fork error");
4 16 }
5 17 else if(pid == 0)
6 18 {
7 19 glob++;
8 20 var++;
9 21 _exit(0);
10 22 }
11 23 printf("pid = %d, glob = %d, var = %d", getpid(), glob, var);
12 24 exit(0);
在上述情况中,结果如下:
1 $ ./a.out
2 before fork
3 pid = 3411, glob = 7, var = 89$ ./a.out > tmp
4 $ cat tmp
5 before fork
6 pid = 3413, glob = 7, var = 89
可以看出,子进程改变了父进程的变量,因为子进程调用_exit,所以,即使在全缓冲时,仍然无法看到子进程输出before fork,而我们看到父进程作出了相应输出。
另外,如果在子进程中调用exit而不是_exit,那么该程序的输出是不确定的,它依赖标准IO的实现,可能会见到输出并没有变化,或者发现没有出现父进程的printf输出。