Linux-fork(),vfork()和clone的区别

  在linux系统中,fork(),vfork()和clone函数都可以创建一个进程,但是它们的区别是什么呢???本文就这三者做一个较深入的分析!!!

1.fork()

  fork()函数的作用是创建一个新进程,由fork创建的进程称为子进程,fork函数调用一次返回两次,子进程返回值为0,父进程返回子进程的进程ID。我们知道,一个进程的地

址空间主要由代码段,数据段,堆和栈构成,那么p2就要复制相关的段到物理内存。原始的unix系统的实现的是一种傻

瓜式的进程创建,这些复制包括:

(1)  为子进程的页表分配页面,确定页表的位置;

(2)为子进程的页分配页面,确定子进程页面的位置;

(3)初始化子进程的页表;

(4)把父进程的页复制到子进程对应的页中

从图中我们可以看出除了正文段外,子进程的所有其它段都分配了物理空间,并将父进程的相关内容拷贝过来。父进程的task_struct结构中的打开文件描述符,进程组ID,

回话ID都进行复制。

下面通过简单的代码检测一下fork()函数:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>
#include <iostream>
using namespace std;

int main() {
        int num = 1;
        int child;
        if(!(child =fork())) {
		cout<<&num<<endl;
                printf("son process, num: %d, address:%p, pid is: %d\n", num,&num, getpid());
		num++;
		cout<<"num:"<<num<<endl;
		cout<<"address: "<<&num<<endl;
		sleep(60);
		exit(0);
        } else {
		sleep(1);
                printf("father process, num: %d, address:%p, pid is: %d\n", num,&num, getpid());		
		sleep(59);		
		exit(0);
        }
}

进程的虚拟空间:

子进程的虚拟地址空间:


父进程的虚拟地址空间:

可以看到子进程和父进程的地址空间是一样的。

  但是这种方法的效果非常不好,如果在fork子进程之后,立即调用了exec函数簇,那么原先拷贝的父进程的数据段,栈,堆的相关副本都将变为徒劳。后来人们想到了一种

替代的方式,那就是写时复制(Copy-On-write,COW技术),这种技术允许父进程和子进程共享上面的区域,而且内核将这些段的访问权限变成只读。如果是父进程或是子进程中

的任何一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统的某一个"页".下面是这种技术在没有写操作的时候的的详细图解,如果子进

程或父进程的任何一个有写操作的话,那么被写的那一页才需要复制到物理空间;


从图中可以看到,COW技术在进程调用fork()的时候,并没给子进程的相关段分配内存空间。这种做法在fork()之后调用exec函数簇的过程中效率有很大的提高。这种情况下,内

核只需要为子进程创建一个task_struct,也就是说如果子进程调用exec函数簇执行了另一个可执行文件,那么内核可能只是新建一个task_struct结构体,将父进程的内容拷贝过

,这样就大大节约了盲目拷贝的消耗。

2.vfork()

    vfork()函数也用于创建一个新进程,而该新进程的目的是exec一个新程序,下面我们开看看简单的测试:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>
#include <iostream>
using namespace std;

int main() {
        int num = 1;
        int child;
        if(!(child =vfork())) {
		cout<<&num<<endl;
                printf("son process, num: %d, address:%p, pid is: %d\n", num,&num, getpid());
		num++;
		cout<<"num:"<<num<<endl;
		cout<<"address: "<<&num<<endl;
		sleep(2);
		exit(0);
        } else {
		sleep(1);
                printf("father process, num: %d, address:%p, pid is: %d\n", num,&num, getpid());		
		sleep(2);		
		exit(0);
        }
}

测试结果:

从测试结果中我们可以看到,在子进程修改了num变量的值后,父进程的num的值也发生改变,说明对于子进程和父进程来说,它们操作的是同一个地方的num值,下面就是vfork的示意图:

可以看出子进程直接共享了父进程的虚拟进程空间。

3.clone()

  clone()函数是linux系统中,用来创建轻量级进程。

函数原形:

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
下面是flags可以取的值
标志                    含义
CLONE_PARENT   创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS           子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES      子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS   在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND   子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE   若父进程被trace,子进程也被trace
CLONE_VFORK     父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM           子进程与父进程运行于相同的内存空间
CLONE_PID          子进程在创建时PID与父进程一致
CLONE_THREAD    Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。父进程被挂起当子线程释放虚
存资源后再继续执行。
测试代码1:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>
#include <iostream>
using namespace std;
#define FIBER_STACK 8192

int a;
void * stack;
int func(void *){
	cout<<&a<<endl;
        printf("This is son, the pid is:%d, the a is: %d\n", getpid(), ++a);
        free(stack); 
        exit(1);
}
int main(){
        void * stack;
        a = 1;
        stack = malloc(FIBER_STACK);
        if(!stack) {
                printf("The stack failed\n");
                exit(0);
        }
	cout<<&a<<endl;
        printf("creating son thread!!!\n");
        clone(func, (char *)stack + FIBER_STACK,CLONE_VFORK|CLONE_VM, 0);
        printf("This is father, my pid is: %d, the a is: %d\n", getpid(), a);
        exit(1);
}

结果:

测试代码2(做如下修改):

clone(func, (char *)stack + FIBER_STACK,CLONE_VFORK, 0);
结果:

很明显,在测试2中将CLONE_VM删掉之后,子进程和父进程就不会公用页表,子进程创建新的页表。从某种意义上来说,clone其实是fork和vfrok的更高层次版本,,它们的关

系如下(《深入理解linux内核》中描述):

   传统的fork()系统调用在Linux中是用clone()实现的,其中clone()的flags参数指定为sigchld信号以及所有清0的clone标志,而它的child_stack参数是父进程当前的堆栈

指针,因此,父进程和子进程暂时共享一个用户态堆栈。而vfork函数系统调用也是用clone实现的,其中clone()的参数flags指定为sigchld和CLONE_VFORK和CLONE_VM标

志,clone()的参数child_stack等于父进程当前的栈指针!!!

。只是有一点不明白,把int a和void * stack挪到main函数里面之后,就会出现编译错误,显示未定义a和stack,这点有些不懂,望高人指点!!!!



  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值