https://note.youdao.com/s/71lWGFsI
圖檔我懶的粘貼過來了,上面是源筆記連結,想看全的請移步。
程序是系統中基本的執行機關。
Linux中,使用者可以建立子程序,子程序存在系統中,獨立于父程序,子程序可以接受系統的排程,可以配置設定得到系統資源。系統可以檢查到子程序的存在,且可以擁有與父程序同樣的權利。
fork()函數不需要傳入參數,傳回值是一個程序的ID,一次調用傳回倆個值。一個是傳回給父程序的,父程序得到的是建立子程序時候,子程序的ID。另一個是傳回給子程序,子程序得到的是一個pid_t = 0 的值,由于0号程序是核心程序,是以子程序的程序号不可能為0,是以可以用來差別誰是父,誰是子。
fork()流程
fork()在建立一個程序後,并從核心中配置設定一個程序ID(新ID)給在個新程序。然後為這個新程序配置設定程序空間,并将父程序的程序空間中的内容複制到子程序的程序空間中,這些被複制的内容包括:資料段、堆棧段(在虛拟位址空間上父程序與子程序,資料段與堆棧段是不一樣的,但是在實際實體位址空間上,他們是共同使用一段實體位址空間。隻有當子程序開始修改父程序的資料段或堆棧段的時候,才會為子程序配置設定一段新資料段、堆棧段的實體位址空間。這就是的是寫時複制)。然後代碼段是與父程序共享的。是以建立出來的子程序其實就是父程序的一個複制版。
如果代說碼段不是共享的話,那麼作業系統需要:
為子程序的頁表配置設定頁幀
為子程序的頁配置設定頁幀
初始化子程序的頁表
把父程序的頁複制到子程序相應的頁中
這個過程十分消耗cup周期,破壞了高速緩存中的内容。
是以現在的Linux核心采用的是寫時複制。父程序和子程序共享頁幀而不是複制頁幀。然而,隻要頁幀被共享,它們就不能被修改,即頁幀被保護。
實際操作
fork()建立出來的子程序是與父程序的地位是一樣的,他們都接受系統的排程,在運作上可以展現出,子程序運作的時候,父程序不被阻塞,父程序可以正常運作。
fork()建立出來的子程序,這個子程序的代碼執行是在從fork()開始,它不像正常啟動程式一樣從main()的第一行往下執行,是以說為什麼我們fork()會得到倆個值,且子程序不再一次重複執行fork()函數。
代碼展現
因為父程序與子程序的地位是一樣的,是以有可能是子程序先輸出,上面的輸出順序是不确定的。它取決于系統的排程算法。
另一種情況
這種情況就是我們所說的僵屍程序,也是孤兒程序。
這是因為父程序提前比子程序先結束了,子程序現在處在懸空的狀态,子程序一直還在,盡管它已經運作結束,這是因為它的第一任父程序已經結束了,現在它被托付給0号核心程序,0号程序是永遠不會結束的。
當我們按下Enter鍵的時候該子程序就被強制結束了。
還有另種常見問題;
fork()函數寫在循環體内。
if(int i = 0; i<2; i++){
fork();
}
childe i= 0;pid = 0;是父程序建立子1程序時候,子1程序pid得到的是0,此時的 i 是為1的,現在還是在第一次循環中
這裡的father i =1, pid = 43732 是子1建立出孫的時候,子1這時候是父親了。4373是孫的程序号,子1的程序号是43732。
father = 1, pid = 43733 是,父程序建立子2的時候,子2程序号是43733。
至于最後的兩個 childe = 1,pid = 0;一個是孫程序被建立後,孫程序中,孫得到的pid 是0,此時 i 是1;
另一個是子2,子2被建立後,子2程序中,pid得到的是0,i 此時是1.
這樣的寫法,總共會建立出3個新的程序,父程序會建立兩個,而第一個被建立出來的程序又會建立一個程序。這是因為
資源共享情況
深入了解:
具體過程是這樣的:
fork子程序完全複制父程序的棧空間,也複制了頁表,但沒有複制實體頁面,是以這時虛拟位址相同,實體位址也相同,但是會把父子共享的頁面标記為“隻讀”,如果父子程序一直對這個頁面是同一個頁面,直到其中任何一個程序要對共享的頁面“寫操作”,這時核心會複制一個實體頁面給這個程序使用,同時修改頁表。而把原來的隻讀頁面标記為“可寫”,留給另外一個程序使用。這就是所謂的“寫時複制”。
在了解上:可以認為fork後,這兩個相同的虛拟位址指向的是不同的實體位址,這樣友善了解父程序之間的獨立性。
但實際上,linux為了提高fork的效率,采用了copy-on-write技術,fork後,這兩個虛拟位址實際上指向相同的實體位址。(記憶體頁),隻有任何一個程序試圖修改這個虛拟位址裡的内容前,兩個虛拟位址才會指向不同的實體位址。新的實體位址的内容從源實體位址中複制得到。
從結果可以看出子程序和父程序的 PID不同,記憶體資源sum是值的複制,子程序改變了sum的值,而父程序中的sum沒有被改變。有人認為這樣大批量的複制會導緻執行效率過低。其實在複制過程中,子程序複制了父程序的task_struct,系統堆棧空間和頁面表,這意味着上面的程式,我們沒有執行sum前,其實子程序和父程序的sum指向的是同一塊記憶體。【而當子程序改變了父程序的變量時候,會通過 copy_on_write的手段為所涉及的頁面建立一個新的副本。是以當我們執行++sum後,這時候子程序才建立了一個頁面複制原來頁面的内容, 基本資源的複制是必須的,而且是高效的。整體看上去就像是父程序的獨立存儲空間也複制了一遍。】
上述解釋來源于————————————————
原文連結:https://blog.csdn.net/gogokongyin/article/details/51178257————————————————
至于我們的sum在childe中改變後為什麼位址還沒變,而e改變後位址立馬變,是因為其實sum是和e一樣,但它們改變的時候系統實際意義上是已重新為他們配置設定空間了,至于sum在輸出位址的時候為什麼沒變,其實是因為系統為了更高效會為一些變量維護一張表這張表放的是像成員函數int這樣的類型的資料,是以我們修該的sum其實是修改系統為這種常用變量的而建立的一張表中的值,真正的記憶體中的sum我們還沒有改變到,sum的真正改變是由系統決定的。
而e是因為我們直接修改記憶體中的值,是以我們會立馬看到e的位址發生改變。
sum對sum的修改是需要經過系統,是以當我們修改sum後,系統還沒修改sum也就沒有看到sum的位址發生改變,等系統真正去更新sum的時候,才會為sum配置設定位址。是以在系統沒有去更新sum的時候輸出sum的位址的實際是原來father父程序中sum的位址,因為寫時複制,是以父程序和子程序他們沒有真正去修改sum的時候,實際是共享的。
此篇文章質料參考來自:LINUX c 程式設計 王者歸來
與原文連結:https://blog.csdn.net/gogokongyin/article/details/51178257————————————————