fork之後父子程序的記憶體關係
阿新 • • 發佈:2019-02-18
1.fock()呼叫的基本語義
#include<unistd.h>
pid_t fork(void);
//父程序返回子程序的pid,子程序返回0,錯誤返回-1
fork()建立了一個心的程序(child)信程序幾乎是呼叫程序(父程序的翻版),理解fork()的關鍵是,在完成對其呼叫之後,會產生2個程序,且每個程序都會從fork()的返回處開始執行. 這倆個程序將執行相同的程式段,但是擁有各自不同的堆段,棧段,資料段,每個子程式都可修改各自的資料段,堆段,和棧段 呼叫fork()之後先執行哪個程序的是由Linux下專有檔案/proc/sys/kernel/sched_child_runs_first的值來確定的(值為0父程序先執行,非0子程序先執行)
2.fork()呼叫之後父子程序的記憶體關係
PS:建議讀者在看這之前先看一下虛擬記憶體方面的知識,我的前幾篇博文中就有
從具體的表現形式(讀者可寫段程式碼測試)上看,我們會認為fork()是對父程序程式段,資料段,堆段,棧段的一個賦值,我們在表象上也的確可以這麼理解
早期的UNIX的fork()實現時,就是原汁原味的複製,和它的表像一樣,但是很明顯,這種方法效率太低,而且造成了很大的浪費,現在大部分的UNIX實現採用瞭如下倆種方法來規避這種浪費
(1)首先我們可以確定父子程序的程式碼段是相同的,所以程式碼段是沒必要複製的,因此核心將程式碼段標記為只讀,這樣父子程序就可以安全的共享此程式碼段了。fork之後在程序建立程式碼段時,新子程序的程序級頁表項都指向和父程序相同的物理頁幀
(2)而對於父程序的資料段,堆段,棧段中的各頁,由於父子程序要相互獨立,所以我們採用寫實複製的技術,來最大化的提高記憶體以及核心的利用率。剛開始,核心做了一些設定,令這些段的頁表項指向父程序相同的實體記憶體頁。呼叫fork之後,核心會捕獲所有父程序或子程序針對這些頁面的修改企圖(說明此時還沒修改)併為將要修改的頁面建立拷貝。系統將新的頁面拷貝分配給被核心捕獲的程序,還會對子程序的相應頁表項做適當的調整,現在父子程序就可以分別修改各自的上述段,不再互相影響了
寫實複製前:
寫實賦值後:
3.總結
表面看起來fork()建立子程序子程序拷貝了父程序的地址空間其實不然 剛呼叫完fork()之後,子程序只是擁有一份和父程序相同的頁表,其中頁表中指向RAM程式碼段的部分是不會改變的,而指向資料段,堆段,棧段的會在我們將要改變父子程序各自的這部分內容時,才會將要操作的部分進行部分複製