摘要:用于創建子進程等同于當前進程的副本。這個函數會有兩次返回,將子進程的返回給父進程,返回給子進程。當父子進程中有更改相應段的行為發生時,再為子進程相應的段分配物理空間。中斷例程中,就會把觸發的異常的頁復制一份,于是父子進程各自持有獨立的一份。
前言
只有光頭才能變強
在讀《Redis設計與實現》關于哈希表擴容的時候,發現這么一段話:
執行BGSAVE命令或者BGREWRITEAOF命令的過程中,Redis需要創建當前服務器進程的子進程,而大多數操作系統都采用寫時復制(copy-on-write)來優化子進程的使用效率,所以在子進程存在期間,服務器會提高負載因子的閾值,從而避免在子進程存在期間進行哈希表擴展操作,避免不必要的內存寫入操作,最大限度地節約內存。
觸及到知識的盲區了,于是就去搜了一下copy-on-write寫時復制這個技術究竟是怎么樣的。發現涉及的東西蠻多的,也挺難讀懂的。于是就寫下這篇筆記來記錄一下我學習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??? #include? ?? ? 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
二、解釋一下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%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技術。
這部分留到下一篇來說,敬請期待~
如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區留言,大家互相學習交流~~~
參考資料:
寫時復制,寫時拷貝,寫時分裂,Copy on write:https://my.oschina.net/dubenju/blog/815836
不會產奶的COW(Copy-On-Write)https://www.jianshu.com/p/b2fb2ee5e3a0
一個堅持原創的Java技術公眾號:Java3y,歡迎大家關注
3y所有的原創文章:
文章的目錄導航(腦圖+海量視頻資源):https://github.com/ZhongFuCheng3y/3y
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71957.html
摘要:體現的就是適配器模式。數組對象集合世界中的機制機制集合世界中比較常見的錯誤檢測機制,防止在對集合進行遍歷過程當中,出現意料之外的修改,會通過異常暴力的反應出來。而在增強循環中,集合遍歷是通過進行的。 前言 學習情況記錄 時間:week 2 SMART子目標 :Java 容器 記錄在學習Java容器 知識點中,關于List的重點知識點。 知識點概覽: 容器中的設計模式 從Array...
摘要:簡單來說是鏡像的源碼。例如,的鏡像鏡像,在中是一個基礎鏡像的鏡像也是鏡像那么鏡像和共享同一個基礎鏡像層,提高了存儲效率。 前言 只有光頭才能變強。 文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y showImg(https://segmentfault.com/img/remote/14600000180560...
摘要:今天主要講解的是本文力求簡單講清每個知識點,希望大家看完能有所收獲一和回顧線程安全的和我們知道是用于替代的,是線程安全的容器。使用迭代器遍歷時不需要顯示加鎖,看看與方法的實現可能就有點眉目了。 前言 只有光頭才能變強 showImg(https://segmentfault.com/img/remote/1460000016931828?w=1120&h=640); 前一陣子寫過一篇C...
摘要:只有在真正需要使用資源時才占用資源,寫時復制通常能減少資源的占用?;A方面規范新特性性能調優垃圾回收機制安全攻擊原理和防范攻擊原理和防范注入攻擊防范密碼哈希計算機網絡協議協議連接過程 從一個例子說起: 很明顯在這段代碼執行以后,$var_dup 的值應該還是laruence, 那么這又是怎么實現的呢?這就是 PHP 的 copy on write 機制: PHP 在修改一個變量以前,...
摘要:編者按本文作者為,主要介紹世上最怪異最難用的種編程語言。這些語言被稱為極品編程語言。創造它們的原因通常是為了測試編程語言設計的臨界,或者只是一個玩笑。就是母牛的編程語言設計時充分考慮了母牛的想法。 【編者按】本文作者為 Deepak Karanth,主要介紹世上最怪異、最難用的5種編程語言。文章系國內 ITOM 管理平臺 OneAPM 編譯呈現。 最難學編程語言有哪些?很多人都用過Ja...
閱讀 3492·2021-11-12 10:36
閱讀 2865·2021-09-22 15:35
閱讀 2814·2021-09-04 16:41
閱讀 1168·2019-08-30 15:55
閱讀 3580·2019-08-29 18:43
閱讀 2075·2019-08-23 18:24
閱讀 1419·2019-08-23 18:10
閱讀 1924·2019-08-23 11:31