Linux 寫時復(fù)制機制原理
在 Linux 系統(tǒng)中,調(diào)用 fork 系統(tǒng)調(diào)用創(chuàng)建子進程時,并不會把父進程所有占用的內(nèi)存頁復(fù)制一份,而是與父進程共用相同的內(nèi)存頁,而當(dāng)子進程或者父進程對內(nèi)存頁進行修改時才會進行復(fù)制 —— 這就是著名的 寫時復(fù)制 機制。
下面我們將分析 Linux 寫時復(fù)制(Copy On Write) 機制的原理。
虛擬內(nèi)存與物理內(nèi)存
進程的內(nèi)存可分為 虛擬內(nèi)存 和 物理內(nèi)存。
物理內(nèi)存:就是電腦安裝的內(nèi)存條,如果電腦安裝了2GB的內(nèi)存條,那么系統(tǒng)就用于 0 ~ 2GB 的物理內(nèi)存空間。虛擬內(nèi)存:虛擬內(nèi)存是使用軟件虛擬的,在 32 位操作系統(tǒng)中,每個進程都獨占 4GB 的虛擬內(nèi)存空間。
應(yīng)用程序使用的是 虛擬內(nèi)存,比如 C 語言取地址操作符號 & 所得到的地址就是 虛擬內(nèi)存地址。而 虛擬內(nèi)存地址 需要映射到 物理內(nèi)存地址 才能使用,如果使用沒有映射的 虛擬內(nèi)存地址,將會導(dǎo)致 缺頁異常。
虛擬內(nèi)存地址 映射到 物理內(nèi)存地址 如下圖所示:

如上圖所示,進程A與進程B的相同 虛擬內(nèi)存地址 映射到不同的 物理內(nèi)存地址,這就是不同進程的相同虛擬內(nèi)存地址互不影響的原因。
寫時復(fù)制原理
前面介紹了 虛擬內(nèi)存 與 物理內(nèi)存 的概念,接下來將會介紹 Linux 寫時復(fù)制 的原理。
前面說過,虛擬內(nèi)存 需要與 物理內(nèi)存 進行映射才能使用,如果不同進程的 虛擬內(nèi)存地址 映射到相同的 物理內(nèi)存地址,那么就實現(xiàn)了共享內(nèi)存的機制。如下圖所示:

由于進程A的 虛擬內(nèi)存M 與進程B的 虛擬內(nèi)存M' 映射到相同的 物理內(nèi)存G,所以當(dāng)修改進程A 虛擬內(nèi)存M 的數(shù)據(jù)時,進程B 虛擬內(nèi)存M' 的數(shù)據(jù)也會跟著改變。
Linux 為了加速創(chuàng)建子進程過程與節(jié)省內(nèi)存使用的原因,實現(xiàn)了 寫時復(fù)制 的機制。
寫時復(fù)制 的原理大概如下:
創(chuàng)建子進程時,將父進程的
虛擬內(nèi)存與物理內(nèi)存映射關(guān)系復(fù)制到子進程中,并將內(nèi)存設(shè)置為只讀(為什么要設(shè)置為只讀?)。當(dāng)子進程或者父進程對內(nèi)存數(shù)據(jù)進行修改時,便會觸發(fā)
寫時復(fù)制機制:將原來的內(nèi)存頁復(fù)制一份新的,并重新設(shè)置其內(nèi)存映射關(guān)系,將父子進程的內(nèi)存讀寫權(quán)限設(shè)置為可讀寫。
寫時復(fù)制 過程如下圖所示:

從上圖可知,當(dāng)創(chuàng)建子進程時,父子進程指向相同的 物理內(nèi)存,而不是將父進程所占用的 物理內(nèi)存 復(fù)制一份。這樣做的好處有兩個:
加速創(chuàng)建子進程的速度。
減少進程對物理內(nèi)存的使用。
但這個時候只能對內(nèi)存進行讀操作,如果父進程或子進程對內(nèi)存進行寫操作,那么將會觸發(fā) 缺頁異常,而在 缺頁異常 處理中會對物理內(nèi)存進行復(fù)制,并且重新映射其內(nèi)存映射關(guān)系。
復(fù)制并重新映射到新的物理內(nèi)存后,父子進程的虛擬內(nèi)存就映射到不同的物理內(nèi)存上,這時父子進程都可以對內(nèi)存進行寫操作而互不影響,所以需要把父子進程的內(nèi)存讀寫權(quán)限設(shè)置為可讀寫。
總結(jié)
本篇文章主要介紹了 Linux 寫時復(fù)制 的原理,寫時復(fù)制 是 Linux 創(chuàng)建子進程高效的關(guān)鍵所在,而且還能節(jié)省對物理內(nèi)存使用。我們將在下一篇文章中對 寫時復(fù)制 的實現(xiàn)進行詳細的分析。
