fork
是Unix和類Unix操作系統中用于創建進程的系統調用。fork
會創建一個子進程,該子進程幾乎是父進程的完全拷貝,包括代碼段、數據段、堆和棧。然而,為了提高效率,fork
使用了一種叫做寫時拷貝(Copy-On-Write, COW)的技術。
寫時拷貝(Copy-On-Write, COW)
基本原理
寫時拷貝的基本思想是:在創建子進程時,不立即復制父進程的內存空間,而是讓父子進程共享同一塊物理內存。只有當其中一個進程嘗試修改這塊內存時,操作系統才會在寫入時進行拷貝,為修改的那塊內存創建一個新的物理內存頁。這種方式節省了內存并提高了性能,特別是在很多情況下,子進程會很快調用exec
類函數替換進程鏡像,而無需實際修改父進程的內存。
內存方面的詳細工作機制
-
內存頁標記為只讀: 當
fork
系統調用創建子進程時,父進程的內存頁會被標記為只讀(read-only)。父子進程共享這些只讀的內存頁。 -
頁表復制: 父進程的頁表(page table)會被復制到子進程中,但是這些頁表指向的是相同的物理內存頁。因為頁表項是只讀的,所以兩個進程都不能修改這些內存頁。
-
頁故障(Page Fault)處理: 當父進程或子進程嘗試寫入某個內存頁時,會觸發頁故障(page fault)。操作系統的頁故障處理程序會檢查這個頁是否標記為寫時拷貝。
-
實際拷貝: 在觸發頁故障后,操作系統會為該進程分配一個新的物理內存頁,并將原來的內容復制到這個新的頁中。然后,頁表會被更新,指向新的物理內存頁,并將這個頁標記為可寫(writable)。只有在這一時刻,實際的物理內存才會被復制。
-
內存保護更新: 新的頁表項指向了新分配的物理內存頁后,內存保護會被更新,允許對新內存頁的寫操作。
舉個例子
假設有一個進程P,它的內存布局如下:
+-----------+
| 代碼段 | -> 共享且只讀
+-----------+
| 數據段 | -> 寫時拷貝
+-----------+
| 堆 | -> 寫時拷貝
+-----------+
| 棧 | -> 寫時拷貝
+-----------+
當進程P調用fork
創建子進程C時,初始情況下,父子進程共享所有的物理內存頁,并且這些頁都被標記為只讀。其頁表情況如下:
P 頁表:
+-----------+ +-----------+
| 代碼段 | --> | 物理頁1 |
+-----------+ +-----------+
| 數據段 | --> | 物理頁2 |
+-----------+ +-----------+
| 堆 | --> | 物理頁3 |
+-----------+ +-----------+
| 棧 | --> | 物理頁4 |
+-----------+ +-----------+C 頁表:
+-----------+ +-----------+
| 代碼段 | --> | 物理頁1 |
+-----------+ +-----------+
| 數據段 | --> | 物理頁2 |
+-----------+ +-----------+
| 堆 | --> | 物理頁3 |
+-----------+ +-----------+
| 棧 | --> | 物理頁4 |
+-----------+ +-----------+
當子進程C嘗試寫入堆時(假設是物理頁3),會觸發頁故障,操作系統執行寫時拷貝:
- 分配新的物理頁(物理頁5)。
- 將物理頁3的內容復制到物理頁5。
- 更新子進程C的頁表,使其堆指向物理頁5,并標記為可寫。
更新后的頁表情況如下:
P 頁表:
+-----------+ +-----------+
| 代碼段 | --> | 物理頁1 |
+-----------+ +-----------+
| 數據段 | --> | 物理頁2 |
+-----------+ +-----------+
| 堆 | --> | 物理頁3 |
+-----------+ +-----------+
| 棧 | --> | 物理頁4 |
+-----------+ +-----------+C 頁表:
+-----------+ +-----------+
| 代碼段 | --> | 物理頁1 |
+-----------+ +-----------+
| 數據段 | --> | 物理頁2 |
+-----------+ +-----------+
| 堆 | --> | 物理頁5 | -> 新分配的頁,可寫
+-----------+ +-----------+
| 棧 | --> | 物理頁4 |
+-----------+ +-----------+
此時,子進程C對堆的修改不會影響到父進程P,確保了兩個進程的內存隔離。
優點
-
節省內存:
- 使用寫時拷貝技術,父進程和子進程在
fork
后共享相同的內存頁,直到需要寫入時才進行實際的物理頁拷貝。這種方式減少了內存使用,尤其在子進程迅速調用exec
系列函數時優勢明顯。
- 使用寫時拷貝技術,父進程和子進程在
-
提高效率:
- 避免了
fork
時對所有內存頁進行立即拷貝的昂貴操作,提高了進程創建的效率。
- 避免了