關於fork函式中的記憶體複製和共享

2021-08-03 12:51:18 字數 3020 閱讀 5805

原來剛剛開始做linux下面的多程序程式設計的時候,對於下面這段**感到很奇怪,

1 #include2 #include3 #include

4 #include5 #include6 #include7

#define len 2

8void err_exit(char *fmt,...);

9int main(int argc,char *argv)

1022

else

2326}27

28return0;

29 }

為什麼這段程式會建立3個子程序,而不是兩個,為什麼在第20行後面加上乙個return 0;就建立的又是兩個子程序了?原來一直搞不明白,後來了解了c語言程式的儲存空間布局以及在fork之後父子程序是共享正文段(**段cs)之後才明白這其中的緣由!具體原理是啥,且容我慢慢道來!

首先得明白乙個東西就是c程式的儲存空間布局,如下圖所示:

當乙個c程式執行之後,它會被載入到記憶體之中,它在記憶體中的布局如上圖,分為這麼幾個部分,環境變數和命令列引數、棧、堆、資料段(初始化和未初始化的)、正文段,下面挨個來說明這幾段分別代表了什麼:

環境變數和命令列引數:這些指的就是unix系統上的環境變數(比如$path)和傳給main函式的引數(argv指標所指向的內容)。

資料段:這個是指在c程式中定義的全域性變數,如果沒有初始化,那麼就存放在未初始化的資料段中,程式執行時統一由exec賦值為0。否則就存放在初始化的資料段中,程式執行時由exec統一從程式檔案中讀取。(了解彙編的朋友們想必知道組合語言中的資料段ds,這和彙編中的資料段其實是乙個東西)。

堆:這一部分主要用來動態分配空間。比如在c語言中用malloc申請的空間就是在這個區域申請的。

正文段:c語言**並不是直接執行的,而是被編譯成了機器指令才能夠在電腦上執行,最終生成的機器指令就是存放在這個區域(彙編中的**段cs指的就是這片區域)。

棧:個人感覺這是c程式記憶體布局最關鍵的部分了。這個部分主要用來做函式呼叫。具體而言怎麼說呢,程式剛開始棧中只有main這乙個函式的內容(即main的棧幀),如果main函式要呼叫func函式,那麼func函式的返回位址(main函式的位址),func函式的引數,func函式中定義的區域性變數,還有func函式的返回值等等這些都會被壓入棧中,這時棧中就多了func函式的內容(func的棧幀)。然後func函式執行完了之後再來彈棧,把它原來壓的內容去掉(即清除掉func棧幀),此時棧中又只剩下了main的棧幀。(這片區域就是彙編中的棧段ss)

ok,這就是c程式的儲存器布局。這裡我聯想到另外一點,就是全域性變數和靜態變數是儲存在資料段中的,而區域性變數是儲存在棧中的,棧中資料在函式呼叫完之後一彈棧就沒了,這就是為什麼全域性變數的生存週期比區域性變數的生存週期要長的原因。

了解了c程式在儲存器的布局之後,我們再來了解fork的記憶體複製機制,關於這個,我們只需要了解一句話就夠了,「子程序複製父程序的資料空間(資料段)、棧和堆,父、子程序共享正文段。」也就是說,對於程式中的資料,子程序要複製乙份,但是對於指令,子程序並不複製而是和父程序共享。具體來看下面這段**(這是我在上面那段**上稍微新增了一點東西):

1

/*這個程式會建立3個子程序,理解這句話,父子程序複製資料段、棧、堆,共享正文段2*

3*/4 #include5 #include6 #include

7 #include8 #include9 #include10

#define bufsize 512

11#define len 2

12void err_exit(char *fmt,...);

13int main(int argc,char *argv)

1428

else

2932}33

34return0;

35 }

為什麼上面那段**會建立三個子程序?我們來具體分析一下它的執行過程:

首先父程序執行迴圈,通過fork建立乙個子程序,然後sleep5秒。

再來看父程序建立的這個子程序,這裡我們記為子程序1.子程序1完全複製了這個父程序的資料部分,但是需要注意的是它的正文段是和父程序共享的。也就是說,子程序1開始執行**的部分並不是從main的 { 開始執行的,而是主函式執行到**了,它就接著執行,具體而言就是它會執行fork後面的**。所以子程序1首先會列印出它的id和它的父程序的id。然後繼續第二遍迴圈,然後這個子程序1再來建立乙個子程序,我們記為子程序11,子程序1開始sleep。

子程序11接著子程序1執行的**開始執行(即fork後面),它也是列印出它的id和父程序id(子程序1),然後此時loop的值再加1就等於2了,所以子程序2直接就返回了。

那個子程序1sleep完了之後也是loop的值加1之後變成了2,所以子程序1也返回了!

然後我們再返回去看父程序,它僅僅迴圈了一次,sleep完之後再來進行第二次迴圈,這次又建立了乙個子程序我們記為子程序2。然後父程序開始sleep,sleep完了之後也結束了。

那麼那個子程序2怎麼樣了呢?它從fork後開始執行,此時loop等於1,它列印完它的id和父程序id之後,就結束迴圈了,整個子程序2就直接結束了!

這就是上面那段**的執行流程,程序間的關係如下圖所示:

上圖中那個loop=%d就是當這個程序開始執行的時候loop的值。上面那段**的執行結果如下圖:

這裡這個3498程序就是我們的主程序,3499就是子程序1,3500就是子程序11,3501就是子程序2。

最後,我們再來回答一下我們開始的時候提出的那個問題,為什麼在子程序的處理部分「 if(pid == 0) 」最後加乙個return 0,就會建立兩個子程序了,就是因為子程序1執行到這裡直接就結束了,不再進行第二遍迴圈了,所以就不會再去建立那個子程序11了,所以最後一共就是建立了兩個子程序啊!

Linux中關於fork 複製程序

fork 複製程序1 fork 的基本概念 乙個現有程序可以用fork 函式通過系統呼叫建立乙個新程序,該函式定義如下 include pid t fork void 返回 若成功則在子程序中返回0,在父程序中返回子程序pid,若出錯則返回 1 因此,可以通過返回值知道當前程序是父程序還是子程序。2...

c 中的fork函式 FORK()函式

乙個程序,包括 資料和分配給程序的資源。fork 函式通過系統呼叫建立乙個與原來程序幾乎完全相同的程序,也就是兩個程序可以做完全相同的事,但如果初始引數或者傳入的變數不同,兩個程序也可以做不同的事。乙個程序呼叫fork 函式後,系統先給新的程序分配資源,例如儲存資料和 的空間。然後把原來的程序的所有...

關於Fork和Malloc的思考

文章試讀 不拘乙個遍程式系列 程式設計序不能乙個腦袋鑽到底,有時要學會變通,即所謂的曲線救國。一 二 三 四 職場規劃 一些雜七雜八的職場感悟吧。不值錢的軟體人才 精力充沛與事業成功 讓係分來得更猛烈些吧 不值錢的系統分析師 經濟危機下大學生如何就業 我的詩歌 都是我的打油詩,但是很值得一讀。不要工...