写时复制

Posted by DeepBlue on 12-05,2020

写时复制技术

什么是写时复制技术

在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

什么是exec系统调用


The exec() family of functions replaces the current process image with a new process image.

exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。

我们都知道一个进程的主要组成包括以下几个部分:

  1. 程序(可执行的数据)
  2. 数据(变量,内存中的值,缓冲区的)
  3. PCB(进程控制块)

exec系统调用做的就是把进程进行替换,但是不会替换进程的PID等信息,会替换掉数据段堆栈段代码段等其他的信息,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。

exec是一个函数簇,由6个函数组成,分别是以excl和execv打头的。具体如下:

execl(const char* filepath,const char* arg1,char*arg2......)

execlp(const charfilename,const chararg1,const char*arg2..... )

execle(const charfilepath,const chararg1,const chararg2,.....,char cons envp[])

execv (const char* filepath,char* argv[])

execvp (const char* filename,char* argv[])

execve (const char* filepath,charargv[],char const envp[])

我们都知道system系统调用也有执行新程序的功能,那么exec系统调用和system系统调用有什么区别呢?

  1. exec是直接用新的进程去代替原来的程序运行,运行完毕之后不回到原先的程序中去
  2. system是调用shell执行你的命令,system=fork+exec+waitpid,执行完毕之后,回到原先的程序中去。继续执行下面的部分

通俗一点来时如果你用system系统调用的话,那么操作系统会给你创建一个进程,然后执行你要执行的代码,但是你用exec的话就要使用fork去新建一个新的进程,然后在新的进程里面调用exec去执行你的代码


理解了上面的exec系统调用的话我们就理解了上面说的写时复制(COW)是上面意思了。

我们可以联想一下,如果我通过fork新开了一个进程,按照我们传统的想法,当然是要把父进程的所有代码段,数据段堆栈段复制给子进程一份,但是很多情况下我们其实新开了一个线程都是大部分还是去做一些exec的系统调用,这时候就如果没有写时复制,就会出现性能问题,你刚开了一个新的进程,我辛辛苦苦的给你把数据,代码段给你搞好了,结果你上来第一句就是exec系统调用,把我辛辛苦苦搞好的代码段和数据段的信息给我作废,去重新加载新的代码段和堆栈段,这样效率肯定是不好的,也就是因为有这样的问题,我们就推出了写时复制的技术。

写时复制技术,它通过允许父进程和子进程最初共享相同的页面来工作。这些共享页面标记为写时复制,这意味着如果任何一个进程写入共享页面,那么就创建共享页面的副本。这样我们就不必给每个子进程都创建相同的内存区域,节省了内存资源和系统运行效率。

进程1修改页面C前后

假设子进程试图修改包含部分堆栈的页面,并且设置为写时复制。操作系统会创建这个页面的副本,将其映射到子进程的地址空间。然后,子进程会修改复制的页面,而不是属于父进程的页面。显然,当使用写时复制技术时,仅复制任何一进程修改的页面,所有未修改的页面可以由父进程和子进程共享。