1,函数原型
#includ<unistd.h>
pid_t fork(void);
函数执行的效果:
创建一个子进程。该子进程是原先进程的副本,子进程获得父进程数据空间、堆、栈的副本,而不是共享它们。所以子进程不能直接修改父进程里的变量值。父子进程共享的是代码的正文段。注意共享的是整个代码段,但执行的话当然是fork之后的部分。不过如果i使用goto语句或其他方法的话,也可以让子进程执行到fork之前的语句。
函数的返回值:
与其他函数不同,这个函数调用一次会返回两次!子进程里返回一次,父进程里也会返回一次。子进程里返回值为0,父进程里返回值为子进程的pid,出错时返回负数。可以通过返回值来判断当前位于父进程还是子进程。
fork()之后的代码,父进程与子进程都会执行,并且不确定哪个进程先执行。
2,例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int globvar = 6;
char buf[] = "a write to stdout\n";
int main(){
// 8.3 fork
int var = 88;
int pid;
// write无缓冲
if(write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1){
printf("Error!\n");
}
//标准IO,输出到终端行缓冲,输出到文件全缓冲
printf("before fork\n");
//开始产生子进程
if((pid = fork()) < 0){
printf("fork error!");
} else if(pid == 0){ // child process,子进程返回0
globvar++;
var++;
} else { // parent process,父进程返回子进程的ID
sleep(2);
}
//观察子进程与父进程里的变量值
printf("pid = %d, globvar = %d, var = %d\n", getpid(), globvar, var);
return 0;
}
正常执行,执行的效果是:
可以看到父子进程里,变量的值是不同的!
这也就是前面说的,子进程只是获得了父进程数据空间、堆、栈的副本,而不是父子进程共享它们。所以子进程里的修改并不影响父进程里的变量的值。
将结果重定向到文件,执行效果如下:
可以看到最明显的区别就是“before fork”被打印了两次!!而write仍然只执行了一次。
为什么会这样呢?
因为write是系统调用,永远都是无缓冲的,两次执行效果一致。
但是,printf是标准IO,其输出到终端的时候是行缓冲,所以第一次在执行fork执行就会输出到屏幕。
但是当输出到文件时,其变成了全缓冲。在执行fork前其并不会写文件,内容存储在缓冲区中。然后执行fork,复制出一个子进程。此时,父进程的缓冲区里的内容也同样被复制一份到了子进程。所以父进程与子进程的缓冲区里都有“before fork”这段字符串。当每个进程终止时,其缓冲区里的内容都会写入文件。因此“before fork”就被写了两次。
这个例子也说明,子进程能获得父进程的缓冲区(数据空间)。