写时复制(COW)技术理论

1、概述

写时复制(Copy-on-write,简称 COW)是一种资源“读写”优化策略,其核心思想是如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所访问的资源保持不变,这个过程对其他调用者都是透明的。此做法的主要优点是如果调用者没有修改该资源,就不会有副本被创建,因此多个调用者只读操作时可以实现资源共享。

2、用途

虚拟内存管理中的写时复制

一般把这种被共享访问的页面标记为只读,当一个 task 试图向内存中写入数据时,内存管理单元(MMU)抛出一个异常,内核处理该异常时为该 task 分配一份物理内存并复制数据到此内存,重新向 MMU 发出执行该 task 的写操作。

数据存储中的写时复制

Linux 等文件管理系统使用了写时复制策略;数据库服务器一般也采用写时复制策略,为用户提供一份快照。

软件应用中的写时复制

C++ 标准程序库中std::string类在 C++98/C++03 标准中允许写时复制策略,但在 C++11 之后为了提高并行性取消了这一策略;gcc 从版本 5 开始,std::string不再采用 COW 策略。

3、写时复制详解

COW 是存储系统中使用的基本更新策略之一(还有 就地更新 UIP,将目标块读入内存进行修改,然后在其原始位置写入磁盘覆盖旧数据),基本模式永远不会覆盖旧数据。使用 COW 策略更新数据块时,数据块被读入内存,进行修改,然后写入新位置,而旧数据则保持不变。由于 COW 永远不会覆盖旧数据,因此通常用于防止由于本地文件系统中的系统崩溃而导致数据丢失。但是 COW 引入了令人不愉快的递归更新过程,文件系统可以看作是由磁盘块组成的树,当使用 COW 策略修改叶子节点时,还需要修改其父节点以更新修改后的叶子节点的新位置,此更新过程将递归进行,直到到达根为止,根可以在磁盘上的固定位置进行更新。我们将这样的过程定义为递归更新。递归更新可能会导致存储系统出现多种副作用,例如 WRITE 放大,I / O模式变更和性能下降。

缺点

  1. 写 放大:递归更新可能会导致写放大,比如应用程序只需要修改一个叶子节点 F,但是递归更新导致总共修改了四个父级节点(F -> D -> A -> root)。实际刷新的数据高达 4 × 请求的数据。实际上,由于在这种情况下忽略了由块分配引起的递归更新,因此修改的块数量可能会更高。

  2. 性能下降: WRITE 放大会引入其他数据以进行写入,这最终可能会降低文件系统的性能。

优点

  1. 保护数据:本地文件系统.由于有备份机制,不会因为文件系统崩溃导致大量甚至全部数据丢失。

  2. 提高性能:日志结构的文件系统,例如 Sprite LFS,使用 COW 更新策略将访问模式从大量的小随机写入转换为单个大的顺序 WRITE,从而利用了磁盘顺序 I/O 带来的高性能。

  3. 在特殊介质上更新数据:一次写入多次读取的介质,例如光盘,使用 COW 实施随机 WRITE;闪存文件系统使用 COW 优化更新过程,这有助于提高 WRITE 性能并实现损耗均衡。

4、fork 和 COW

COW 是一种优化策略,fork 是 Linux 提供的创建新线程的方法,大多数的 fork 实现借用了 COW 策略来节省内存空间。父进程 fork 出子进程后,子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程所拥有的拷贝,父、子进程并不共享这些存储空间。如果正文段是只读的,则父、子进程共享正文段 , 现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在 fork 之后经常跟随着 exec。作为替代,使用了在写时复制技术这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中的“页”。

fork 细节: 一般来说,在 fork 之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。如果要求父、子进程之间相互同步,则要求某种形式的进程间通信。

总结: fork 在借用 COW 策略实现时,其实父子进程会共享数据段、代码段和堆,而栈是父子进程独有的。

5、vfork 和 fork

vfork 也用于创建一个新进程,而该新进程的目的是调用 exec 执行一个新程序。但 vfork 它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用 exec/exit,于是也就不会存访该地址空间。不过在子进程调用 exec/exit 之前,它在父进程的空间中运行。 这种工作方式在某些 Unix 的页式虚存实现中提高了效率(和 fork 类似,即在 fork 之后跟随 exec,并采用写时复制技术) 。

vfork 和 fork 之间的另一个区别是: vfork 保证子进程先运行,在它调用 exec/exit 之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。子进程在 exec 和 exit 之前其实运行在父进程的内存空间,所以子进程的数据操作其实是在修改父进程的对应数据,操作不当有可能导致进程崩溃。所以 vfork 之后建议立即执行 exec/exit。

posted @ 2022-08-22 14:57  HOracle  阅读(229)  评论(0编辑  收藏  举报