vfork 挂掉的一个问题

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

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

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

#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。

你 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干废掉了,你让我怎么执行?

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

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

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

也就是说,对于fork后并不是马上拷贝内存,而是只有你在需要改变的时候,才会从父进程中拷贝到子进程中,这样fork后立马执行exec的成本就非常小了

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

转自   酷 壳 – CoolShell.cn  

<think>好的,我现在需要回答用户关于vfork系统调用的问题。首先,用户希望了解vfork在类Unix操作系统中的相关信息。根据用户提供的系统级指令,我需要确保回答符合格式要求,比如行内数学表达式用$...$,独立公式用$$...$$,正确使用中文,生成相关问题,并在引用处添加标识。 首先,我应该回忆vfork的基本概念。vfork是Unix系统中的一个系统调用,用于创建新进程,通常用于执行exec系列函数。与fork不同,vfork不会复制父进程的页表,而是让子进程共享父进程的地址空间,这样可以更高效。但使用时需要特别小心,避免修改父进程的数据,否则可能导致不可预料的结果。 接下来,我需要整理vfork的主要特点。例如,它创建子进程时暂停父进程,直到子进程调用exec或exit;子进程不应修改内存,否则会影响父进程。此外,vfork的历史背景也很重要,早期fork没有写时复制(Copy-on-Write)时,vfork更高效,但现在可能被优化过的fork取代。 然后,根据用户提供的引用,我需要检查是否有相关内容可以引用。例如,引用[3]提到不需要调用setpgid(),因为spawn调用会等待子进程加入进程组后再返回。这可能与vfork的行为有关,因为vfork在子进程执行exec或exit之前,父进程是暂停的,所以避免了竞争条件。因此,在回答vfork的父进程行为时,可以引用这一点,说明vfork如何避免传统fork中的某些问题。 接下来,用户要求生成相关问题。我需要考虑用户可能的后续疑问,比如vforkfork的区别、适用场景、安全性问题、现代系统的使用情况等。同时,还要确保每个引用正确对应到相关内容。 在结构上,我需要按照用户示例中的方式,先介绍vfork,再分点详细说明特点、注意事项、与fork的区别,然后结合引用内容解释相关机制。最后列出相关问题。 要注意避免使用Markdown格式,但用户示例中使用了代码块,所以需要保持Python代码的正确格式。不过用户的问题是关于vfork,可能不需要代码示例,但如果有相关代码,比如使用vfork的C程序片段,可以加入。但根据引用内容,可能不需要,所以可能直接文字描述。 最后,检查LaTeX语法是否正确,是否有行内公式,虽然当前问题可能不涉及数学表达式,但如果有相关内容,比如进程ID的计算,可能需要用$符号包裹。例如,进程ID变量$pid$。 现在,我需要确保所有引用正确放置。例如,在讨论vfork的父进程暂停时,引用[3]提到不需要setpgid()调用,因为spawn调用在子进程放入进程组后才返回,这可能与vfork的机制相关,所以可以在这里添加引用标识[^3]。 总结回答结构: 1. vfork的定义和用途 2. 主要特点:共享地址空间、父进程暂停、高效但危险 3. 注意事项:子进程不应修改内存、必须调用exec/exit 4. 与fork的区别:复制机制、性能、安全性 5. 现代系统中的使用情况:由于COW的forkvfork可能被替代 6. 引用相关部分,特别是关于进程组和竞争条件的引用[^3] 7. 生成相关问题 在写作过程中,要确保语言通顺,结构清晰,符合用户的要求。</think>### vfork系统调用解析 `vfork`是类Unix操作系统中用于创建新进程的系统调用,主要设计用于高效执行`exec`系列函数。其核心机制是通过**共享父进程地址空间**来避免传统`fork`的完整内存复制开销,但使用时需严格遵守特定限制[^1]。 #### 一、主要特点 1. **地址空间共享** 子进程与父进程共享内存空间,直到调用`exec`或`exit`。这避免了传统`fork`的写时复制(Copy-on-Write)机制,但要求子进程**不得修改内存**,否则会破坏父进程状态[^3]。 2. **父进程挂起** 父进程在`vfork`调用后暂停执行,直到子进程通过`exec`加载新程序或调用`exit`终止。这种设计消除了传统`fork`可能引发的进程组竞争条件,如引用[3]所述: > 父进程无需调用`setpgid()`,因为子进程已加入进程组后才会返回[^3]。 3. **高效但高风险** 在早期系统中,`vfork`比`fork`更高效,但现代系统通过优化`fork`的写时复制机制,使得`vfork`的使用场景逐渐减少。 #### 二、使用注意事项 - **子进程行为限制** 子进程只能调用`exec`或`exit`,若执行其他操作(如修改变量或调用非异步信号安全函数),可能导致未定义行为。 - **返回值处理** `vfork`在父进程中返回子进程PID,在子进程中返回0,与`fork`类似。 #### 三、与`fork`的关键区别 | 特性 | vfork | fork | |---------------|---------------------------|--------------------------| | 内存复制 | 无,共享父进程空间 | 写时复制(COW) | | 父进程状态 | 挂起直到子进程结束 | 立即恢复执行 | | 适用场景 | 子进程立即调用`exec` | 通用进程复制 | | 安全性 | 高风险,需严格遵循规则 | 相对安全 | #### 四、现代系统中的使用 由于`fork`的COW机制已高度优化,`vfork`在Linux等系统中**可能仅是`fork`的别名**,但仍需遵循其语义限制[^1]。 ```c #include <unistd.h> pid_t vfork(void); // 函数原型 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值