Copy-On-Write COW機制

Copy-On-Write COW機制

轉自:https://zhuanlan.zhihu.com/p/48147304

作者:Java3y

前言

只有光頭才能變強

在讀《Redis設計與實現》關于哈希表擴容的時候,發現這么一段話:

執行BGSAVE命令或者BGREWRITEAOF命令的過程中,Redis需要創建當前服務器進程的子進程,而大多數操作系統都采用寫時復制(copy-on-write)來優化子進程的使用效率,所以在子進程存在期間,服務器會提高負載因子的閾值,從而避免在子進程存在期間進行哈希表擴展操作,避免不必要的內存寫入操作,最大限度地節約內存。

觸及到知識的盲區了,于是就去搜了一下copy-on-write寫時復制這個技術究竟是怎么樣的。發現涉及的東西蠻多的,也挺難讀懂的。于是就寫下這篇筆記來記錄一下我學習copy-on-write的過程。

本文力求簡單講清copy-on-write這個知識點,希望大家看完能有所收獲。

關于fork(狀態機的復制)和exec(狀態機的重置)的理解可參考:12 [虛擬化] 進程抽象;fork,execve,exit

一、Linux下的copy-on-write

在說明Linux下的copy-on-write機制前,我們首先要知道兩個函數:fork()exec()。需要注意的是exec()并不是一個特定的函數, 它是一組函數的統稱, 它包括了execl()execlp()execv()execle()execve()execvp()

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系列函數在執行時會直接替換掉當前進程的地址空間

我去畫張圖來理解一下:

在這里插入圖片描述

參考資料:

  • 程序員必備知識——fork和exec函數詳解https://blog.csdn.net/bad_good_man/article/details/49364947
  • linux中fork()函數詳解(原創!!實例講解):https://blog.csdn.net/jason314/article/details/5640969
  • linux c語言 fork() 和 exec 函數的簡介和用法:https://blog.csdn.net/nvd11/article/details/8856278
  • Linux下Fork與Exec使用:https://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.html
  • Linux 系統調用 —— fork()內核源碼剖析:https://blog.csdn.net/chen892704067/article/details/76596225

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()把當前進程映像替換成新的進程文件,完成自己想要實現的功能。

參考資料:

  • Linux進程基礎:http://www.cnblogs.com/vamei/archive/2012/09/20/2694466.html
  • Linux寫時拷貝技術(copy-on-write)http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html
  • 當你在 Linux 上啟動一個進程時會發生什么?https://zhuanlan.zhihu.com/p/33159508
  • Linux fork()所謂的寫時復制(COW)到最后還是要先復制再寫嗎?https://www.zhihu.com/question/265400460
  • 寫時拷貝(copy-on-write) COW技術https://blog.csdn.net/u012333003/article/details/25117457
  • Copy-On-Write 寫時復制原理https://blog.csdn.net/ppppppppp2009/article/details/22750939

二、文件系統的COW

下面來看看文件系統中的COW是啥意思:

Copy-on-write在對數據進行修改的時候,不會直接在原來的數據位置上進行操作,而是重新找個位置修改,這樣的好處是一旦系統突然斷電,重啟之后不需要做Fsck。好處就是能保證數據的完整性,掉電的話容易恢復

  • 比如說:要修改數據塊A的內容,先把A讀出來,寫到B塊里面去。如果這時候斷電了,原來A的內容還在!

參考資料:

  • 文件系統中的 copy-on-write 模式有什么具體的好處?https://www.zhihu.com/question/19782224/answers/created
  • 新一代 Linux 文件系統 btrfs 簡介:https://www.ibm.com/developerworks/cn/linux/l-cn-btrfs/

三、解釋一下Redis的COW

基于上面的基礎,我們應該已經了解COW這么一項技術了。

下面我來說一下我對《Redis設計與實現》那段話的理解:

  • Redis在持久化時,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis會fork出一個子進程來讀取數據,從而寫到磁盤中
  • 總體來看,Redis還是讀操作比較多。如果子進程存在期間,發生了大量的寫操作,那可能就會出現很多的分頁錯誤(頁異常中斷page-fault),這樣就得耗費不少性能在復制上。
  • 而在rehash階段上,寫操作是無法避免的。所以Redis在fork出子進程之后,將負載因子閾值提高,盡量減少寫操作,避免不必要的內存寫入操作,最大限度地節約內存。

參考資料:

  • fork()后copy on write的一些特性:https://zhoujianshi.github.io/articles/2017/fork()%E5%90%8Ecopy%20on%20write%E7%9A%84%E4%B8%80%E4%BA%9B%E7%89%B9%E6%80%A7/index.html
  • 寫時復制:[https://miao1007.github.io/gitbook/java/juc/cow/](

最后

最后我們再來看一下寫時復制的思想(摘錄自維基百科):

寫入時復制(英語:Copy-on-write,簡稱COW)是一種計算機程序設計領域的優化策略。其核心思想是,如果有多個調用者(callers)同時請求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統才會真正復制一份專用副本(private copy)給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程對其他的調用者都是透明的(transparently)。此作法主要的優點是如果調用者沒有修改該資源,就不會有副本(private copy)被建立,因此多個調用者只是讀取操作時可以共享同一份資源。

至少從本文我們可以總結出:

  • Linux通過Copy On Write技術極大地減少了Fork的開銷
  • 文件系統通過Copy On Write技術一定程度上保證數據的完整性

參考資料:

  • 寫時復制,寫時拷貝,寫時分裂,Copy on write:https://my.oschina.net/dubenju/blog/815836
  • 不會產奶的COW(Copy-On-Write)https://www.jianshu.com/p/b2fb2ee5e

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532685.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532685.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532685.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

實驗報告:抽象數據類型的表現和實現

實驗報告&#xff1a;抽象數據類型的表現和實現 實驗內容 基本要求&#xff1a; 設計實現抽象數據類型“三元組”&#xff0c;要求動態分配內存。每個三元組由任意三個實數的序列構成&#xff0c;基本操作包括&#xff1a;創建一個三元組&#xff0c;取三元組的任意一個分量&…

關于x86、x86_64/x64、amd64和arm64/aarch64

關于x86、x86_64/x64、amd64和arm64/aarch64 轉自&#xff1a;https://www.jianshu.com/p/2753c45af9bf 為什么叫x86和x86_64和AMD64? 為什么大家叫x86為32位系統&#xff1f; 為什么軟件版本會注明 for amd64版本&#xff0c;不是intel64呢&#xff1f; x86是指intel的開…

實驗報告: 線性表的基本操作及應用

實驗報告&#xff1a; 線性表的基本操作及應用 實驗內容 基本要求&#xff1a; &#xff08;1&#xff09;實現單鏈表的創建&#xff1b;&#xff08;2&#xff09;實現單鏈表的插入&#xff1b;&#xff08;3&#xff09;實現單鏈表的刪除 &#xff08;4&#xff09;實現單鏈…

TVM:源碼編譯安裝

TVM&#xff1a;Linux源碼編譯安裝 筆者環境&#xff1a; OS&#xff1a;Ubuntu 18.04 CMake&#xff1a;3.10.2 gcc&#xff1a;7.5.0 cuda&#xff1a;11.1 編譯安裝過程總覽 本文將簡介 tvm 的編譯安裝過程&#xff0c;包含兩個步驟&#xff1a; 通過C代碼構建共享庫設置相…

第2章線性表的基本使用及其cpp示例(第二章匯總,線性表都在這里)

2.1線性表的定義和特點 【類型定義&#xff1a; *是n個元素的有限序列 *除了第一個元素沒有直接前驅和最后一個沒有直接后驅之外&#xff0c;其余的每個元素只有一個直接前驅和直接后驅&#xff1b; &#xff08;a1,a2…an&#xff09; 【特征&#xff1a; *有窮性&#xff1…

TVM:通過Python接口(AutoTVM)來編譯和優化模型

TVM&#xff1a;通過Python接口&#xff08;AutoTVM&#xff09;來編譯和優化模型 上次我們已經介紹了如何從源碼編譯安裝 tvm&#xff0c;本文我們將介紹在本機中使用 tvm Python 接口來編譯優化模型的一個demo。 TVM 是一個深度學習編譯器框架&#xff0c;有許多不同的模塊…

TVM:在樹莓派上部署預訓練的模型

TVM&#xff1a;在樹莓派上部署預訓練的模型 之前我們已經介紹如何通過Python接口&#xff08;AutoTVM&#xff09;來編譯和優化模型。本文將介紹如何在遠程&#xff08;如本例中的樹莓派&#xff09;上部署預訓練的模型。 在設備上構建 TVM Runtime 首先我們需要再遠程設備…

2.2線性表的順序表

2.2.1線性表的順序表示和實現------順序映像 【順序存儲】在【查找時】的時間復雜度為【O(1)】&#xff0c;因為它的地址是連續的&#xff0c;只要知道首元素的地址&#xff0c;根據下標可以很快找到指定位置的元素 【插入和刪除】操作由于可能要在插入前或刪除后對元素進行移…

TVM:交叉編譯和RPC

TVM&#xff1a;交叉編譯和RPC 之前我們介紹了 TVM 的安裝、本機demo和樹莓派遠程demo。本文將介紹了在 TVM 中使用 RPC 進行交叉編譯和遠程設備執行。 通過交叉編譯和 RPC&#xff0c;我們可以在本地機器上編譯程序&#xff0c;然后在遠程設備上運行它。 當遠程設備資源有限…

2.3單鏈表的基本使用及其cpp示例

2.3線性表的鏈式表現與實現 2.3.1.1單鏈表 【特點&#xff1a; *用一組任意的存儲單元存儲線性表的數據元素 *利用指針實現用不同相鄰的存儲單元存放邏輯上相鄰的元素 *每個元素ai&#xff0c;除存儲本身信息外&#xff0c;還存儲其直接后繼的元素&#xff08;后一個元素的地址…

TVM:簡介

TVM&#xff1a;簡介概述 Apache TVM 是一個用于 CPU、GPU 和機器學習加速器的開源機器學習編譯器框架。它旨在使機器學習工程師能夠在任何硬件后端上高效地優化和運行計算。本教程的目的是通過定義和演示關鍵概念&#xff0c;引導您了解 TVM 的所有主要功能。新用戶應該能夠從…

2.3.3單鏈表的雙向鏈表

2.3.3雙向鏈表 插入、刪除 指在前驅和后驅方向都能游歷&#xff08;遍歷&#xff09;的線性鏈表 雙向鏈表的每個結點有兩個指針域 【結構】&#xff1a;prior data next 雙鏈表通常采用帶頭結點的循環鏈表形式 可理解為首位相接的數據“圈”&#xff0c;每個結點都可以向前…

nvidia-smi 命令詳解

nvidia-smi 命令詳解 簡介 nvidia-smi - NVIDIA System Management Interface program nvidia smi&#xff08;也稱為NVSMI&#xff09;為來自 Fermi 和更高體系結構系列的 nvidia Tesla、Quadro、GRID 和 GeForce 設備提供監控和管理功能。GeForce Titan系列設備支持大多數…

2.4一元多項式的表示及相加,含cpp算法

2.4一元多項式的表示及相加 n階多項式的表示&#xff1a; n階多項式有n1項 指數按升冪排序 【 優點&#xff1a; 多項式的項數可以動態增長&#xff0c;不存在存儲溢出的問題插入&#xff0c;刪除方便&#xff0c;不移動元素 【表示&#xff1a; 有兩個數據域&#xff0c;一…

TVM:使用Tensor Expression (TE)來處理算子

TVM&#xff1a;使用Tensor Expression (TE)來處理算子 在本教程中&#xff0c;我們將聚焦于在 TVM 中使用張量表達式&#xff08;TE&#xff09;來定義張量計算和實現循環優化。TE用純函數語言描述張量計算&#xff08;即每個表達式都沒有副作用&#xff09;。當在 TVM 的整體…

4-數據結構-串的學習

4.1串類型的定義 1.串&#xff1a;&#xff08;或字符串&#xff09; 串是由多個字符組成的有限序列&#xff0c;記作&#xff1a;S‘c1c2c3…cn’ (n>0) 其中S是串的名字&#xff0c;‘c1c2c3…cn’ 是串值 ci是串中字符 n是串的長度&#xff0c;表示字符的數目 空串&a…

Linux下rm誤刪恢復 extundelete

Linux下rm誤刪恢復 extundelete 誤刪之后要第一時間卸載&#xff08;umount&#xff09;該分區&#xff0c;或者以只讀的方式來掛載&#xff08;mount&#xff09;該分區&#xff0c;否則覆寫了誰也沒辦法恢復。如果誤刪除的是根分區&#xff0c;最好直接斷電&#xff0c;進入…

5-數據結構-數組的學習

5.1數組的定義 定義&#xff1a; 由一組類型相同的數據元素構成的有序集合&#xff0c;每個數據元素稱為一個數據元素&#xff08;簡稱元素&#xff09;&#xff0c;每個元素受n&#xff08;n>1&#xff09;個線性關系的約束&#xff0c;每個元素在n個線性關系中的序號i1、…

timm 視覺庫中的 create_model 函數詳解

timm 視覺庫中的 create_model 函數詳解 最近一年 Vision Transformer 及其相關改進的工作層出不窮&#xff0c;在他們開源的代碼中&#xff0c;大部分都用到了這樣一個庫&#xff1a;timm。各位煉丹師應該已經想必已經對其無比熟悉了&#xff0c;本文將介紹其中最關鍵的函數之…

C--數據結構--樹的學習

6.2.1二叉樹的性質 1.二叉樹 性質&#xff1a; 1.若二叉樹的層次從1開始&#xff0c;則在二叉樹的第i層最多有2^(i-1)個結點 2.深度為k的二叉樹最多有2^k -1個結點 &#xff08;k>1&#xff09; 3.對任何一顆二叉樹&#xff0c;如果其葉結點個數為n0,度為2的非葉結點個數…