COW奶牛!Copy On Write机制了解一下

前言

只有光头才能变强html

在读《Redis设计与实现》关于哈希表扩容的时候,发现这么一段话:java

执行BGSAVE命令或者BGREWRITEAOF命令的过程当中,Redis须要建立当前服务器进程的子进程,而大多数操做系统都采用写时复制(copy-on-write)来优化子进程的使用效率,因此在子进程存在期间,服务器会提升负载因子的阈值,从而避免在子进程存在期间进行哈希表扩展操做,避免没必要要的内存写入操做,最大限度地节约内存。linux

触及到知识的盲区了,因而就去搜了一下copy-on-write写时复制这个技术到底是怎么样的。发现涉及的东西蛮多的,也挺难读懂的。因而就写下这篇笔记来记录一下我学习copy-on-write的过程。git

本文力求简单讲清copy-on-write这个知识点,但愿你们看完能有所收获。程序员

1、Linux下的copy-on-write

在说明Linux下的copy-on-write机制前,咱们首先要知道两个函数:fork()exec()。须要注意的是exec()并非一个特定的函数, 它是一组函数的统称, 它包括了execl()execlp()execv()execle()execve()execvp()github

1.1简单来用用fork

首先咱们来看一下fork()函数是什么鬼:服务器

fork is an operation whereby a process creates a copy of itself.函数

fork是类Unix操做系统上建立进程的主要方法。fork用于建立子进程(等同于当前进程的副本)。性能

  • 新的进程要经过老的进程复制自身获得,这就是fork!

若是接触过Linux,咱们会知道Linux下init进程是全部进程的爹(至关于Java中的Object对象)学习

  • Linux的进程都经过init进程或init的子进程fork(vfork)出来的。

下面以例子说明一下fork吧:

#include <unistd.h>  
#include <stdio.h>  
 
int main ()    {   
    pid_t fpid; //fpid表示fork函数返回的值  
    int count=0;
	
	// 调用fork,建立出子进程  
    fpid=fork();

	// 因此下面的代码有两个进程执行!
    if (fpid < 0)   
        printf("建立进程失败!/n");   
    else if (fpid == 0) {  
        printf("我是子进程,由父进程fork出来/n");   
        count++;  
    }  
    else {  
        printf("我是父进程/n");   
        count++;  
    }  
    printf("统计结果是: %d/n",count);  
    return 0;  
}  
复制代码

获得的结果输出为:

我是子进程,由父进程fork出来

统计结果是: 1

我是父进程

统计结果是: 1

复制代码

解释一下:

  • fork做为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。(若是小于0,则说明建立子进程失败)。
  • 再次说明:当前进程调用fork(),会建立一个跟当前进程彻底相同的子进程(除了pid),因此子进程一样是会执行fork()以后的代码。

因此说:

  • 父进程在执行if代码块的时候,fpid变量的值是子进程的pid
  • 子进程在执行if代码块的时候,fpid变量的值是0

1.2再来看看exec()函数

从上面咱们已经知道了fork会建立一个子进程。子进程的是父进程的副本

exec函数的做用就是:装载一个新的程序(可执行映像)覆盖当前进程内存空间中的映像,从而执行不一样的任务

  • exec系列函数在执行时会直接替换掉当前进程的地址空间

我去画张图来理解一下:

exec函数的做用

参考资料:

1.3回头来看Linux下的COW是怎么一回事

fork()会产生一个和父进程彻底相同的子进程(除了pid)

若是按传统的作法,会直接将父进程的数据拷贝到子进程中,拷贝完以后,父进程和子进程之间的数据段和堆栈是相互独立的

父进程的数据拷贝到子进程中

可是,以咱们的使用经验来讲:每每子进程都会执行exec()来作本身想要实现的功能。

  • 因此,若是按照上面的作法的话,建立子进程时复制过去的数据是没用的(由于子进程执行exec(),原有的数据会被清空)

既然不少时候复制给子进程的数据是无效的,因而就有了Copy On Write这项技术了,原理也很简单:

  • fork建立出的子进程,与父进程共享内存空间。也就是说,若是子进程不对内存空间进行写入操做的话,内存空间中的数据并不会复制给子进程,这样建立子进程的速度就很快了!(不用复制,直接引用父进程的物理空间)。
  • 而且若是在fork函数返回以后,子进程第一时间exec一个新的可执行映像,那么也不会浪费时间和内存空间了。

另外的表达方式:

在fork以后exec以前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,二者的虚拟空间不一样,但其对应的物理空间是同一个

当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间

若是不是由于exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此二者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(二者的代码彻底相同)。

而若是是由于exec,因为二者执行的代码不一样,子进程的代码段也会分配单独的物理空间。

Copy On Write技术实现原理:

fork()以后,kernel把父进程中全部的内存页的权限都设为read-only,而后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,因而触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,因而父子进程各自持有独立的一份。

Copy On Write技术好处是什么?

  • COW技术可减小分配和复制大量资源时带来的瞬间延时
  • COW技术可减小没必要要的资源分配。好比fork进程时,并非全部的页面都须要复制,父进程的代码段和只读数据段都不被容许修改,因此无需复制

Copy On Write技术缺点是什么?

  • 若是在fork()以后,父子进程都还须要继续进行写操做,那么会产生大量的分页错误(页异常中断page-fault),这样就得不偿失。

几句话总结Linux的Copy On Write技术:

  • fork出的子进程共享父进程的物理空间,当父子进程有内存写入操做时,read-only内存页发生中断,将触发的异常的内存页复制一份(其他的页仍是共享父进程的)。
  • fork出的子进程功能实现和父进程是同样的。若是有须要,咱们会用exec()把当前进程映像替换成新的进程文件,完成本身想要实现的功能。

参考资料:

2、解释一下Redis的COW

基于上面的基础,咱们应该已经了解COW这么一项技术了。

下面我来讲一下我对《Redis设计与实现》那段话的理解:

  • Redis在持久化时,若是是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中
  • 整体来看,Redis仍是读操做比较多。若是子进程存在期间,发生了大量的写操做,那可能就会出现不少的分页错误(页异常中断page-fault),这样就得耗费很多性能在复制上。
  • 而在rehash阶段上,写操做是没法避免的。因此Redis在fork出子进程以后,将负载因子阈值提升,尽可能减小写操做,避免没必要要的内存写入操做,最大限度地节约内存。

参考资料:

3、文件系统的COW

下面来看看文件系统中的COW是啥意思:

Copy-on-write在对数据进行修改的时候,不会直接在原来的数据位置上进行操做,而是从新找个位置修改,这样的好处是一旦系统忽然断电,重启以后不须要作Fsck。好处就是能保证数据的完整性,掉电的话容易恢复

  • 好比说:要修改数据块A的内容,先把A读出来,写到B块里面去。若是这时候断电了,原来A的内容还在!

参考资料:

最后

最后咱们再来看一下写时复制的思想(摘录自维基百科):

写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,若是有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其余调用者所见到的最初的资源仍然保持不变。这过程对其余的调用者都是透明的(transparently)。此做法主要的优势是若是调用者没有修改该资源,就不会有副本(private copy)被创建,所以多个调用者只是读取操做时能够共享同一份资源。

至少从本文咱们能够总结出:

  • Linux经过Copy On Write技术极大地减小了Fork的开销
  • 文件系统经过Copy On Write技术必定程度上保证数据的完整性

其实在Java里边,也有Copy On Write技术。

Java中的COW

这部分留到下一篇来讲,敬请期待~

若是你们有更好的理解方式或者文章有错误的地方还请你们不吝在评论区留言,你们互相学习交流~~~

参考资料:

一个坚持原创的Java技术公众号:Java3y,欢迎你们关注

3y全部的原创文章:

相关文章
相关标签/搜索