fork的定义
函数原型:pid_t fork(void)
返回值:-1失败,0或者大于0的正整数
作用:fork成功后,产生一个新的子进程,在新的子进程中返回0,在父进程中返回大于0的正整数,该正整数即为子进程的PID。
fork特点:
1、子进程会从fork()返回值的下一条逻辑语句中开始运行
2、父子进程的执行顺序是随机、并发的。
3、父子进程是相互独立的,子进程会复制父进程的整个内存空间(虚拟空间和物理空间)、包括栈、堆、数据段、代码段标志IO缓冲区等。但在现在的Unix内核(包括Linux),采用一种更为有效的方法称之为写时复制(或COW),物理空间只有在写时才会复制,这个后面会介绍。
父子进程的异同:
同
(1)、所有环境变量
(2)、打开的文件
(3)、信号响应函数
(4)、整个内存空间、包括栈、堆、数据段、代码段标志IO缓冲区等
(5)、进程组的ID、实际的UID和GID等等
异
(1)进程号
(2)记录锁
(3)挂起的信号
vfork的定义
函数原型:pid_t vfork(void);
返回值:-1失败,0或者大于0的正整数
作用:fork成功后,产生一个新的子进程,在新的子进程中返回0,在父进程中返回大于0的正整数,该正整数即为子进程的PID。
vfork的特点
1、vfork保证子进程先运行,在它调用exec或exit之后父进程才能调度运行。
2、vfork的父子进程是是共享资源的,并没有像fork那样复制父进程的内存,所以父子进程是相互影响的。
3、由vfork创建出来的子进程不应该使用return或者exit()返回调用者,应该使用exec系列函数或者_exit()函数退出。因为vfork创建的父子进程的资源是共享的,数据段、栈、堆等是共享的,使用return时,相对于在父进程的栈里操作return函数,会影响父进程的栈。exit()退出时,会冲洗标准IO缓冲区数据到内核,标准IO缓冲区也是共享的。
vfork出现的原因:
vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。
在早期的时候,还没出现写时复制技术,fork将会创建一个新的地址空间,并且拷贝父进程的资源,而往往在子进程中会执行exec 调用,exec调用后,之前的拷贝工作就没有任何作用,反而一方面会降低性能,一方面会占用内存空间,非常不值。所以就出现了vfork,父子进程公用资源,创建子进程后,立即执行exec 调用,子进程不需要拷贝资源,直接在父进程的空间里操作,但在调用exec 之间要避免写操作,因为写操作会修改父进程的资源,造成一些不必要的麻烦。但随着写时复制技术的出现,vfork的效果就没那么明显了。
写时复制技术(copy-on-write)
内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。
Linux的fork写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使父子进程拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下—例如,fork()后立即执行exec(),地址空间就无需被复制了。fork()的实际开销就是复制父进程的页表以及给子进程创建一个进程描述符。在一般情况下,进程创建后都为马上运行一个可执行的文件,这种优化,可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。
测试例子
test.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <string.h>
int main()
{
pid_t pid;
int status;
char *pmalloc = NULL;
pmalloc = (char *)malloc(1024*1024*10);
if(pmalloc == NULL)
{
printf("error!--\n");
}
memset(pmalloc , 0 , 1024*1024*10);
if((pid = fork())<0)
{
printf(" fork fail !\n");
}
else if(pid == 0)
{
while(1)
{
memset(pmalloc , 0 , 1024*1024*10);
printf("chen_test\n");
sleep(1);
}
_exit(127);
}
else //父进程
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
printf("waitpid fail\n"); //如果waitpid被信号中断,
break;
}
}
}
return 0;
}
编译以上代码,运行,通过free -m,会发现多了20M的内存使用,一个是父进程申请的堆空间10M,一个是子进程复制的10M堆空间。但如果把子进程里的memset(pmalloc , 0 , 1024102410),屏蔽掉,然后在编译,运行测试,发现内存只是使用了10M的空间,是因为fork采用了写时复制的技术,只有写操作才去复制内存,所以就出现了测试的两种结果。如果把fork改成vfork进行测试,会发现内存都是使用10M,因为vfork的父子进程的资源是共享的。