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全部的原創文章:

相關文章
相關標籤/搜索