目錄
- 一、fork的由來
- 二、早期UNIX的覆蓋(overlaying)技術(shù)
- 三、fork引入UNIX前的表象
- 1、UNIX fork的誕生
- 2、UNIX fork-exec
- 3、UNIX fork/exec/exit/wait
一、fork的由來
fork的思想在UNIX出現(xiàn)幾年前就出現(xiàn)了,時間大概是1963年,這比UNIX在PDP-7上的第一個版本早了6年。
1963年,計算機科學家Melvin Conway(以Conway's Law聞名于世)寫下一篇論文,正式提出了fork思想,
fork的思想最初是Conway作為一種 多處理器并行 的方案提出來的,這個想法非常有意思。簡而言之,fork思想來源于流程圖。
我們看一個普通的流程圖:
你看,流程圖的分枝處,fork-叉子,多么形象!
一個流程圖上的分支點分裂出來的分支顯然是邏輯獨立的,這便是可并行的前提,于是它們便可以表現(xiàn)為不同的 處理進程(process) 的形式,當時的表達還只是“process”這個術(shù)語,它還不是現(xiàn)代操作系統(tǒng)意義上的“進程”的概念。
join同步點表現(xiàn)為多個并行處理的進程由于某種原因不得不同步的點,也就是多個并行流程匯合的點,直到現(xiàn)在,在多線程編程中,這個點依然叫join。比如Java Thread的join方法以及pthread庫的pthread_join函數(shù)。
廣義來講,join也表示諸如臨界區(qū)等必須串行通過的點, 減少join點的數(shù)量將會提高并行的效率。
我們來看看Conway論文中關(guān)于fork的原始圖示:
Conway在論文中的另一個創(chuàng)舉是,他將處理進程(也就是后來操作系統(tǒng)中的process的概念)以及執(zhí)行該進程的處理器(即CPU核)分離了開來,抽象出了schedule層。
大意是說、“只要滿足系統(tǒng)中的活動處理器數(shù)量是總處理器數(shù)量和并行處理進程的最小值即可?!?這意味著調(diào)度程序可以將多處理器系統(tǒng)的所有處理器和系統(tǒng)所有處理進程分別看作是統(tǒng)一的資源池和消費者,執(zhí)行統(tǒng)一調(diào)度:
在UNIX引入fork之后,這種多處理器并行的設(shè)計思想就深入到了UNIX的核心。這個思想最終也影響了UNIX以及后來的Linux,直到現(xiàn)在。
關(guān)于這個設(shè)計思想為什么可以影響UNIX這么久,我想和Conway本人的“Conway's law”不無關(guān)系,在這個law中,他提到:Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.
二、早期UNIX的覆蓋(overlaying)技術(shù)
接下來看UNIX fork的另一個脈絡(luò),1969年最初的UNIX用一種在現(xiàn)在看來非常奇怪的方式運行。
一般的資料都是從UNIX v6版本開始講起,那個版本已經(jīng)是比較 “現(xiàn)代” 的版本了,所以很少有人能看到最初的UNIX是什么樣子的。即便是能查閱到的1970年的PDP-7上運行的UNIX源碼,也是引入fork之后的版本,在那之前的最原始版本幾乎找不到了(你可能會說,那時的UNIX不叫UNIX,but who cares…)。
最初的UNIX是一個分時系統(tǒng),它只有兩個shell進程,分別屬于兩個終端:
分時系統(tǒng)最初并不是基于進程分時的,那時根本還沒有完整的進程的概念,分時系統(tǒng)是針對終端分時的,而操作員坐在終端前,為了讓每個操作員在操作過程中感覺上是在獨占機器資源,每個終端享受一段時間的時間片,在該時間片內(nèi),該終端前的操作員完全享受機器,但是為了公平,超過了時間片,時間片就要給另一個終端。
就是這樣,最初的UNIX為了體現(xiàn)分時特性,實現(xiàn)了最少的兩個終端。注意,最初的UNIX沒有fork,沒有exec,甚至沒有多進程的概念,為了實現(xiàn)分時,系統(tǒng)中僅有兩個樸素的shell進程。
事實上,最初的UNIX用只有兩個元素的表來容納所有進程(顯然,這看起來好笑…),當然,這里的 “表” 的概念也是抽象的樸素概念,因為當時的系統(tǒng)是用PDP-7的匯編寫的,還沒有后來C語言數(shù)據(jù)結(jié)構(gòu)。
我們現(xiàn)在考慮其中一個終端的shell進程如何工作。馬上問題就來了, 這個shell進程如何執(zhí)行別的命令程序??
如果說系統(tǒng)中最多只能容納兩個進程,一個終端只有一個shell進程的話,當該終端的shell進程執(zhí)行其它命令程序時,它自己怎么辦?這個問題得思考一會兒…
注意:不要用現(xiàn)代的眼光去評價1969年的初版UNIX,按照現(xiàn)代的眼光,執(zhí)行一個程序必然要生成一個新的進程,顯然這在初版UNIX中并不正確。
答案是根本不用產(chǎn)生新的進程,直接將命令程序的代碼載入內(nèi)存并 覆蓋 掉shell進程的代碼即可!當命令執(zhí)行完后,再用shell的代碼覆蓋掉命令程序的代碼,針對單獨的終端,系統(tǒng)其實一直在執(zhí)行下面的覆蓋循環(huán)(摘自論文的Process control 章節(jié)):
然而,在fork被引入UNIX之前,事實就是這樣。一個終端上一直都是那一個進程,一會兒它執(zhí)行shell的代碼,一會兒它執(zhí)行具體命令程序的代碼,以下是一個覆蓋程序的結(jié)構(gòu)(圖片來自《FreeBSD操作系統(tǒng)設(shè)計與實現(xiàn)》一書):
然而,當時畢竟還沒有將這個邏輯封裝成exec系統(tǒng)調(diào)用,這些都是每一個進程顯式完成的:
- 對于shell執(zhí)行命令程序而言,shell自己執(zhí)行disk IO來載入命令程序覆蓋掉自身;
- 對于命令程序執(zhí)行結(jié)束時,exit調(diào)用內(nèi)部執(zhí)行disk IO載入shell程序。
exec邏輯是shell程序的一部分,由于它會被所有的命令程序所使用,該邏輯也被封裝到了exit調(diào)用中。
三、fork引入UNIX前的表象
1963年Melvin Conway提出了fork思想,作為在多處理器中并行執(zhí)行進程的一個手段。
1969年湯普森版UNIX僅有兩個shell進程,使用覆蓋(overlaying)技術(shù)執(zhí)行命令。
截止目前,我們看到的表象是:
湯普森版UNIX沒有fork,沒有exec,沒有wait,僅有的庫函數(shù)般的exit也和現(xiàn)在的exit系統(tǒng)調(diào)用大相徑庭,顯然湯普森版UNIX并非一個多進程系統(tǒng),而只是一個可以跑的簡陋的兩終端分時系統(tǒng)!
1、UNIX fork的誕生
fork是如何引入UNIX的呢?
這還要從采用覆蓋技術(shù)的湯普森版UNIX所固有的問題說起,還是看論文原文:
若要解決這些問題,很簡單的方案湯普森都想到了:
- 保持shell進程的駐留而不是銷毀。命令執(zhí)行時,將其交換到磁盤便是了
很顯然,命令程序是不能覆蓋掉shell進程了。解決方案是使用 “交換” 技術(shù)。
交換技術(shù)和覆蓋技術(shù)其實都是解決有限內(nèi)存的多進程使用問題的,不同點在于方向不同:
- 覆蓋技術(shù)指的是用不同的進程磁盤映像覆蓋當前的進程內(nèi)存映像。
- 交換技術(shù)指的是用將進程的內(nèi)存映像交換到磁盤,載入一個別的進程磁盤映像。
使用交換技術(shù)解決覆蓋的問題,意味著要創(chuàng)建新的進程:
UNIX需要進行改動,兩個配額的進程表顯然不夠用了。當然,解決方案也并不麻煩:
要講效率,創(chuàng)造不如抄襲,創(chuàng)建新進程的最直接的就是copy當前shell進程,在copy的新進程中執(zhí)行覆蓋,命令程序覆蓋copy的新進程,而當前的終端shell進程則被交換到磁盤保得全身。
覆蓋和交換相結(jié)合了,UNIX離現(xiàn)代化更近了一步!
確定了copy當前進程的方案后,進一步的問題是如何來copy進程。
現(xiàn)在要說回fork了。
Conway提出fork思想后,馬上就有了fork的實現(xiàn)原型(正如Conway自己所說,他只是提出了一個可能造就存在的想法,并沒有實現(xiàn)它),Project Genie算是實現(xiàn)fork比較完善的系統(tǒng)之一了。
Project Genie系統(tǒng)的fork不僅僅是盲目地copy進程,它對fork的過程擁有精細的控制權(quán),比如分配多大的內(nèi)存空間,copy哪些必要的資源等等。顯然,Project Genie的fork是沖著Conway的多處理器并行邏輯去的。
還是那句話,創(chuàng)造不如抄襲,UNIX若想實現(xiàn)進程copy,有一個現(xiàn)成的模版就是Project Genie,但是Project Genie的fork對于UNIX太過復雜,太過精細化了,UNIX顯然用不到這些精細的控制, UNIX僅僅是想讓fork出來的新進程被覆蓋,而不是讓它去執(zhí)行什么多處理器上的并行邏輯。
換句話說,UNIX只是借用了fork的copy邏輯的實現(xiàn),來完成一件別的事。
于是,UNIX非常粗暴的實現(xiàn)了fork!即完全copy父進程,這就是直到現(xiàn)在我們依然在使用的fork系統(tǒng)調(diào)用:
投機取巧:
- fork本來就不是讓你用來覆蓋新進程的,不然為何多此一舉。fork是讓你來分解程序流程得以并行處理的。
UNIX fork就此誕生!
我們再次回顧一下UNIX fork誕生之前的景象:
再來看看fork誕生之后的景象:
于是UNIX正式邁開了現(xiàn)代化建設(shè)的步伐,一直走到了今天。
2、UNIX fork-exec
關(guān)于exec,故事沒什么好講的,它事實上就是關(guān)于上述覆蓋邏輯的封裝,此后程序員不必自己寫覆蓋邏輯了,直接調(diào)用exec系統(tǒng)調(diào)用即可。
于是經(jīng)典的UNIX fork-exec序列便形成了。
3、UNIX fork/exec/exit/wait
值得一提的是,fork被引入UNIX后,exit的語義發(fā)生了巨大的改變。
在原始的1969年湯普森版UNIX中,由于每一個終端有且僅有一個進程,這意味著覆蓋永遠是在shell程序和某個命令程序之間進行的:
- shell執(zhí)行命令A:命令程序A覆蓋內(nèi)存中的shell代碼。
- 命令A執(zhí)行結(jié)束:shell覆蓋結(jié)束的命令A的內(nèi)存代碼。
然而,在fork被引入后,雖然shell執(zhí)行某個命令依然是特定的命令程序覆蓋fork出來的shell子進程,但是當命令執(zhí)行完畢后,exit邏輯卻不能再讓shell覆蓋當前命令程序了,因為shell從來就沒有結(jié)束過,它作為父進程只是被交換到了磁盤而已(后來內(nèi)存到了,可以容納多個進程時,連交換都不需要了)。
那么exit將讓誰來覆蓋當前進程呢?
答案是不用覆蓋,按照exit的字面意思,它只要結(jié)束自己就可以了。
本著 自己的資源自己管理的責任原則 exit只需要清理掉自己分配的資源即可。比如清理掉自己的內(nèi)存空間以及一些其它的數(shù)據(jù)結(jié)構(gòu)。
對于子進程本身而言,由于它是父進程生成的,所以它便由父進程來管理釋放。于是經(jīng)典的UNIX進程管理四件套正式形成:
到此這篇關(guān)于Unix/Linux fork隱藏的開銷的文章就介紹到這了,更多相關(guān)Unix/Linux fork內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!,希望大家以后多多支持腳本之家!