fork和vfork都是创建进程,exit和_exit都是退出进程,但之间也有些细微的区别,并且很让人迷惑。一般情况下用fork创建的子进程用exit结束,而用vfork创建的进程则用_exit。那他们具体的区别在哪儿了,为什么这么做!
预备知识:程序的存储空间布局,系统的缓冲区
c程序在内存中从高地址到低地址依次为:
命令行参数和环境变量:存储命令行的各个参数和系统的环境变量
栈段:局部变量(函数内部定义使用的变量)和函数调用存放的位置,此块由操作系统处理
堆段:通常在堆里面进行动态存储分配,malloc就是分配堆里面的空间,通常由程序员控制
数据段:存储一些初始化数据和非初始化数据
正文段:cpu执行的机器码
系统的缓冲区:
操作系统为了提高文件都写效率,都使用的缓存机制,当我们写入一个文件时,很有可能写入了缓冲区而没有真真意义上的写入硬盘,缓存分为三个级别:
全缓冲:当缓冲空间(4096B或其它)写满后,一次性写入硬盘,文件缓存通常如此
行缓冲:遇到输入输出遇到换行符,执行写入操作,命令行界面就是使用行缓冲
无缓冲:这个就不用解释了
fork与vfork
执行fork创建子进程时,子进程将拷贝父进程的数据段、堆段和栈段,此时父子进程的数据一致当互相独立,各不影响,子进程对数据的处理不影响父进程。(其实当父子进程都不对三个段进行写操作时,父子进程仍然共享数据段、堆段和栈段,当有任何一方有写操作时,才产生副本,这种方式成为写是复制)
vfork与fork区别有两点:1、vfork创建的子进程与父进程共享数据段,2、vfork创建的子进程优先于父进程执行,当子进程执行时,父进程进入阻塞状态。
所以vfork产生的子进程对数据的修改一定可以影响到父进程的数据
exit与_exit
exit:当执行exit时,终止处理程序,执行标准的I/O清楚操作(将缓存中文件写入),调用atexit,调用_exit。由此可见,exit是加强版的_exit.
_exit:通知内核,进程结束
测试exit和_exit
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- printf("the first line\n");
- printf("the second line\n");
- exit(0);
- }
- eno@eno-ThinkPad-T420:~$ vim exit.c
- eno@eno-ThinkPad-T420:~$ gcc exit.c -o exit
- eno@eno-ThinkPad-T420:~$ ./exit
- the first line
- the second line
- eno@eno-ThinkPad-T420:~$
结果与预期相同,改为_exit(0)
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- printf("the first line\n");
- printf("the second line\n");
- _exit(0);
- }
- eno@eno-ThinkPad-T420:~$ ./_exit
- the first line
- the second line
- eno@eno-ThinkPad-T420:~$
结果怎么还是一样的,有的系统可能看到是没有输出,这都是正常的,每个系统最输出的缓存机制可能不一致,通常为行缓存,把他强制设置成全缓存看看:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- char buf[4096];
- //设置成全缓存
- setvbuf(stdout, buf, _IOFBF, 4096);
- printf("the frist line\n");
- printf("the second line\n");
- _exit(0);
- }
ok,结果与预期相同,缓冲区的数据全部没掉了
- eno@eno-ThinkPad-T420:~$ gcc _exit.c -o _exit
- eno@eno-ThinkPad-T420:~$ ./_exit
- eno@eno-ThinkPad-T420:~$
这么一比划,就基本上明白了为什么fork产生的子进程要用exit而不用_exit了,如果缓冲区的东西都丢了,子进程所产生的数据就不完整,这对于程序使用者来说是不可接受的。那为什么vfork一般用_exit了,难道不怕丢数据么?用vfork的子程序通常用_exit结束,而父进程通常用exit结束,这样做数据就不会丢失,因为他们共享一个数据段,父进程结束会处理所有打开的数据流,将其写入。万一子程序也用exit结束,那就比较麻烦了,试想一下父子进程都把同一份数据写入,那个文件的价值有在哪里。