說程序之前要先說一下程式,相信大家都知道什麼是程式,程式就是一個可執行檔案,是一堆指令的集合。相對而言程式是靜态的。而運作起來的程式就是程序,是動态的,是程式執行的過程。程式可以運作多次比如QQ可以啟動多個,但是每一個都會在記憶體中有獨立的隔離空間用于裝載程式代碼和資料。
我們通過ps指令可以檢視Linux系統中目前運作的程序
<code>ps</code> <code>-aux</code>
<a href="http://s5.51cto.com/wyfs02/M02/89/B6/wKiom1gaosLw7WIXAACiReMMKes324.jpg-wh_500x0-wm_3-wmp_4-s_4231471388.jpg" target="_blank"></a>
這裡面每一行都是一個程序,PID是程序号,後面的COMMAND這是具體程序或者說是程式名稱。第一行永遠都是PID 1的系統init程序,這個是開機核心建立的,它會一直運作直到關機,在它産生的初期是在核心态運作,然後通過系統調用,執行/sbin/init程式,從核心态切換到使用者态。以後所有的使用者程序都是由這個程序派生出來,它是所有使用者程序的父程序,也就是說Linux核心并不直接建立使用者程序。
那使用者運作程式是怎麼變成程序的呢?其實都是init程序通過調用fork函數複制一個自己,然後去exec最終要執行的程式。
<code>top</code> <code>#預設按照程序來檢視</code>
<code>top</code> <code>-H </code><code>#按照線程來檢視</code>
<code>top</code> <code>-H -p PID </code><code>#檢視程序中跑了多少線程,其實也就是這個程序派生出來的線程</code>
關于fork:
fork的作用就是複制一個與自己一樣的程序,新程序的變量、參數等都和原來的一樣,但是它是一個全新的程序,并且作為原來程序的子程序。
可是通常情況下複制了自己以後就馬上執行exec函數集合,調用可執行程式來取代調用該執行程式的程序内容,這就會導緻原來複制的資料都白費了。是以在fork複制的時候采用了寫時複制技術,也就是新的子程序和父程序都有獨立的虛拟記憶體位址,不過這個虛拟位址都指向相同的實體位址,這就保證了它們有獨立的邏輯空間,如果子程序需要修改資料的時候,在為其配置設定獨立的實體空間,然後在把資料複制過去。但是要注意它并不是所有的東西都是用到才複制,比如虛拟位址空間結構(mm結構)、父程序的頁表資訊都是實實在在複制過去的,因為任何程序都是task_struct結構的執行個體。
是以fork就是複制一個和自己一樣的程序并作為自己的子程序存在,然後由子程序去調用真正需要執行的程式。這就要用到exec函數集合。
還有一個叫做vfork,隻要是跟記憶體有關的東西都不複制了,父子程序記憶體完全共享。為了避免共同操作同一個棧,當子程序生成以後,父程序就被挂起,直到子程序調用了exec函數并有了自己獨立的空間或者子程序退出。如果使用vfork然後馬上執行exec的話效率會比用fork要高。和fork相比,vfork節省的最大開銷就是對頁表的拷貝。
說明:程序都是task_struct這個資料結構的執行個體,這個也被稱為程序描述符它記錄了程序的上下文,其中有一個叫做記憶體描述符的資料結構(mm_struct)它描述了程序位址空間的所有資訊,它包括代碼段、資料段等。每個程序都有自己獨立的mm_struct。即使是fork一個程序,子程序也有獨立的task_struct并且有獨立的mm_struct,隻是它的虛拟位址空間映射到了父程序的實體位址空間而已,如果是vfork則完全不用,父子使用相同的虛拟位址空間,當然也就共享同樣的實體位址空間。
fork()函數的傳回值說明:
傳回值
說明
PID值
說明目前在父程序中
說明目前在子程序中
負值
出現錯誤
關于exec函數集合:
exec函數集合的作用就是根據指定的檔案名找到可執行檔案(使用系統環境變量或者接收一個傳遞過來的環境變量),并用這個可執行檔案取代調用程序(調用程序就是調用該exec函數集的程序,了解為上面提到的子程序)的内容。
exec函數執行成功後不會傳回資訊,因為調用程序(子程序)的實體包括代碼、資料和堆棧都是存在的,隻是被新的可執行程式所取代,隻有程序ID還是和原來一樣(子程序和父程序的ID可是不同的,fork一個程序就會為其配置設定一個獨立的ID号),你可以了解為把一個程式裝入到子程序中,是以不能算是全新的建立;但是如果執行失敗會傳回-1.
exec函數集合,如下:
arg為清單參數
int execl(const char *path, const char *arg, ...); path是可執行程式的完整路徑
int execlp(const char *file, const char *arg, ...); file是可執行程式的檔案名,無論是path還是file都會自動使用系統目前環境變量
int execle(const char *path, const char *arg, ..., char * const envp[]); envp是接收自定義的環境變量來找可執行檔案
arg為數組參數
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]); envp是接收自定義的環境變量來找可執行檔案
上面隻有execve是真正會被執行的,其他都是經過了不同封裝因為傳遞的參數會有不同,但是他們最終也是要執行execve的。
再說回程序,當子程序退出時,會通知父程序,讓父程序來清理自己的記憶體空間,并在核心裡留下自己的退出資訊,如果退出正常完成該code就是0,如果不正常就是大于0的整數。父程序調用wait函數清理子程序使用的記憶體空間,并且可以擷取到子程序的退出資訊。如果子程序沒有退出,而父程序退出了呢?那麼子程序就會被init程序所接管,成為子程序的新父程序,而之前退出的父程序是init程序的子程序,是以init就會調用wait函數去清理其使用的記憶體然後擷取退出資訊。
<a href="http://s4.51cto.com/wyfs02/M01/89/B8/wKioL1ga3azhKqwjAACJ_mxkizI407.jpg-wh_500x0-wm_3-wmp_4-s_3415067929.jpg" target="_blank"></a>
從上圖我們可以看到程序的樹形結構init在最頂層。
Linux中到底有沒有線程概念呢?如果你了解的線程是Windows平台下的那種線程的話,那在Linux中則沒有。确切的說Linux有的就是程序,而沒有真正的線程,它的線程其實就是一通特殊的程序而已。fork出來的程序你也可以說它是線程,不過跟父程序比,它是更加輕量級的。那Linux中的線程庫是幹嘛的呢?那個隻是用來模拟線程操作而已。
本文轉自linuxjavachen 51CTO部落格,原文連結:http://blog.51cto.com/littledevil/1868967,如需轉載請自行聯系原作者