前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >进程的创建fork vs vfork

进程的创建fork vs vfork

作者头像
DragonKingZhu
发布2020-03-24 16:23:13
1K0
发布2020-03-24 16:23:13
举报

上一篇文章学习了进程的基本概念,以及进程的状态,最后学习了Linux中是如何描述一个进程的。本节来学习Linux中进程是如何创建的,以及fork和vfork的区别。

在大学的时候操作系统课程中我们都学过如何去创建一个进程,是通过fork系统调用命令来创建的。

使用fork创建进程

如下是一个简单的通过fork系统调用来创建子进程的例子

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int main()
{
   pid_t pid = fork();
 
   if(pid == -1){
       printf("create child process failed!\n");
       return -1;
   }else if(pid == 0){
       printf("This is child process!\n");
   }else{
       printf("This is parent process!\n");
       printf("parent process pid = %d\n",getpid());
       printf("child process pid = %d\n",pid);
   }
 
   getchar();
 
   return 0;
}

运行结果:

root@ubuntu:zhuxl$ ./a.out
This is parent process!
parent process pid = 91985
child process pid = 91986
This is child process!

先说几个关于fork的知识点:

  • fork返回值为-1, 代表创建子进程失败
  • fork返回值为0,代表子进程创建成功。返回值为0,这个分支就是子进程运行的逻辑
  • fork返回值大于0,这个分支是父进程的运行逻辑。并且返回值等于子进程的pid
  • 简单来说就是fork创建子进程成功后,父进程返回子进程的pid,子进程返回0.

从上面的运行结果来看,子进程的pid=91986, 父进程的pid=91985

ps -ef 命令可以看进程的
UID         PID   PPID  C STIME TTY          TIME CMD
root       91985  90968  0 00:02 pts/0    00:00:00 ./a.out
root       91986  91985  0 00:02 pts/0    00:00:00 ./a.out

Copy-on-Write(写时复制)

在linux早期设计中,当调用fork命令来创建子进程时,子进程会将父进程的所有资源做一次全部拷贝复制工作。这个拷贝复制的工作是相当耗时的,影响系统的整体性能。

当引入了COW机制之后,当fork用来创建子进程时,子进程只拷贝页表的内存,不再拷贝物理地址对应的真正数据了。

比如对应mm资源,使用fork创建子进程后,父子进程会指向同一片物理内存,当父子进程中随便一个去写这块内存时,就会发生分裂(fork),然后谁先写给谁分配一块新的物理页面。这就是COW的原理。我们用一个简单里的例子描述下:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int data=10;
 
int main()
{
  pid_t pid = fork();
 
  if(pid == -1){
      printf("create child process failed!\n");
      return -1;
  }else if(pid == 0){
      printf("This is child process, data=%d!\n",data);
      data =90;
      printf("After child process modify data =%d\n",data);
  }else{
      printf("This is parent process=%d!\n",data);
  }  
 
  getchar();
  return 0;
}

执行结果:

root@ubuntu:zhuxl$ ./a.out
This is parent process=10!
This is child process, data=10!
After child process modify data =90

从运行结果可以看到,不论子进程如何去修改data的值,父进程永远看到的是自己的那一份。有人就可能会说上面的代码是父进程先运行的,父进程运行的时候子进程还没修改data=90。有兴趣的小伙伴可以自动动手修改代码尝试下。

当然了从man fork中也可以找到答案:

子进程虽然是和父进程做资源的拷贝,但是也有一些不同之处,这些在man fork中都有详解。

就比如pid,子进程和父进程都有一个unique的ID。相当于您和您的父亲,出生时是做了一次拷贝比如DNA,但是不一样的也是有的,比如面貌,身材。这都是fork之后的结果

The child process is an exact duplicate of the parent process except for the following points:
 
       *  The child has its own unique process ID, and this PID does not match the ID of any existing process group (setpgid(2)) or session.
 
       *  The child's parent process ID is the same as the parent's process ID.
 
       *  The child does not inherit its parent's memory locks (mlock(2), mlockall(2)).
 
       *  Process resource utilizations (getrusage(2)) and CPU time counters (times(2)) are reset to zero in the child.
 
       *  The child's set of pending signals is initially empty (sigpending(2)).
 
      等等

man fork中也提到了linux中fork是通过cow实现的,是通过复制父进程的page table了实现的。而且我们在现在调用fork命令是通过glibc封装的,其实真正的是调用clone的系统调用命令

Under Linux, fork() is implemented using copy-on-write pages, so the only penalty that it incurs is the time  and  memory  required  to
      duplicate the parent's page tables, and to create a unique task structure for the child.
 
  C library/kernel differences
      Since  version  2.3.3,  rather  than invoking the kernel's fork() system call, the glibc fork() wrapper that is provided as part of the
      NPTL threading implementation invokes clone(2) with flags that provide the same effect as the traditional  system  call.   (A  call  to
      fork()  is  equivalent  to a call to clone(2) specifying flags as just SIGCHLD.)  The glibc wrapper invokes any fork handlers that have
      been established using pthread_atfork(3).

通过strace来看下fork最终调用的是clone系统调用

可以看到最终是通过clone系统调用,来实现fork子进程的。

通过vfork来创建子进程

上面我们学习了使用fork来创建子进程,接下来看下使用vfork来创建子进程,以及两者的区别。

还是通过例子来学习vfork

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int data=10;
 
int main()
{
    pid_t pid = vfork();
 
    if(pid == -1){
        printf("create child process failed!\n");
        return -1;
    }else if(pid == 0){
        printf("This is child process, data=%d!\n",data);
        data =90;
        printf("After child process modify data =%d\n",data);
        exit(0);
    }else{
        printf("This is parent process=%d!\n",data);
    }
 
    getchar();
 
    return 0;
}

运行结果:

root@ubuntu:zhuxl$ ./a.out
This is child process, data=10!
After child process modify data =90
This is parent process=90!

从运行结果中可以看出,当子进程修改了data=90之后,父进程中打印data的值也是90。而且细心的朋友可以看出在相比fork的代码,在子进程中调用了exit函数、

总结下vfork的几个特点:

  • vfork父子进程的mm资源是共享的,当父子进程任意一个进行修改,另外一个进程都会看到修改后的值
  • vfork中子进行是永远先执行的,等待子进程退出父进程才可以执行
  • 以上就是fork和vfork的区别

再通过man vfork来详细看下vfork

Historic description
    Under Linux, fork(2) is implemented using copy-on-write pages, so the only penalty incurred by fork(2) is the time and memory  required
    to  duplicate  the  parent's  page tables, and to create a unique task structure for the child.  However, in the bad old days a fork(2)
    would require making a complete copy of the caller's data space, often needlessly, since usually immediately afterward  an  exec(3)  is
    done.   Thus,  for greater efficiency, BSD introduced the vfork() system call, which did not fully copy the address space of the parent
    process, but borrowed the parent's memory and thread of control until a call to execve(2) or an exit occurred.  The parent process  was
    suspended  while  the  child  was  using  its  resources.  The use of vfork() was tricky: for example, not modifying data in the parent
    process depended on knowing which variables were held in a register.

上面一大段介绍了vfork出现的背景。in the bad old days 就指的是fork子进程的时候需要全部拷贝父进程的地址空间数据,而且是没必要的,因为拷贝完毕后会立刻执行exec会去重新装载子进行的数据,导致前面的拷贝浪费了。

而vfork的出现使得子进程和父进程共享内存资源,但必须是子进程先运行父进程挂起等待子进程调用exit退出时,父进程才可以真正的运行。

Linux description
    vfork(), just like fork(2), creates a child process of the calling process.  For details and return value and errors, see fork(2).
 
    vfork() is a special case of clone(2).  It is used to create new processes without copying the page tables of the parent  process.   It
    may be useful in performance-sensitive applications where a child is created which then immediately issues an execve(2).
 
    vfork()  differs from fork(2) in that the calling thread is suspended until the child terminates (either normally, by calling _exit(2),
    or abnormally, after delivery of a fatal signal), or it makes a call to execve(2).  Until that point, the child shares all memory  with
    its  parent,  including the stack.  The child must not return from the current function or call exit(3) (which would have the effect of
    calling exit handlers established by the parent process and flushing the parent's stdio(3) buffers), but may call _exit(2).
 
    As with fork(2), the child process created by vfork() inherits copies of  various  of  the  caller's  process  attributes  (e.g.,  file
    descriptors, signal dispositions, and current working directory); the vfork() call differs only in the treatment of the virtual address
    space, as described above.
 
    Signals sent to the parent arrive after the child releases the parent's memory (i.e., after the child terminates or calls execve(2)).

上面说了几点:

  • fork和vfork都是用来创建子进程的。
  • vfork和fork相比是不需要做page table拷贝的,也就是父子进程共享地址空间数据
  • vfork创建的子进程是必须先运行的
Linux notes
    Fork  handlers  established  using  pthread_atfork(3)  are not called when a multithreaded program employing the NPTL threading library
    calls vfork().  Fork handlers are called in this case in a program using the LinuxThreads threading library.  (See  pthreads(7)  for  a
    description of Linux threading libraries.)
 
    A call to vfork() is equivalent to calling clone(2) with flags specified as:
 
         CLONE_VM | CLONE_VFORK | SIGCHLD

Vfork相当于调用了clone系统调用,而且参数传递的是CLONE_VM | CIONE_VFORK ,这两个表示CLONE_VM意思是共享mm资源,CLONE_VFORK代表的意思是使用vfork来创建子进程。这两个标识在分析fork的内核源码的时候会用的上。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用fork创建进程
  • Copy-on-Write(写时复制)
    • 当然了从man fork中也可以找到答案:
    • 通过vfork来创建子进程
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档