(转帖)为什么vfork的子进程里用return,整个程序会挂掉,而且exit()不会?

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

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

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

#include
#include
#include
intmain(void) {
intvar;
var = 88;
if((pid = vfork()) < 0) {
  printf("vfork error");
  exit(-1);
} elseif(pid == 0) { /* 子进程 */
  var++;
  return0;
}
printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var);
return0;
}

基础知识

首先说一下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,除非你非常关注性能。

时间: 2024-07-28 19:15:11

(转帖)为什么vfork的子进程里用return,整个程序会挂掉,而且exit()不会?的相关文章

说一个MySQL里可能90%的程序员都会遇到的坑

说一个MySQL里可能90%的程序员都会遇到的坑最近我遇到了一个bug,我试着通过Rails在以"utf8"编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误:Incorrect string value: '\xF0\x9F\x98\x83 <-' for column 'summary' at row 1 我用的是UTF-8编码的客户端,服务器也是UTF-8编码的,数据库也是,就连要保存的这个字符串" 原文地址:https://blog.51c

转帖: 代理模式在游戏里的应用

原帖地址及作者的github: http://alloyteam.github.com/StreetFighter/ 代理模式的定义是把对一个对象的访问, 交给另一个代理对象来操作. 举一个例子, 我在追一个MM想给她送一束花,但是我因为我性格比较腼腆,所以我托付了MM的一个好朋友来送. 这个例子不是非常好, 至少我们没看出代理模式有什么大的用处,因为追MM更好的方式是送一台宝马. 再举个例子,假如我每天都得写工作日报( 其实没有这么惨 ). 我的日报最后会让总监审阅. 如果我们都直接把日报发给

不要困在自己建造的盒子里——写给.NET程序员(转)

从我个人的观点看,本文中“.NET程序员”是指具有如下特点的程序员群体: 学习.工作的技术范围均局限于.NET平台及衍生,对.NET之外的技术没有主动接触或学习的欲望.不断学习各种.NET平台上的库或框架,如ADO.NET,ASP.NET MVC,WPF,Silverlight,WCF,WP,EF,NHibernate……工作无法脱离Visual Studio,习惯于图形化的工作环境.时常抱怨微软的技术更新太快,微软开发平台包办太多以至于自己身价贬值.对面向对象.设计模式.软件架构等东西具有极大

不要困在自己建造的盒子里——写给.NET程序员(附精彩评论)

转自:http://kb.cnblogs.com/page/92260/ 此文章的主旨是希望过于专注.NET程序员在做好工作.写好.NET程序的同时,能分拨出一点时间接触一下.NET之外的东西(例如10%-20%的时间),而不是鼓动大家什么都去学最后什么都学不精,更不是说.NET不行或劝大家放弃.NET.恕我愚钝,此主旨在文中表达不够清楚,看评论中很多朋友误解了,特此说明. 另外,本文中的观点并不全部是我个人的想法,相当一部分来自我以前聊过天的某些大牛,他们很多来自微软.百度.腾讯等知名企业,并

python中try里有return时,finally还执行不执行

写一个函数,在try里返回,最后在finnaly里打印语句,代码如下 def fun1(): x = 1 try: print('执行try') x = x + 1 # x = x / 0 # 执行次语句时,finally执行时,x的值依然是4 return print('执行try的return', x) except: print('异常') return x finally: x += 1 x += 1 print('finally执行:', x) 执行结果如下图所示: 由此可以看出,函数

try catch finally,try里有return,finally还执行么?

1.不管有木有出现异常,finally块中代码都会执行:2.当try和catch中有return时,finally仍然会执行:3.finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的:4.finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值.

java里的分支语句--程序运行流程的分类(顺序结构,分支结构,循环结构)

JAVA里面的程序运行流程分三大类: 1,顺序结构:顺序结构就是依次执行每一行代码 2,分支结构:分支结构就是按不同的条件进行分支 3,循环结构:一段代码依条件进行循环执行. 其中,分支结构有两大类: if...else...结构和switch...结构       switch中的case支持的数据类型只有四种:    char  (字符型)    byte  (比特型)    short (短整型)    int     (整型) 注意switch里面的判断语句后面需要加break,否则的话

vfork 为何挂掉

在知乎上,有个人问了这样的一个问题——为什么vfork的子进程里用return,整个程序会挂掉,而且exit()不会?并给出了如下的代码,下面的代码一运行就挂掉了,但如果把子进程的return改成exit(0)就没事. 我受邀后本来不想回答这个问题的,因为这个问题明显就是RTFM的事,后来,发现这个问题放在那里好长时间,而挂在下面的几个答案又跑偏得比较严重,我觉得可能有些朋友看到那样的答案会被误导,所以就上去回答了一下这个问题. 下面我把问题和我的回答发布在这里,也供更多的人查看. 1 2 3

vfork与fork(转载)

最近学了一些关于Unix/Linux下C编程的知识,大致了解了关于基于文件描述符的IO.基于流的IO.进程.进程间通信.信号.网络编程.gtk+编程,等等,以及其对应的相关API,看完后觉得也就这样,甚至有些傲慢,这些平日听起来高深的技术也就这些东西,但同时也感到疑惑:怎么可能,绝对不是我第一印象感觉的那样,绝对还有很多我不知道的. 幸好当天晚上没事逛了陈皓的coolshell,看到了一篇文章<vfork 挂掉的一个问题>,才感觉到自己的第一感觉是多么的无知,并不是它简单,而是因为难的.复杂的