一、函数介绍
作用
一个进程可以调用fork函数创建一个子进程。
#include <unistd.h>
pid_t fork(void);
返回值
如果是在子进程中则返回0,在父进程中则返回子进程的id,若出错则返回-1。pid_t是可以用于存储进程的id号的类型,进程的id号一般是一个正整数。
二、举个栗子
fork函数被调用一次,但返回两次,在父进程(即调用fork函数的进程)中fork函数返回子进程的id,而在子进程中(fork函数创建的进程)fork函数返回0,并且两个进程都会继续执行fork调用之后的指令,所以可以用fork函数的返回值来判断当前是处于父进程还是子进程。例子:
#include <unistd.h>
int globvar = 1;
main(void){
pid_t pid;
int var = 0;
if((pid = fork()) < 0){
//若fork函数返回-1则出错,并退出进程
printf("fork failed\n");
exit(-1);
}else if(pid == 0){
//若pid=0则代表当前处于子进程中
//修改globvar和var
globvar = 3;
var = 3;
printf("当前处于子进程中,globvar为%d,var为%d\n",globvar, var);
}else{
//若pid大于0则代表处于父进程中,因为父子进程执行顺序不能确定,所以让父进程sleep来
保证子进程先执行
sleep(2);
printf("当前处于父进程中,globvar为%d,var为%d\n",globvar, var);
}
exit(0);
}
下面是上述程序的执行结果:
可以看到在子进程中修改全局变量和局部变量都不会对父进程中的变量产生影响,这是因为子进程是父进程的副本,他们是完全独立开来的,父进程和子进程只共享正文段(即你写的程序)。
三、写时拷贝(copy-on-write)
fork有以下两种用法:
(1)一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段(上面的例子程序就就属于这种)。
(2)一个进程要执行不同的程序,比如子进程从fork返回后立即调用exec,exec可以使进程执行新的程序,所以就不需要复制父进程的整个地址空间。
但是传统的fork是直接把父进程的所有资源复制给创建的子进程,这种实现效率过于低下,所以后面的linux系统中的fork使用写时拷贝技术,只有在需要写入的时候,数据才会被复制,在此之前,资源都是以只读的方式共享。
这种技术使得地址空间上的页的拷贝被推迟到实际发生写入的时候才进行,在页根本不会被写入的情况下(比如fork()之后立马exec())它们就无需复制了。比如下面这个程序:
#include <sys/wait.h>
#include <unistd.h>
int main(){
pid_t pid;
if((pid = fork()) < 0)
printf("fork error");
else if(pid == 0){
//子进程,执行下述exec函数后就立马跳转执行ls程序,并且不会返回
if(execlp("ls", "ls", "-al", (char *)0) < 0)
printf("execlp error");
}
//父进程,因为此时子进程已经在执行新程序ls了,所以不用担心子进程会执行到这里
if(waitpid(pid, NULL, 0) < 0)
printf("wait error");
printf("execlp successful!\n");
exit(0);
}
上述代码的执行结果如下:
可以看到上述程序成功执行了ls的功能,并且execlp successful!没有被打印两次,这是因为子进程执行exec函数后就立马更换成ls程序执行了,ls程序执行完后子进程就自动终止,父进程通过waitpd函数来确认子进程是否执行完毕。这个过程子进程根本不需要复制父进程的全部地址空间,所以对于上述程序,写时拷贝能大大地提高效率。