vfork & fork
转载 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,除非你非常关注性能。
(全文完)
vfork & fork的更多相关文章
- fork()和vfork()的区别(转载)
fork和vfork 转载 http://coolshell.cn/articles/12103.html 在知乎上,有个人问了这样的一个问题--为什么vfork的子进程里用return,整个程序会挂 ...
- linux进程解析--进程的创建
通常我们在代码中调用fork()来创建一个进程或者调用pthread_create()来创建一个线程,创建一个进程需要为其分配内存资源,文件资源,时间片资源等,在这里来描述一下linux进程的创建过程 ...
- 自己实现一个shell
用C实现一个简单的交互式shell,要求:当用户输入一行命令时,识别程序名和参数并调用适当的exec函数执行程序,等待执行完成后给出提示符. exec函数实际上是六种以exec开头的函数,统称exec ...
- busybox启动流程简单解析:从init到shell login
关键词:kernel_init().init.inittab.wait/waitpid.fork/vfork.setsid().execvp/execlp.dup2等等. 由于遇到一系列定制,从ini ...
- fork-vfork -exit&_exit
昨天帮人查bug,发现了一个vfork fork exit _exit不分导致的问题. 使用vfork 后调用exit导致的问题. 主要需要弄清楚他们之间的区别: 1. fork ():子进程拷 ...
- Linux&c 文件操作,线程进程控制,网络编程,简单知识点梳理
一:文件操作 在linux下,一切皆文件,目录是文件,称为目录文件,内容是该目录的目录项(但是目录只有内核可以编辑,超级用户也不可以编辑),设备也是设备文件,在/dev存放的就是一些设备文件,linu ...
- Linux&c 文件操作,线程进程控制,网络编程,简单知识点梳理
一:文件操作 在linux下,一切皆文件,目录是文件,称为目录文件,内容是该目录的目录项(但是目录只有内核可以编辑,超级用户也不可以编辑),设备也是设备文件,在/dev存放的就是一些设备文件,linu ...
- fork与vfork
先看一个fork的例子: ; int main(void) { int var, pid; ; ) { printf("vfork error"); exit(-); } ) { ...
- fork与vfork的区别与联系
fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别: 1. fork ():子进程拷贝父进程的数据段,代码段 vfork ( ):子进程与父进程共享数据段 2. fo ...
随机推荐
- JZYZOJ 1385 拉灯游戏 状态压缩 搜索
http://172.20.6.3/Problem_Show.asp?id=1385 刚开始想的时候一直以为同一排不同的拉灯顺序对结果是有影响的,手推了好多遍才发现拉灯结果只和拉的灯有关,这也要打 ...
- 【推导】Gym - 101243A - Fried Fish
题意:有n条鱼,煎一条鱼的一面要一分钟,锅只能同时煎K条鱼,问最少时间是? 想想小时候那个脑筋急转弯,3条鱼只需2分钟.可以大胆猜测,n条鱼,只需ceil(n*2/K)分钟,即一定能非常高效地煎完,每 ...
- 【递归】【线段树】【堆】AtCoder Regular Contest 080 E - Young Maids
给你一个1~n的排列p,n是偶数,每次从中任选一对相邻的数出来,插到排列q的开头,如此循环,问你所能得到的字典序最小的排列q. 我们先确定q开头的两个数q1,q2,q1一定是p的奇数位的最小的数,而q ...
- [转]OpenSessionInView模式
OpenSessionInView模式解决的问题: * hibernate事物边界问题 * 因session关闭导致hibernate延迟加载例外的问题 事物边界: 一个事物的完成应该 ...
- [移动应用安全]移动应用安全培训PPT
这是年初给公司做的移动应用安全培训,主要是针对开发的兄弟们,让大家对目前的移动领域的安全形势有个了解,以及不正确的开发所带来的危害,另外还配了个详细的word版的移动应用开发指南,后面一起附上:).
- HDU 3537 Daizhenyang's Coin(博弈,翻硬币)
Daizhenyang's Coin Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Other ...
- Android Json的使用(2) 使用Jackson解析和生成json
使用Jackson的三种方式 数据绑定模式:使用最方便 流模式:性能最佳 树模式:最灵活 以最常用的数据绑定模式为例 Json数据如下 { "name" : { "fir ...
- Setup JIRA Software 7.6.2 on Oracle Linux 6.8
OS Oracle Linux 6.8 V138414-01.iso Database mysql5.6.30 MySQL-5.6.30-1.el6.x86_64.rpm-bundle.tar JIR ...
- google的开源项目总结
转自http://www.feng5166.com/blog/424.html google的开源项目值得我们一用的,这些项目很有意义,甚至可以直接用在我们自己的工作上!学习编程的的一个比较好的方式就 ...
- PASCAL 的开源工具
PASCAL 的开源工具: 1)free pascal 代码编译器 http://www.freepascal.org/ 2)lazarus 图形界面开发工具 http://www.la ...