1. 程式人生 > >Copy On Write機制瞭解一下

Copy On Write機制瞭解一下

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

我去畫張圖來理解一下:

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

二、解釋一下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/

三、檔案系統的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/

最後

最後我們再來看一下寫時複製的思想(摘錄自維基百科):

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

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

  • Linux通過Copy On Write技術極大地減少了Fork的開銷

  • 檔案系統通過Copy On Write技術一定程度上保證資料的完整性

其實在Java裡邊,也有Copy On Write技術。

Java中的COW