1.fork函数

#include <unistd.h>

pid_t fork(void);

返回值:父进程返回的是新建的子进程的进程ID,子进程返回的是0。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

当调用完fork函数后,子进程活的父进程的数据空间、堆和栈,但是这是子进程单独拥有的,并不和父进程共享,因此修改子进程的变量不会影响父进程的变量。父进程和子进程共享正文段。

由于在fork之后经常跟随者exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆得完全副本,作为代替,使用了写时复制(copy-on-write,COW)技术。

写时复制:写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下---例如,fork()后立即执行exec(),地址空间就无需被复制了。

不适用写时复制的缺点:传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因为它拷贝的数据或许可以共享。但是如果新进程立即执行了exec()函数,那么之前的拷贝就全都浪费了。

 

#include "apue.h"

int globvar = 6;
char buf[] = "a write to stdout \n";

int main(void)
{
  int var;
  pid_t pid;

  var = 88;
  if(write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) -1)
  {
     err_sys("write_error");
  }    
  printf("before fork\n");

  if((pid = fork()) < 0)
  {
     err_sys("fork error");
  }  
  else if(pid == 0)
  {
    globvar++;
    var++;
  }
  else
  {
    sleep(2);
  }

  printf("pid=%ld, glob=%d, var=%d\n",(long)getpid(), globvar, var);

 exit(0);
}

$ ./a.out

a write to stdout

before fork

pid=430,glob=7,var=69     //子进程的变量值改变了

pid=430,glob=6,var=68    //父进程的变量值没有改变

由此可以证明:父子进程不同享数据。

$ ./a.out > temp.out

$ cat temp.out

a write to stdout

before fork

pid=430,glob=7,var=69     //子进程的变量值改变了

before fork

pid=430,glob=6,var=68    //父进程的变量值没有改变

问题:这边为什么会输出两个before fork?

 因为将标准输出重定向到一个文件时,这个时候标准I/O就变成了一个全缓冲区(当缓冲区或者程序结束才会将缓冲区的数据输出),所以在第一次printf的时候,因为缓冲区没有满所以不会将数据打印出来,而是存在缓冲区中。fork函数将父进程复制完后,会将这个缓冲区也复制下来,所以在子程序的第二个printf中,会在已有的缓冲区数据后面再增加数据,最后在子程序的第二个printf中就会将两个数据一起输出出来。

2.vfork函数

vfork和fork的区别:

执行次序:vfork是先调用子进程,等子进程的exit或exec被调用后,再调用父进程。

        fork对父子进程的调度室由调度器决定的。

数据段的影响:fork采用的是写时复制技术。

       vfork的父子进程是共享数据的,所以在子程序中修改变量,父进程的变量也会被修改。(在fork中不会这样)

总结:vfork创建的子进程调用exec前,与父进程是共享一个地址空间的(根本不存在复制的这个步骤,因此直接执行exec效率方面比fork快)。

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄