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。(關于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 的機器(這機器好古董?。┥嫌锌梢蕴岣邘酌腌姷男阅堋T斍橐姟?a class=" wrap external" target="_blank" rel="nofollow noreferrer">NetBSD Documentation: Why implement traditional vfork()

今天的Linux下,fork和vfork還是各是各的,不過,還是建議你不要用vfork,除非你非常關注性能。

轉自:http://coolshell.cn/articles/12103.html

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2027

(0)
s19930811s19930811
上一篇 2016-08-15 12:11
下一篇 2016-08-15 12:12

相關推薦

  • Linux系統之用戶和組

    Linux系統之用戶和組 1、什么是用戶 用戶:資源獲取標識符,資源分配,安全權限模型的核心要素之一 2、沒有用戶,操作系統可否正常執行? 答案是肯定的 在Linux系統上,用戶管理是基于用戶名和密碼的方式進行資源的分配, Username/UID分為以下類別:     管理員:root, 0  &…

    Linux干貨 2016-08-04
  • 馬哥教育網絡班21期+第10周課程練習

    1、請詳細描述CentOS系統的啟動流程(詳細到每個過程系統做了哪些事情) POST(Power On Self Test): 檢測系統外圍關鍵設備(如:CPU、內存、顯卡、I/O、鍵盤鼠標等)是否正常。 加載BIOS(Basic Input and Output System): 根據在BIOS中設置的系統啟動順序來搜索用于啟動系統的驅動器(硬盤、光盤、U…

    Linux干貨 2016-09-08
  • 編譯安裝httpd-2.2.27.tat.gz及配置常見參數

    一、編譯安裝的整體步驟  1、在官網下載源碼,并解壓 2、切換到其目錄中  3、執行./configure 4、編譯   二、編譯中及安裝后配置常見的參數及其說明 編譯中配置 1)指定安裝路徑 –prefix=/usr/local/Pacakage_name  指定安裝路徑 –sysc…

    Linux干貨 2016-08-24
  • keepalive高可用haproxy實現URL資源的動靜分離

    keepalive高可用haproxy實現動靜分離URL資源 實現要點: (1) 動靜分離discuzx,動靜都要基于負載均衡實現; (2) 進一步測試在haproxy和后端主機之間添加varnish緩存; (3) 給出拓撲設計; (4) haproxy的設定要求:     (a…

    Linux干貨 2016-11-12
  • Linux之啟動和內核管理

     Linux之啟動和內核管理     本文主要包括以下內容一  CentOS 5和6的啟動流程二  服務管理三  Grub管理四  自制Linux五  啟動排錯六  編譯安裝內核   Linux組成Linux: kernel+rootfskernel: 進程管…

    Linux干貨 2016-09-15
  • 馬哥教育網絡班21期+第7周課程練習

    1、創建一個10G分區,并格式為ext4文件系統;     fdisk /dev/sdb    n p 1 +10G w (1) 要求其block大小為2048, 預留空間百分比為2, 卷標為MYDATA, 默認掛載屬性包含acl…

    Linux干貨 2016-08-22
欧美性久久久久