fork 与vfork

来源:互联网 发布:ue json格式化 编辑:程序博客网 时间:2024/05/29 06:55

-----------------

缓冲区被复制引发的问题

其中的示例代码我跑了一下,没有得到文中的结果。

分析:printf("before fork!!\n");应该为printf("before fork!!");

           遇到\n就会马上输出了。  

  

 

-------------------------

一、fork系统调用

1、函数的声明:

[cpp]
  1.  #include <unistd.h> 
  2. pid_t fork(void);  

2、返回值:

    fork函数调用一次,将会返回两次(返回给主进程为新创建的子进程的进程ID,返回给子进程的是0)。当进程创建失败时候,fork返回值为-1。

  • 因为父进程种可能有多个子进程,但没有一个函数可以获得所有子进程的进程ID,所以我们通过fork调用时候将新创建的进程的进程ID返回给主进程。
  • 由于应用程序中创建的进程的进程ID不可能为0,且我们知道进程ID是一个非负值,所以为了在后续代码中区分当前进程是主进程还是新创建的子进程,我们需要通过返回值来区分,因此fork将在子进程中返回0;

3、函数详细说明

1、子进程是父进程的副本   

子进程通过fork创建成功以后。子进程和父进程都将继续执行fork调用之后l的指令,子进程是父进程的副本(子进程获得父进程的数据空间:堆、栈副本)。这里我们特别要注意的是,子进程获得的是父进程的副本,而不是父子进程共享数据空间。但父子进程将会共享正文段。如下程序所示:

[cpp]
 
  1. #include <unistd.h>  
  2. #include <stdio.h>   
  3. int main()  
  4. {  
  5.     int a = 9;  
  6.         pid_t pid = -1;  
  7.   
  8.     if((pid = fork()) < 0)  
  9.     {  
  10.         //如果进程ID小于零则报错,退出  
  11.         printf("fork error!!\n");  
  12.         return -1;  
  13.     }  
  14.     else if(pid == 0)  
  15.     {  
  16.          //我们在子进程中将会获得主进程中自动变量a的一个副本,我们在子进程中a++操作将操作的是子进程中的副本而不是父进程中的自动变量a的值。 因此父进程中的变量a的值将不会改变。  
  17.         a++;  
  18.     }  
  19.     return 0;  
  20. }  
2、缓冲区被复制引发的问题
    当我们在调用fork之前如果使用了标准I/O(带缓冲的I/O),则在使用fork之前最好刷新(或冲洗)标准输出流。因为在fork创建子进程后,系统会为子进程复制父进程数据空间以及标准输出缓冲区。而如果不刷新标准输出缓冲区,则调用fork时候,之前通过标准输出流输出的数据还在缓冲区中,则子进程也就拥有了一份缓冲区的副本。如下程序所示:
[cpp] view plaincopyprint?
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3.   
  4. int main()  
  5. {  
  6.     pid_t pid = -1;  
  7.   
  8.     //printf 带缓冲的标准输出,当输出以后,数据将仍就存留在缓冲区中。随后,子进程会复制缓冲区中的数据。所以在子进程中将会再次输出: before fork  
  9.     printf("before fork!!\n");  
  10.   
  11.     if((pid = fork()) < 0)  
  12.     {  
  13.         //如果进程ID小于零则报错,退出  
  14.         printf("fork error!!\n");  
  15.         return -1;  
  16.     }  
  17.     else if(pid == 0)  
  18.     {  
  19.        //子进程中我们什么都没有做  
  20.     }  
  21.   
  22.     return 0;  
  23. }  
 如果没有改变标准输出(默认为终端)运行该程序,输出将为(该输出为调用fork之前,主进程中的printf输出):

[html] view plaincopyprint?
  1. before fork!!  
而如果我们将该程序的标准输出重定向到文件中,我们将得到以下结果(第一个为调用fork之前,主进程中的printf输出,第二个为缓冲区复制后在子进程中的输出):

[html] view plaincopyprint?
  1. before fork!!  
  2. before fork!!  
3、标准输出重定向:

如果我们重定向了父进程的标准输出,那么子进程的标准输出也会被重定向。即:fork会将父进程中所有打开的文件描述符都复制到子进程中。

4、文件共享

从上面所述,我们已经了解到调用fork后,系统会将父进程中所有打开的文件描述符复制到子进程中。父进程与子进程将会共享所打开的文件表项,则父进程与子进程共享同一个文件偏移量。如果我们要满足子进程与父进程之间文件访问的同步,则在fork调用之后处理文件描述符有两种常见的情况:

    • 父进程等待子进程完成。
    • 父子进程各自执行不同的程序段。即,虽然子进程会复制父进程的文件描述符,但可以在子进程程序段开始关闭这些打开但不再需要的文件描述符,则可以避免父子进程之间的互相影响。

5、fork的两种用法:

    • 一个父进程希望自己被复制,使得父子进程同时执行不同的程序段,这在网络程序设计中是常见的。例如:父进程一直等待客户端的连接请求。当请求到达时候,父进程调用fork,使子进程处理刚到达的请求,而父进程继续等待下一个请求的到达。
    • 一个进程要执行一个不同的程序,这对shell是常见的。子进程从fork返回后立即调用exec。

6、另外的一点话:

某些操作系统讲fork与exec合并在一起,并称其为spawn。UNIX将其分开,可以使在fork和 exec之间做一些其他的必要的操作。如,重定向标准输出、设置用户ID等。另外有些时候,在我们调用fork之后没有必要执行exec。所以,Unix系统中并没有将fork与exec合并(UNIX在高级实现时候,选项组中确实包括了spawn接口,但是该接口并不打算代替fork和exec)。

二、vfork函数

1、函数声明:

[cpp] view plaincopyprint?
  1. #include <sys/types.h>  
  2. #include <unistd.h>  
  3.   
  4. pid_t vfork(void);  

2、返回值:

vfork函数的返回值与fork函数返回值相同,参见fork函数返回值部分的说明。

3、函数详细说明:

1、创建进程的目的

vfork函数用于创建一个新进程,新进程的目的是执行(exec)一个新程序。

2、与fork函数一样都创建一个子进程。但它并不将父进程的地址空间完全复制到子进程中。因为子进程会立即调用exec函数,于是也就没有必要保存父进程的地址空间了。

3、vfork函数创建的子进程中,在调用exec之前,子进程是在父进程空间中执行的。因此,子进程会与父进程共享堆、栈,子进程中改变父进程中的变量会引起父进程中变量值的修改,如下:

[cpp] view plaincopyprint?
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3.   
  4. int main()  
  5. {  
  6.     int a = 9;  
  7.     pid_t pid = -1;  
  8.   
  9.     if((pid = vfork()) < 0)  
  10.     {  
  11.         //如果进程ID小于零则报错,退出  
  12.         printf("vfork error!!\n");  
  13.         return -1;  
  14.     }  
  15.     else if(pid == 0)  
  16.     {  
  17.         //我们在子进程中修改a的值,会改变父进程中自动变量a的值  
  18.         a++;  
  19.         printf("child process: a = %d  ppid = %d\n",a,getppid());  
  20.         _exit(0);  
  21.     }  
  22.     else  
  23.     {  
  24.         //我们在父进程中打印变量a的值。  
  25.         printf("parent process: a = %d  child_pid = %d\n",a,pid);  
  26.     }  
  27.   
  28.     return 0;  
  29.   
  30. }  
输出结果如下:
[html] view plaincopyprint?
  1. child process: a = 10  ppid = 2893  
  2. parent process: a = 10  child_pid = 2894  
从上面例子我们可以看出父进程中自动变量a的值被改变了。

4、vfork与fork之间的另外一个区别:

前面我们已经提到vfork与fork之间的一个区别(是否会复制父进程的地址空间)。vfork与fork之间的另外一个区别是,vfork会保证子进程首先运行,在调用exec或者exit之后,父进程才可能被调度运行。如果在这些函数(exec、exit)之前有依赖于父进程的进一步动作,则会导致死锁

5、子进程中exit的使用

原创粉丝点击