在子进程中调用exit()函数对标准I/O流的影响

我们知道exit()函数是用来终止程序用的,它将调用exit系统调用,将程序状态作为参数返回给内核。但是之于_exit()和_Exit(),exit()的不同之处在于,exit()在调用exit系统调用之前,它将做一些最后的处理,包括两个部分:调用由atexit()注册的终止处理程序;关闭打开的流。这里我们要讨论的是第二点。


fork()

我们都知道,由fork()创建的子进程是父进程的副本,即使是采用了cow技术,在修改时也还是会创建副本再修改。因此,在父进程中打开的流也会复制到子进程中,如下面的代码。




上面的代码可以看出,在fork()之前我们有一条printf输出语句,输出“before fork()!”字符串,然后父进程创建了一个子进程之后,在子进程中什么都没有做,只是调用了exit()函数来关闭流。我们分析一下过程,从父进程开始执行,打开了一个流,输出了“before fork()”字符串,然后紧接着fork()了一个子进程,然后是先执行哪个进程我们并不知晓,但是从运行结果来看貌似是printf语句执行了两次。首先有一次是父进程执行的printf语句,而另外一次呢?这是因为,在父进程调用fork()时,之前输出的字符串仍然还在缓冲区中,然后fork()函数将父进程的数据空间复制到子进程中,然后当进程终止时,将调用exit()函数,并将清除缓冲区中的内容。将如这里将exit()函数改成_exit(),情况又不同了,如下




我们可以看到,什么都没有输出。因为在父进程的标准IO流输出缓冲区中有字符串,然后fork子进程后在子进程中也有一个该字符串的副本,但是由于两个进程都是调用_exit函数来终止进程,而这个函数在终止进程的时候并不刷新IO缓冲区,因此会导致父、子进程的输出均未被刷新到标准设备上。



vfork()

我们知道,vfork()创建的子进程在调用exec或者exit函数之前将与父进程共用虚拟地址空间,如果我们在子进程调用这两个函数之前关闭就关闭了打开流,就等于关闭了父进程的打开流,另外,由于vfork总是使得子进程先执行,这会导致父进程无法输出流。如下代码可以表示之。




从上面的代码可以看出,我们在父、子进程中都调用exit函数来刷新缓冲区,但是由于子进程在exit之前都是与父进程共用地址空间,而且vfork总是让子进程先执行,因此在子进程中调用exit函数,先刷新了缓冲区,即刷新了两个进程的共有缓冲区,断开了IO流,然后终止自己,然后父进程被调度执行,然后父进程在执行exit的时候,已经不存在打开着的流了,因此在试图刷新IO缓冲区时没有任何操作,因此在父进程中没有执行任何语句了。


因此我们发现,fork的父子进程总是独立的,而vfork的子进程总是用来exec一个新程序的,即后者用在用户已经知道创建子进程的目的是用来执行新程序的情况下。


另外,根据vfork的性质,我们很容易想到进程中的线程,同一进程中的线程总是共用进程的地址空间的,其实线程也是通过vfork来实现的。
发布了68 篇原创文章 · 获赞 42 · 访问量 22万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章