Chinaunix首页 | 论坛 | 博客
  • 博客访问: 993215
  • 博文数量: 200
  • 博客积分: 5011
  • 博客等级: 大校
  • 技术积分: 2479
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-27 15:07
文章分类

全部博文(200)

文章存档

2009年(12)

2008年(190)

我的朋友

分类:

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 librarycleanup 工作。貌似,如果子进程先调用exit()的话就会关闭父子上方共享的file streams数据,会影响到父进程的standard library的工作。但是我们测试结果是只要子进程不显式地fclose关闭文件的stream,就不影响父进程对该stream的访问。结论:由于程序退出的时候,会自动关闭位于standard i/o library下层的文件descriptor。所以stream结构就没必要去清除了。所以子进程调用_exit()或者exit()都没有清除文件的stream结构。那么exit()cleanup工作都干了什么? streams flush算不算?

 

(一)单进程standard i/o streamdescriptor测试例子:

#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 streamdescriptor测试例子:

我们只让子进程调用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 streamSTDOUT_FILENO都是可用的。

(三)使用vfork,多进程standard i/o streamdescriptor测试的例子

 我们让子进程显式调用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标准流,后childSTDOUT_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

3Exit()函数的cleanup操作并不清除stream结构,即不关闭stream

4.进城退出的时候,自己的打开的descriptor被关闭应该是肯定的。

5.关闭文件,要看关闭的是stream还是descriptor,是有分别的。

阅读(771) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~