在知乎上,有个人问了这样的一个问题——为什么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() 没有修改函数栈,所以,父进程得以顺利执行

但是!注意!如果你调用 exit() 函数,还是会有问题的,正确的方法应该是调用 _exit() 函数,因为 exit() 函数 会 flush 并 close 所有的 标准 I/O ,这样会导致父进程受到影响。(这个情况在fork下也会受到影响,会导致一些被buffer的数据被flush两次,这里可以参看《一个fork的面试题》)

关于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,除非你非常关注性能。

转载于:https://www.cnblogs.com/alantu2018/p/8503399.html

vfork 挂掉的一个问题相关推荐

  1. fork和vfork,return和exit的理解

    fork和vfork的差别: 1.fork是创建一个子进程,并把父进程的内存数据copy到子进程中. vfork是创建一个子进程,并和父进程的内存数据share一起. 2.vfork是这样的工作的: ...

  2. php 运行外部程序_php 中运行外部程序的一个潜在风险

    php 中有 exec system popen 等一系列运行外部程序的函数.在 web 环境中使用这些函数的时候,即使控制好了权限,保证了被执行程序本身的安全,还可能有另外的潜在风险. php 的这 ...

  3. Linux基本C编程fork、signal、time以及用printf在终端打印一个GUI窗口 - 使用cygwin

    1 fork, vfork示例 创建一个新进程的方法只有由某个已存在的进程调用fork()或vfork(): vfork创建新进程的主要目的在于调用exec函数执行另外的一个新程序,在没调用exec或 ...

  4. Kudu - 一个融合低延迟写入和高性能分析的存储系统

    Kudu 是一个基于 Raft 的分布式存储系统,它致力于融合低延迟写入和高性能分析这两种场景,并且能很好的嵌入到 Hadoop 生态系统里面,跟其他系统譬如 Cloudera Impala,Apac ...

  5. linux如何在C程序中使用exit,c语言exit和return区别,在fork和vfork中使用

    exit函数在头文件stdlib.h中. 简述: exit(0):正常运行程序并退出程序: exit(1):非正常运行导致退出程序: return():返回函数,若在main主函数中,则会退出函数并返 ...

  6. Linux fork与vfork的区别

    vfork用于创建一个新进程,而该新进程的目的是exec一个新进程,vfork和fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,不会复制页表.因为子进程会立即调用exec ...

  7. fork()函数与vfork()函数的区别

    1.fork()函数与vfork()函数 头文件: #include <sys/types.h> #include <unistd.h> 函数原型: pid_t fork(vo ...

  8. 【linux基础】fork与vfork

    fork 一个现有进程可以调用fork创建一个新进程. 返回值:子进程中返回0,父进程返回子进程ID,出错返回零. 子进程是父进程的副本. 一个现有进程可以调用fork函数创建一个新进程.由fork创 ...

  9. Linux系统编程(vfork和fork)

    文章目录 前言 一.vfork讲解 二.vfork使用 三.exit和_exit 1.exit和_exit对比 2.在vfork中的使用 四.vfork和fork区别 总结 前言 本篇文章讲解vfor ...

最新文章

  1. redis php 持久化,详解Redis RDB持久化、AOF持久化,
  2. 看清条款,小心网通陷阱
  3. DCMTK:修改DICOM文件的类
  4. matlab数字滤波器设计函数汇总(转载)
  5. 第三次学JAVA再学不好就吃翔(part103)--BufferedInputStream和BufferOutputStream
  6. solr基本查询和高级查询
  7. 美国组建半导体工作组或与中国推进海外并购冲突
  8. python post参数传递不成功_Python中的API构建指南:在Flask中进行API开发
  9. js 导出pdf上传至oss_前端上传图片到oss,压缩图片后上传至oss(补充图片文件旋转90度问题)...
  10. ACL 2021 | 火山翻译成绩斐然
  11. mysql kill 很多_MySQL--批量KILL连接
  12. 实践的意义——写给图像处理算法爱好者的建议
  13. 工作中关于 使用YUICompressor进行JS压缩 的问题小节
  14. CSA云安全指南V4.0 D9 D10
  15. 扫地机器人不取出水箱可以吗_小米扫地机器人水箱不出水是怎么回事
  16. 如何寻找综述性论文?
  17. Git Github操作简易教程
  18. CAD二次开发 根据多段线Polyline产生的线段Line需要做进一步处理才可以使用
  19. ppt关闭受保护视图
  20. 铁乐学python_day18-19_面向对象编程1

热门文章

  1. android程序贴吧,【Android 教程总结贴】归纳所有android贴
  2. python 字典 转 pandas DataFrame
  3. 【JVM】StackOverflowError与OutOfMemoryError
  4. 车道检测--VPGNet: Vanishing Point Guided Network for Lane and Road Marking Detection and Recognition
  5. 语义分割--Large Kernel Matters--Improve Semantic Segmentation by Global Convolutional Network
  6. linux用户登陆后无法加在.bashrc
  7. anaconda: import numpy报错:ImportError: DLL load failed: 找不到指定的模块。
  8. LeetCode 202. Happy Number--Python解法
  9. ASP .NET Core Web Razor Pages系列教程四:使用数据库进行交互 entity-framework(MySQL/MariaDB 版)
  10. ubuntu安装pip3