zoukankan      html  css  js  c++  java
  • fork与vfork

    先看一个fork的例子:

    int glob = 4;
    
    int main(void) 
    {
        int var, pid;
        var = 88; 
    
        if ((pid = fork()) < 0) {
            printf("vfork error");
            exit(-1);
    
        } else if (pid == 0) { /* 子进程 */
            var++;
            glob++;
            exit(0);
        }   
    
        printf("pid=%d, glob=%d, var=%d
    ", getpid(), glob, var);
    
        return 0;
    }

    运行结果:

    [root@localhost tmp]# ./a.out 
    pid=15297, glob=4, var=88

    可见,子进程修改的局部变量var和全局变量glob后,父进程是不可见的。

    如果把代码中的fork替换成vfork,再次运行,得到的结果:

    [root@localhost tmp]# ./a.out 
    pid=15304, glob=5, var=89

    可见,父进程共享了子进程的修改操作。

    在使用vfork时,如果子进程使用return语句结束,会发生什么呢?

    int glob = 4;
    
    int main(void) 
    {
        int var, pid;
        var = 88; 
    
        if ((pid = vfork()) < 0) {
            printf("vfork error");
            exit(-1);
    
        } else if (pid == 0) { /* 子进程 */
            var++;
            glob++;
            return 0;
        }   
    
        printf("pid=%d, glob=%d, var=%d
    ", getpid(), glob, var);
    
        return 0;
    }

    在我的机器上,导致了无限循环(直到vfork调用出错),这是因为子进程调用return语句破坏了父进程的栈。

    fork与vfork的区别:

    • fork 是 创建一个子进程,并把父进程的地址空间copy到子进程中;
    • vfork是 创建一个子进程,并和父进程的地址空间share一起用。

    我们知道,fork通常采用写时复制技术(copy-on-write, COW)创建子进程,以提高进程clone的性能;但在更早还没有COW的年代,fork创建子进程时时需要完整的复制父进程地址空间到子进程中,如果我们创建子进程的目的是为了调用exec,那么这种复制就显得既低效又无必要。而vfork让子进程共享父进程的地址空间,而不作克隆操作,就是为了节省这种不必要的复制开销。

    回到上面return导致程序crash的例子,return会释放局部变量,并弹栈,回到上级函数执行。exit直接退掉。如果你用c++ 你就知道,return会调用局部对象的析构函数,exit不会。(注:exit不是系统调用,是glibc对系统调用 _exit()或_exitgroup()的封装)

    可见,子进程调用exit() 没有修改函数栈,所以,父进程得以顺利执行。而子进程调用return,相当于在父进程的栈上执行了弹栈操作,父进程也就跪了。

    注意:

    1、vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行;

    2、子进程在调用exec或exit之前是在父进程的地址空间中运行的。

    可见,vfork的设计初衷是为了应对那些子进程需要马上调用exec的场景,因此不对父进程的地址空间做任何复制。


    再看一个fork的有趣例子,

    int main(void)
    {
        int i, pid = 0;  
    
        for (i = 0; i < 2; i++) {
    
            pid = fork();
    
            if (pid == 0) {
                printf("pid:%d
    ", getpid());
            }   
        }   
    
        return 0;
    }

    问题是,执行这段代码,一共产生了几个进程呢?

    从执行结果来看,printf函数打印了3次,fork被调用了3次,连上main进程一共有4个进程。

    再看下面这个例子,一共打印了多少个 “_” 呢?

    int main(void)
    {
        int i;
    
        for(i=0; i<2; i++){
    
            fork();
    
            printf("-");
    
        }
    
        return 0;
    }

    按照上面的例子,程序运行过程中一共有4个进程,把main进程记为A,则有

    i=0时,A进程 fork调用,产生子进程B1,然后A、B1各打印一个"_";

    i=1时,A进程 fork调用,产生子进程B2,然后A、B2各打印一个"_";

    与此同时,B1进程fork调用,产生子进程C1,然后B1、C1各打印一个"_";

    看起来,好像有6个"_"被打印了,但这段代码的执行结果却是8个,这是为啥呢?

    先来看下,这4个进程间的关系如下:

    A --> B1  --> C1

     |--> B2

    可见,B1、B2继承自A,而C1继承自B1。

    1、B1是在i=0时复制A的,此时A还没有调用过printf函数;

    2、B2是在i=1时复制A的,此时A已经调用过一次printf函数;

    3、C1是在i=1时复制B1的,此时B1已经调用过一次printf函数;

    我们知道,fork进程会让子进程完整复制父进程的地址空间,这也就包括了I/O缓冲区,这就是为什么最终打印了8个"_"的原因。

    参考文档:

    http://coolshell.cn/articles/12103.html

    http://coolshell.cn/articles/7965.html

  • 相关阅读:
    Python集成开发环境Pycharm+Git+Gitee(码云)
    【AI图像识别二】JMeter轻松实现大数据量AI图像识别接口测试
    pycharm中django同步数据库问题
    绿盟-WEB应用漏洞扫描系统
    Python脚本轻松实现批量图片重命名
    Linxu下JMeter进行接口压力测试
    依赖Anaconda环境安装TensorFlow库,避免采坑
    【AI图像识别一】人脸识别测试探索
    Postgresql死锁的处理
    PostgreSQL 9.5,带来 UPSERT 等新特性
  • 原文地址:https://www.cnblogs.com/chenny7/p/4123000.html
Copyright © 2011-2022 走看看