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

在知乎上,有个人问了这样的一个问题——为什么vfork的子进程里用return,整个程序会挂掉,而且exit()不会?并给出了如下的代码,下面的代码一运行就挂掉了,但如果把子进程的return改成exit(0)就没事。

我受邀后本来不想回答这个问题的,因为这个问题明显就是RTFM的事,后来,发现这个问题放在那里好长时间,而挂在下面的几个答案又跑偏得比较严重,我觉得可能有些朋友看到那样的答案会被误导,所以就上去回答了一下这个问题。

下面我把问题和我的回答发布在这里,也供更多的人查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
    int var;
    var = 88;
    if ((pid = vfork()) < 0) {
        printf("vfork error");
        exit(-1);
    } else if (pid == 0) { /* 子进程 */
        var++;
        return 0;
    }
    printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var);
    return 0;
}

基础知识

首先说一下fork和vfork的差别:

  • fork 是 创建一个子进程,并把父进程的内存数据copy到子进程中。
  • vfork是 创建一个子进程,并和父进程的内存数据share一起用。

这两个的差别是,一个是copy,一个是share。(关于fork,可以参看酷壳之前的《一道fork的面试题》)

你 man vfork 一下,你可以看到,vfork是这样的工作的,

1)保证子进程先执行。
2)当子进程调用exit()或exec()后,父进程往下执行。

那么,为什么要干出一个vfork这个玩意? 原因在man page也讲得很清楚了:

Historic Description

Under Linux, fork(2) is implemented using copy-on-write pages, so the only penalty incurred by fork(2) is the time and memory required to duplicate the parent’s page tables, and to create a unique task structure for the child. However, in the bad old days a fork(2) would require making a complete copy of the caller’s data space, often needlessly, since usually immediately afterwards an exec(3) is done. Thus, for greater efficiency, BSD introduced the vfork() system call, which did not fully copy the address space of the parent process, but borrowed the parent’s memory and thread of control until a call to execve(2) or an exit occurred. The parent process was suspended while the child was using its resources. The use of vfork() was tricky: for example, not modifying data in the parent process depended on knowing which variables are held in a register.

意思是这样的—— 起初只有fork,但是很多程序在fork一个子进程后就exec一个外部程序,于是fork需要copy父进程的数据这个动作就变得毫无意了,而且这样干还很重(注:后来,fork做了优化,详见本文后面),所以,BSD搞出了个父子进程共享的 vfork,这样成本比较低。因此,vfork本就是为了exec而生。

为什么return会挂掉,exit()不会?

从上面我们知道,结束子进程的调用是exit()而不是return,如果你在vfork中return了,那么,这就意味main()函数return了,注意因为函数栈父子进程共享,所以整个程序的栈就跪了。

如果你在子进程中return,那么基本是下面的过程:

1)子进程的main() 函数 return了,于是程序的函数栈发生了变化。

2)而main()函数return后,通常会调用 exit()或相似的函数(如:_exit(),exitgroup())

3)这时,父进程收到子进程exit(),开始从vfork返回,但是尼玛,老子的栈都被你子进程给return干废掉了,你让我怎么执行?(注:栈会返回一个诡异一个栈地址,对于某些内核版本的实现,直接报“栈错误”就给跪了,然而,对于某些内核版本的实现,于是有可能会再次调用main(),于是进入了一个无限循环的结果,直到vfork 调用返回 error)

好了,现在再回到 return 和 exit,return会释放局部变量,并弹栈,回到上级函数执行。exit直接退掉。如果你用c++ 你就知道,return会调用局部对象的析构函数,exit不会。(注:exit不是系统调用,是glibc对系统调用 _exit()或_exitgroup()的封装)

可见,子进程调用exit() 没有修改函数栈,所以,父进程得以顺利执行

关于fork的优化

很明显,fork太重,而vfork又太危险,所以,就有人开始优化fork这个系统调用。优化的技术用到了著名的写时拷贝(COW)

也就是说,对于fork后并不是马上拷贝内存,而是只有你在需要改变的时候,才会从父进程中拷贝到子进程中,这样fork后立马执行exec的成本就非常小了。所以,Linux的Man Page中并不鼓励使用vfork() ——

“ It is rather unfortunate that Linux revived this specter from the past. The BSD man page states: “This system call will be eliminated when proper system sharing mechanisms are implemented. Users should not depend on the memory sharing semantics of vfork() as it will, in that case, be made synonymous to fork(2).””

于是,从BSD4.4开始,他们让vfork和fork变成一样的了

但在后来,NetBSD 1.3 又把传统的vfork给捡了回来,说是vfork的性能在 Pentium Pro 200MHz 的机器(这机器好古董啊)上有可以提高几秒钟的性能。详情见——“NetBSD Documentation: Why implement traditional vfork()

今天的Linux下,fork和vfork还是各是各的,不过,还是建议你不要用vfork,除非你非常关注性能。

fork和vfork的更多相关文章

  1. fork与vfork

    先看一个fork的例子: ; int main(void) { int var, pid; ; ) { printf("vfork error"); exit(-); } ) { ...

  2. fork、vfork、clone区别

    在Linux中主要提供了fork.vfork.clone三个进程创建方法. 问题 在linux源码中这三个调用的执行过程是执行fork(),vfork(),clone()时,通过一个系统调用表映射到s ...

  3. Linux下fork()、vfork()、clone()和exec()的区别

    转自Linux下fork().vfork().clone()和exec()的区别 前三个和最后一个是两个类型.前三个主要是Linux用来创建新的进程(线程)而设计的,exec()系列函数则是用来用指定 ...

  4. c语言exit和return区别,在fork和vfork中使用

    转自c语言exit和return区别,在fork和vfork中使用 exit函数在头文件stdlib.h中. 简述: exit(0):正常运行程序并退出程序: exit(1):非正常运行导致退出程序: ...

  5. 进程创建函数fork()、vfork() ,以及excel()函数

    一.进程的创建步骤以及创建函数的介绍 1.使用fork()或者vfork()函数创建新的进程 2.条用exec函数族修改创建的进程.使用fork()创建出来的进程是当前进程的完全复制,然而我们创建进程 ...

  6. fork与vfork详解

    一.fork函数 要创建一个进程,最基本的系统调用是fork,系统调用fork用于派生一个进程,函数原型如下: pid_t fork(void)  若成功,父进程中返回子进程ID,子进程中返回0,若出 ...

  7. linux 进程创建clone、fork与vfork

    目录: 1.clone.fork与vfork介绍 2.fork说明 3.vfork说明 4.clone说明5.fork,vfork,clone的区别 内容: 1.clone.fork与vfork介绍 ...

  8. 1.2 Linux中的进程 --- fork、vfork、exec函数族、进程退出方式、守护进程等分析

    fork和vfork分析: 在fork还没有实现copy on write之前,Unix设计者很关心fork之后立即执行exec所造成的地址空间浪费,也就是拷贝进程地址空间时的效率问题,所以引入vfo ...

  9. UNIX高级环境编程(9)进程控制(Process Control)- fork,vfork,僵尸进程,wait和waitpid

    本章包含内容有: 创建新进程 程序执行(program execution) 进程终止(process termination) 进程的各种ID   1 进程标识符(Process Identifie ...

  10. fork()、vfork()、clone()和exec()

    前三个和最后一个是两个类型.前三个主要是Linux用来创建新的进程(线程)而设计的,exec()系列函数则是用来用指定的程序替换当前进程的所有内容.所以exec()系列函数经常在前三个函数使用之后调用 ...

随机推荐

  1. ML学习笔记之TF-IDF原理及使用

    0x00 什么是TF-IDF TF-IDF(Term Frequency-Inverse Document Frequency, 词频-逆文件频率). # 是一种用于资讯检索与资讯探勘的常用加权技术. ...

  2. 【转载】UiPath,UiPath教程,UiPath视频教程 RPA

    第一课-UiPath简介 第二课-变量和数据类型第1部分-基础讲解 第二课-变量和数据类型第2部分-实例讲解 第二课-流程控制第1部分-ifelse语句讲解 第二课-流程控制第3部分-高级流程控制语句 ...

  3. WinExec, ShellExecute,CreateProcess 区别

    其中以WinExec最为简单,ShellExecute比WinExec灵活一些,CreateProcess最为复杂. WinExec 有两个参数,前一个指定路径,后一个指定显示方式. ShellExe ...

  4. ES6 字符串&正则表达式

    目录 第二章 字符串和正则表达式UTF-16码位codePointAt()方法String.fromCodePoint()方法normalize()方法正则表达式u修饰符其他字符串变更字符串中的字串识 ...

  5. JavaScript 运算符(Operator)

    一.算数运算符 1.加法(+) 表示操作数相加:  处理特殊值规则: 如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来: 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后 ...

  6. TensorFlow NMT的数据处理过程

    在tensorflow/nmt项目中,训练数据和推断数据的输入使用了新的Dataset API,应该是tensorflow 1.2之后引入的API,方便数据的操作.如果你还在使用老的Queue和Coo ...

  7. (九)Kubernetes 存储卷

    Kubernetes存储卷概述 Pod本身具有生命周期,这就带了一系列的问题,第一,当一个容器损坏之后,kubelet会重启这个容器,但是文件会丢失-这个容器会是一个全新的状态:第二,当很多容器在同一 ...

  8. python测试开发django-66.图片403forbidden

    前言 用 django 开发 web 页面,在 HTML 页面上添加图片时,发现本地图片可以正常显示,但是添加一个互联网的图片,却不能正常显示. 本地 static 图片 先在本地 static 放一 ...

  9. git 学习笔记 —— 获取远端分支并修改后提交至远端仓库

    笔者最近进行开发过程中,所有参与者的代码需要通过 git 上传到远端仓库中,不同的模块对应不同的 git 分支,不同模块的数据需要从远端仓库中获取.这里记录下笔者从远端仓库中获取分支数据,进行修改,最 ...

  10. HDU6701:Make Rounddog Happy(启发式分治)

    题意:给定数组a[],求区间个数,满足区间的数各不同,而且满足maxval-len<=K: 思路:一看就可以分治做,对于当前的区间,从max位置分治. 对于这一层,需要高效的统计答案,那么对短的 ...