分类:
2008-11-26 17:57:16
8.4 vfork function
本文侧重讨论和测试了vfork产生的多进程对标准库,STDOUT_FILENO descriptor,以及exit()函数,_exit()对他们的影响。
Vfork是为了那些一产生就立马执行exiec的进程而产生的,这个东西并不被推荐使用,所以有些版本的unix将其摒弃,但有些有将其加了进来。之所以不推荐,我觉得是因为他与父进程共享数据区,并不进行copy操作,也不进行copy-on-write,所以容易造成混乱。它的优点是:如果仅仅正常使用它,速度较快。即一fork出来,立马exec或者exit。
Vfork函数类似fork函数,不过区别:
1. Fork出来的子进程采取了copy-on-write,即需要时会将父进程的page拷贝给子进程。而vfork采取不拷贝,与父进程共享数据区。注意,子进程还是拷贝了一份descriptor列表,即打开的文件列表。所以如果子进程关闭一个打开的descriptor,那么不会影响父进程对该descriptor的访问。
2. 父进程会等待vfork出来的子进程先执行,然后才会执行,要不然就会block。所以vfork出来的子进程千万不要去首先等待父进程的一个事件,那样会死锁。
子进程共享了父进程的数据,但有自己的一份独立的descriptor表,但是与父进程共享standard i/o library的数据,即他们共享stream数据结构。所以父子进程有一方fclose了一个stream,即一个FILE*,那么两个进程就都不能再使用这个stream结构了,但是,由于父子进程各有一份descriptor列表,因此虽然一方关闭了一个stream,进而也关闭了其对应的descriptor,但仅仅是关闭了一方的descriptor,使这一方不能再使用该descriptor,但是由于另一方的descriptor依然存在,它依然指向file table 中的一个entry,所以fille table entry不会关闭,另一方依然可以使用其descriptor对该文件进行操作。
一个程序调用exit()退出时,会对其standard library的数据进行cleanup工作,调用_exit()退出时,不会进行standard library的cleanup 工作。貌似,如果子进程先调用exit()的话就会关闭父子上方共享的file streams数据,会影响到父进程的standard library的工作。但是我们测试结果是只要子进程不显式地fclose关闭文件的stream,就不影响父进程对该stream的访问。结论:由于程序退出的时候,会自动关闭位于standard i/o library下层的文件descriptor。所以stream结构就没必要去清除了。所以子进程调用_exit()或者exit()都没有清除文件的stream结构。那么exit()的cleanup工作都干了什么? 将streams flush算不算?
(一)单进程standard
i/o stream和descriptor测试例子:
#include
#include
#include
Char buffer[] = “contents here\n”;
Int main()
{
//在我们关闭stdout stream之前,我们用STDOUT_FILENO这个descriptor去输出
Puts( “Before we close the stdout stream” );
Write( STDOUT_FILENO, buffer, sizeof(buffer) );
Fsync(STDOUT_FILENO);
//在我们关闭stdout stream之后,我们看STDOUT_FILENO还是否有效
Puts( “we are going to close the stdout stream” );
Fclose( stdout );
Write( STDOUT_FILENO, buffer, sizeof(buffer) );
Fsync( STDOUT_FILENO );
}
运行结果:
Before we close the stdout stream
contents here
we are going to close the stdout stream
可见,关闭了本进程的标准输出流, 进而关闭了STDOUT_FILENO, 所以本进程就不能再使用STDOUT_FILENO输出了。
(二)使用vfork多进程standard i/o stream和descriptor测试例子:
我们只让子进程调用exit()
#include
#include
#include
Char buffer[] = “contents here\n”;
Int main()
{
//在我们关闭stdout stream之前,我们用STDOUT_FILENO这个descriptor去输出
Puts( “Before we close the stdout stream” );
Write( STDOUT_FILENO, buffer, sizeof(buffer) );
Fsync(STDOUT_FILENO);
If( vfork() == 0 )
{
//child, we just exit()
Exit();
}
Else
{
//尽管自己进程是先运行的,我们还是等2秒等他结束了再说
Sleep( 2 );
}
//在子进程退出后,我们看父进程的stdout还是否有效
Puts( “child has exited” );
}
结果 :
Before we close the stdout stream
contents here
等待一段时间后输出了:
child has exited
可见,子进程的exit()并没有关闭父子进程共享的stdout stream,而仅仅关闭了自己的STDOUT_FILENO descriptor,而不影响父进程的descritpor,所以父进程的stdout stream和STDOUT_FILENO都是可用的。
(三)使用vfork,多进程standard i/o stream和descriptor测试的例子
我们让子进程显式调用fclose(stdout)
#include
#include
#include
Char buffer[] = “contents here\n”;
Int main()
{
//在我们关闭stdout stream之前,我们用STDOUT_FILENO这个descriptor去输出
Puts( “Before we close the stdout stream” );
Write( STDOUT_FILENO, buffer, sizeof(buffer) );
Fsync(STDOUT_FILENO);
If( vfork() == 0 )
{
//child, we just exit()
//子进程显式关闭stdout stream,会导致父进程的stdout stream也被关闭,但是
//不影响父进程的STDOUT_FILENO的使用
Fclose(stdout);
Exit();
}
Else
{
//尽管自己进程是先运行的,我们还是等2秒等他结束了再说
Sleep( 2 );
}
//在子进程关闭stdout stream后,我们看父进程的stdout stream是否有效
Puts( “child has exited” );
//在子进程关闭stdout stream之后,我们看父进程的STDOUT_FILENO还是否有效
Write( STDOUT_FILENO, buffer, sizeof(buffer) );
Fsync(STDOUT_FILENO);
}
结果:
Before we close the stdout stream
contents here
等一段时间后输出了:
contents here
这说明了,父进程的stdout已经不能再使用了,子进程已经把它关闭了,父子共享stream。而父进程的descritpor还有效。
(四) 多进程vfork测试例子,关闭standout后各个进程的STDOUT_FILENO的使用
下面的例子在child里关闭stdout标准流,后child的STDOUT_FILENO也不能用了。说明本标准输出库是要关闭STDOUT_FILENO的。而在parent进程里,虽然stdout不能用了,但是STDOUT_FILENO依然可以用write去写。这充分证明了,两个进程虽然共享内存,但是他们有自己的descriptor拷贝。
#include
#include
#include
#include
char childbuf[100] = {"child still can use STDOUT_FILENO\n"};
char pbuf[100] = {"parent can still use STDOUT_FILENO\n"};
char nbuf[100] = {"child can not use STDOUT_FILENO but parent can still\n"};
int main()
{
setbuf(stdout, NULL);
puts("before vfork");
int ret = vfork();
if( ret<0 )
exit(0);
if( ret == 0 )
{
//child
puts("in child, we are closing stdout");
fclose(stdout);
if( write( STDOUT_FILENO, childbuf, sizeof(childbuf) ) != sizeof(childbuf) )
memcpy( pbuf, nbuf,100 );
exit(0);
}
//father
sleep(2);
write( STDOUT_FILENO, pbuf, sizeof(pbuf) );
return 0;
}
运行结果:
shaoting@desktopbj-LabSD:/home/shaoting/mytest> g++ vforktest.cpp
shaoting@desktopbj-LabSD:/home/shaoting/mytest> ./a.out
before vfork
in child, we are closing stdout
child can not use STDOUT_FILENO but parent can still
shaoting@desktopbj-LabSD:/home/shaoting/mytest>
看输出,可以证明我的的结论。
总结论:vfork出来的父子进程之间:
1.父子共享stream 结构,但不共享descriptor
2.任意进程关闭stream,就会关闭与他共享该stream的其它进程的该steam,但仅会关闭本进程的descriptor。
3.Exit()函数的cleanup操作并不清除stream结构,即不关闭stream。
4.进城退出的时候,自己的打开的descriptor被关闭应该是肯定的。
5.关闭文件,要看关闭的是stream还是descriptor,是有分别的。