只有光頭才能變強html
在讀《Redis設計與實現》關於哈希表擴容的時候,發現這麼一段話:java
執行BGSAVE命令或者BGREWRITEAOF命令的過程當中,Redis須要建立當前服務器進程的子進程,而大多數操做系統都採用寫時複製(copy-on-write)來優化子進程的使用效率,因此在子進程存在期間,服務器會提升負載因子的閾值,從而避免在子進程存在期間進行哈希表擴展操做,避免沒必要要的內存寫入操做,最大限度地節約內存。linux
觸及到知識的盲區了,因而就去搜了一下copy-on-write寫時複製這個技術到底是怎麼樣的。發現涉及的東西蠻多的,也挺難讀懂的。因而就寫下這篇筆記來記錄一下我學習copy-on-write的過程。git
本文力求簡單講清copy-on-write這個知識點,但願你們看完能有所收穫。程序員
在說明Linux下的copy-on-write機制前,咱們首先要知道兩個函數:fork()
和exec()
。須要注意的是exec()
並非一個特定的函數, 它是一組函數的統稱, 它包括了execl()
、execlp()
、execv()
、execle()
、execve()
、execvp()
。github
首先咱們來看一下fork()
函數是什麼鬼:服務器
fork is an operation whereby a process creates a copy of itself.函數
fork是類Unix操做系統上建立進程的主要方法。fork用於建立子進程(等同於當前進程的副本)。性能
若是接觸過Linux,咱們會知道Linux下init進程是全部進程的爹(至關於Java中的Object對象)學習
下面以例子說明一下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),因此子進程一樣是會執行fork()
以後的代碼。因此說:
fpid變量
的值是子進程的pidfpid變量
的值是0從上面咱們已經知道了fork會建立一個子進程。子進程的是父進程的副本。
exec函數的做用就是:裝載一個新的程序(可執行映像)覆蓋當前進程內存空間中的映像,從而執行不一樣的任務。
我去畫張圖來理解一下:
參考資料:
fork()會產生一個和父進程徹底相同的子進程(除了pid)
若是按傳統的作法,會直接將父進程的數據拷貝到子進程中,拷貝完以後,父進程和子進程之間的數據段和堆棧是相互獨立的。
可是,以咱們的使用經驗來講:每每子進程都會執行exec()
來作本身想要實現的功能。
exec()
,原有的數據會被清空)既然不少時候複製給子進程的數據是無效的,因而就有了Copy On Write這項技術了,原理也很簡單:
另外的表達方式:
在fork以後exec以前兩個進程用的是相同的物理空間(內存區),子進程的代碼段、數據段、堆棧都是指向父進程的物理空間,也就是說,二者的虛擬空間不一樣,但其對應的物理空間是同一個。
當父子進程中有更改相應段的行爲發生時,再爲子進程相應的段分配物理空間。
若是不是由於exec,內核會給子進程的數據段、堆棧段分配相應的物理空間(至此二者有各自的進程空間,互不影響),而代碼段繼續共享父進程的物理空間(二者的代碼徹底相同)。
而若是是由於exec,因爲二者執行的代碼不一樣,子進程的代碼段也會分配單獨的物理空間。
Copy On Write技術實現原理:
fork()以後,kernel把父進程中全部的內存頁的權限都設爲read-only,而後子進程的地址空間指向父進程。當父子進程都只讀內存時,相安無事。當其中某個進程寫內存時,CPU硬件檢測到內存頁是read-only的,因而觸發頁異常中斷(page-fault),陷入kernel的一箇中斷例程。中斷例程中,kernel就會把觸發的異常的頁複製一份,因而父子進程各自持有獨立的一份。
Copy On Write技術好處是什麼?
Copy On Write技術缺點是什麼?
幾句話總結Linux的Copy On Write技術:
exec()
把當前進程映像替換成新的進程文件,完成本身想要實現的功能。參考資料:
基於上面的基礎,咱們應該已經瞭解COW這麼一項技術了。
下面我來講一下我對《Redis設計與實現》那段話的理解:
參考資料:
下面來看看文件系統中的COW是啥意思:
Copy-on-write在對數據進行修改的時候,不會直接在原來的數據位置上進行操做,而是從新找個位置修改,這樣的好處是一旦系統忽然斷電,重啓以後不須要作Fsck。好處就是能保證數據的完整性,掉電的話容易恢復。
參考資料:
最後咱們再來看一下寫時複製的思想(摘錄自維基百科):
寫入時複製(英語:Copy-on-write,簡稱COW)是一種計算機程序設計領域的優化策略。其核心思想是,若是有多個調用者(callers)同時請求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統纔會真正複製一份專用副本(private copy)給該調用者,而其餘調用者所見到的最初的資源仍然保持不變。這過程對其餘的調用者都是透明的(transparently)。此做法主要的優勢是若是調用者沒有修改該資源,就不會有副本(private copy)被創建,所以多個調用者只是讀取操做時能夠共享同一份資源。
至少從本文咱們能夠總結出:
其實在Java裏邊,也有Copy On Write技術。
這部分留到下一篇來講,敬請期待~
若是你們有更好的理解方式或者文章有錯誤的地方還請你們不吝在評論區留言,你們互相學習交流~~~
參考資料:
一個堅持原創的Java技術公衆號:Java3y,歡迎你們關注
3y全部的原創文章: