# Man中的描述
NAME
fork - create a child process
// 创建一个子进程
SYNOPSIS
#include
pid_t fork(void);
描述:
子进程是父进程的一份拷贝,它与父进程共享正文代码段,独占下面这些数据:
共享代码段说明,父进程和子进程在fork()
之后的指令双方都能看到,所以需要根据fork的返回值控制父子进程的执行代码。值得一提的是为了提高效率,Linux中对子进程的数据采取写实拷贝的技术,当对数据有修改时,才会拷贝。
fork()
函数执行成功时有两个返回值:
执行失败时:
errno
fork()
fork()
的调用者希望有一个进程可以拥有与它同样的资源,而执行不同的操作时。比如,在网络编程中,父进程负责监听客户端的连接请求,当有客户端发起连接时,fork()
出一个子进程,让子进程去和客户端进行交互,而父进程继续监听。
当一个进程要执行一个不同的程序时。比如在shell下,当我们输入一个程序的名字或一条命令的时候(当然这里的命令指外部命令,对于shell可以直接执行的内部命令,则不会fork出并程序替换),并不是shell直接去执行,而是通过fork()
创建出一个与自己有相同资源的子进程,然后exec()
程序替换,去替我们执行。
NAME
vfork - create a child process and block parent
// 创建一个子进程并阻塞父进程
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
调用方法和返回值同上。
vfork并不会完全复制父进程的地址空间,它的作用是创建一个新进程,而该新进程的目的是 exec 一个新程序。
这块可能比较难理解,可以这样理解:如果当前进程需要另外一个进程帮忙做一件事情,也就是立马需要进程进行程序替换。如果我们使用fork的话,它在前期会做大量工作,从父进程拷贝数据空间(堆、栈、全局变量和静态变量等),从而降低效率。
而vfork()
就不会,在进行程序替换(调用exec())或调用exit()之前,由vfork产生的子进程就在父进程的地址空间执行,也就是说此时它与父进程共享数据,一旦在程序替换或结束之前对父进程的数据进行修改,则会影响到父进程。
vfork调用产生的子进程中,子进程进行程序替换或者exit退出之前,父进程会被阻塞,也就是说保证子进程在父进程之前被执行。
fork 出的子进程 和父进程,谁先执行并不确定,这完全依赖于系统当前负载状况,以及内核调度算法。就算在父进程中进入关键代码区之前采取sleep()
的方式,也不能完全可以保证子进程执行,如果此时系统负载特别重,在sleep 返回之前,子进程还没有得到CPU资源,当sleep()返回时,就不能保证谁先执行,这种情况比较少见,也叫做竞争条件。
vfork不完全复制父进程的地址空间,在exec或exit之前,与父进程共享数据空间,而fork会拷贝一份。
观察:子进程和父进程数据独立,子进程修改数据是否会影响父进程。
#include
#include
int global = 10;
int main()
{
int local = 20;
pid_t id = fork();
if(id < 0)
{
perror("fork fail");
_exit(1);
}
else if(id == 0)
{// child
local++;
global++a;
printf("child ID is %d, global = %d, local = %d\n", getpid(), global, local);
printf("in child process global address:%x, local address : %x\n", &global, &local);
}
else
{// parent
wait(NULL);
printf("\n\nparent ID is %d, global = %d, local = %d \n", getpid(), global, local);
printf("in parent process global address:%x, local address : %x\n", &global, &local);
}
return 0;
}
结果:关于值的结果很明显。对于打印出来的地址确实一样的,刚开始有点不解,这不是写实拷贝吗为什么会一样,后来想到,因为有虚拟地址机制,各个进程之间互相独立,也就是说在父进程有一个地址 0X00001010,而同样在子进程也有0X00001010,它们的虚拟地址之所以相同,是因为它是从父进程拷贝过来的,这样理解写实拷贝:在子进程未对数据进程修改之前,子进程和父进程都是指向相同的物理地址,而当子进程需要对数据进行修改时,就会进行写实拷贝,此时分配不同于父进程的物理空间,然后改变页表中的指向,所以在程序中打印的是在自己所属的4GB空间中的虚拟地址,而具体到物理地址中需要根据页表进行虚实地址转换。
观察在exit之前修改数据,是否会影响父进程。
#include
#include
#include
#include
int global = 10;
int main()
{
int local = 20;
pid_t id = vfork();
if(id < 0)
{
perror("fork fail");
exit(1);
}
else if (id == 0)
{// child
local++;
global++;
printf("child ID is %d, global = %d, local = %d\n", getpid(), global, local);
exit(0);
}
else
{
printf("parent ID is %d, global = %d, local = %d\n", getpid(), global, local);
}
return 0;
}
值的提醒的是,在子进程退出的时候使用的是exit()而没有用exec。
注意:vfork 出的子进程必须被 exit或_exit终止或者程序替换,否则会出错(各平台实现不同,行为未定义),出错的原因是子进程如果没有exit,则它运行到 } 时,相当于return,而return 会清空当前函数栈帧,而此时父进程与子进程共享栈,当子进程return返回时,父进程恢复运行,看到我的栈帧怎么没了,一脸懵逼,所以此时栈帧情况不确定, 行为是未定义的,可能是段错误,也可能有别的错误,比如下面的错误。
我测试了在centos6.5上子进程中注释掉exit的情况:
fork fail: Resource temporarily unavailable
程序直接挂掉,也可以说,此时父进程栈帧情形不确定是什么,它可能会“乱跳”。