date: 2014-10-18 12:00linux
這一章《情景分析》講得很通俗易懂。數據結構
在linux系統中,第一個進程(init進程)是與生俱來的,是內核設計者刻意安排的。除此之外,一個進程必須從當前已經存在的某個進程「複製」出來,而不能憑空「創造」出來。函數
linux將進程的建立與目標程序的執行分兩步走。線程
第一步,從已經存在的「父進程」像細胞分裂那樣「複製」出一個子進程。 此時,子進程有本身的task_struct結構與系統空間堆棧(即進程四要素中的第2和第3個要素),但與父進程共享全部其餘資源,好比打開的文件,並且這些文件的當前讀寫指針也停留在相同的地方(父子進程每一個相同的文件描述符共用同一個文件表項)。因此這一步所作的工做是「複製」。爲此,linux提供了兩個系統調用,fork和clone。區別在於fork是所有複製(所以fork不帶參數),父進程的資源經過結構體的複製(能夠理解爲「深拷貝」)「遺傳」給子進程。而clone則是有選擇性的複製(因此clone帶參數),而沒有複製的數據結構則經過指針的拷貝(能夠理解爲「淺拷貝」)與父進程共享。在極端狀況下,一個進程能夠clone出一個線程。另外還有一個vfork系統調用,也不帶參數,除了task_struct結構和系統空間堆棧外,全部的資源都是經過指針拷貝與父進程共享,因此vfork複製出來的是線程而不是進程。設計
爲何要有複製這一步?所謂複製(「深拷貝」)只是進程基本資源的複製,如task_struct結構、系統空間堆棧、頁面表等等;除此之外,其餘的資源好比父進程的代碼及全局變量並不須要複製,而是以只讀的方式共享,僅在須要寫時才經過copy on write手段爲所涉及的頁面創建一個新的副本(這就意味着,當子進程改變一個全局變量時,並不會影響該全局變量在父進程中的值,由於子進程修改的是屬於它的副本)。因此複製的代價是很低的,可是經過複製繼承下來的資源每每對子進程頗有用。好比在client/server系統中的server一方的實現中,fork或clone經常是最天然、最有效的手段。指針
第二步是目標程序的執行。大多數狀況下,複製出一個子進程後有新的目標程序等待子進程去執行(但也不必定,好比子進程共享父進程的代碼段),這種狀況下,複製完成之後,子進程通常要與父進程分道揚鑣走本身的路了。爲此,linux提供了一個系統調用execve,讓一個進程執行一個以文件形式存在的可執行程序映像。既然子進程要走本身的路,那就不要回頭了,因此係統調用execve不會返回,而是「壯士一去兮不復返」。server
那麼這種分兩步走有什麼好處呢?首先,不少場景只需單獨使用fork,其後並不跟execve;其次,分兩步走是的子進程在執行exceve以前有機會修改本身的屬性(好比IO重定向、用戶ID,信號安排等)。繼承
建立了子進程後,父進程該何去何從?通常有三個選擇。第一是繼續走本身的路,與子進程分道揚鑣。只是若是子進程先於父進程去世,則由內核給父進程發一個報喪的信號(SIGCHILD)。第二個是停下了,也就是進入睡眠狀態,等待子進程完成本身的使命而最終去世,而後父進程再運行。linux爲此提供了兩個系統調用,wait4(等待某個特定的子進程去世)和wait3(等待任何一個子進程去世)。第三個選擇是「自行退出歷史舞臺」,結束本身的生命。linux爲此提供了一個系統調用exit,(不過代碼中通常不多直接調用exit,而是經過return語句從main函數中返回,gcc在編譯和連接時會自動加上exit,詳見APUE第7章)。進程
須要說明一點:父進程經過fork系統調用複製出一個子進程後,因爲子進程有了本身的task_struct結構,因此子進程就會被調度器看到並可能被調度執行,且與父進程具備相同的返回地址(即返回到調用fork的下一條指令),因此當父進程和子進程受調度繼續運行而從內核空間返回到用戶空間時都返回到同一點上,但兩者的返回值不同,父進程的返回值爲子進程的pid,而子進程的返回值爲0。所以,代碼中能夠根據返回值來判斷父子進程。資源