摘要
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带你走进科学:
- main函数进来不久,程序用atexit将end设置成收尾函数(headool自创名词, 它在main函数return或exit()之后被调用);
- 接着fork,进程一分为二;
- 父进程 等待的子进程退出。子进程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
。