fork出来的子进程最好总是用_exit退出

来源:互联网 发布:红警扫矿软件手机 编辑:程序博客网 时间:2024/04/30 04:04

摘要

fork家族函数(fork, vfork)所创建的子进程最好用_exit()退出,不管是通过 man vfork还是通过看相关网站都能得到这一结论。本文主要介绍一些案例来说明使用 exit()函数退出进程会引起弊病。

正文

linux下man vfork可以看到这样一句话:

The child must not return from the current function or call exit(), but may call _exit().

下面举几个因使用exit从fork出来的子进程退出而导致各种蛋疼的例子

atexit()掉包返回值

    #include <stdio.h>    #include <stdlib.h>    void end(void)    {        exit(-1);    }    int main()    {        int pid = 0;        atexit(end);        pid = fork();        if (pid == 0) {            exit(0);        } else if (pid > 0) {            int status = 0;            wait(&status);            printf("%d\n", status);        }        return 0;    }

大家猜它输出什么?有的同学可能一下子就看出应该输出0,headool曾经也这么相信爱卿。实际上它输出为65280((unsigned char)-1 * 256)。现在headool带你走进科学: 

  1. main函数进来不久,程序用atexit将end设置成收尾函数(headool自创名词, 它在main函数return或exit()之后被调用);
  2. 接着fork,进程一分为二;
  3. 父进程 等待的子进程退出。子进程exit()退出后调用收尾函数end(),再次调用exit(-1)( 不会再递归调用end())。

退出码本来是妥妥的0,现在被调包成-1。

这样的exit,能靠谱吗?把它换成_exit(头文件unistd.h)或_Exit(头文件 stdlib.h)试试。

不要以为只有C语言有这问题,python有这问题,perl估计也有(这个headool没试过)。 python中解决此问题有对应的os._exit,perl中有对应的POSIX::_exit

伤不起的printf

下面的程序输出什么?

#include <stdio.h>#include <stdlib.h>int main(){    int pid = 0;    printf("headool");    pid = fork();    if (pid == 0) {        exit(0);    } else if (pid > 0) {        int status = 0;        wait(&status);    }    return 0;}

和直觉相反,它输出为: headoolheadool(没有回车)

理解上一结果需要知道以下几个unix的事实:

  • fork是父子进程间的全拷贝(写时才复制),包括io缓存数据。
  • printf所打印的数据会经过行缓冲,只有在遇到\n时才会真正刷到文件(stdout)。
  • exit时进程所有文件将关闭,所有缓存数据会因此刷到文件。

明白上一例子到底怎么回事了吧。怎么办?将exit换成_exit!


最后说一个看上去更离奇的例子,它stdout的结果和重定向到文件里的不一样,但exit 换成_exit后天下又重新太平。

#include <stdio.h>#include <stdlib.h>int main(){    int pid = 0;    printf("headool\n");//较上个示例程序仅仅加了一个\n    pid = fork();    if (pid == 0) {        exit(0);    } else if (pid > 0) {        int status = 0;        wait(&status);    }    return 0;}

其中的原因以后再细说,查查setvbuf函数吧。 headool提醒你:用fork,记得_exit

原创粉丝点击