天天看點

system()、exec()、fork()三個與程序有關的函數的比較

system()函數可以啟動一個新的程序。

int system (const char *string )

這個函數的效果就相當于執行sh –c string。

一般來說,使用system函數遠非啟動其他程序的理想手段,因為它必須用一個shell來啟動需要的程式。這樣對shell的安裝情況,以及shell的版本依賴性很大。

system函數的特點:

 建立獨立程序,擁有獨立的代碼空間,記憶體空間

等待新的程序執行完畢,system才傳回。(阻塞)

exec函數可以用來替換程序映像。執行exec系列函數後,原來的程序将不再執行,新的程序的PID、PPID和nice值與原先的完全一樣。其實執行exec系列函數所發生的一切就是,運作中的程式開始執行exec調用中指定的新的可執行檔案中的代碼。

exec函數的特點:

當程序調用一種exec函數時,源程序完全由新程式代換,而新程式則從其main函數開始執行。因為調用exec并不建立新程序,是以前後的程序ID并未改變。exec隻是用另一個新程式替換了目前程序的正文、資料、堆和棧段。特别地,在原程序中已經打開的檔案描述符,在新程序中仍将保持打開,除非它們的“執行時關閉标志”(close on exec flag)被置位。任何在原程序中已打開的目錄流都将在新程序中被關閉。

複制程序映像(fork函數)

頭檔案

#include<unistd.h>  

#include<sys/types.h>  

函數原型

pid_t fork( void);  

傳回值:

若成功調用一次則傳回兩個值,子程序傳回0,父程序傳回子程序ID;否則,出錯傳回-1

關于fork函數的作用,《Linux程式設計》中是這樣解釋的:

我們可以通過調用fork建立一個新程序。這個系統調用複制目前程序,在程序表中建立一個新的表項,新表項中的許多屬性與目前程序是相同的。新程序幾乎與元程序一模一樣,執行的代碼也完全相同,但是新程序有自己的資料空間、環境和檔案描述符。

這個解釋其實過于籠統,很多細節問題都沒有說。下面就簡單說一下調用fork時發生的一些細節問題。或者叫fork函數的特點:

首先,現在的UNIX系統和Linux系統都采用寫時複制技術(COW:Copy On Write)。使用這種技術,當調用fork函數時,新的程序隻是擁有自己的虛拟記憶體空間,而沒有自己的實體記憶體空間。新程序共享源程序的實體記憶體空間。而且新記憶體的虛拟記憶體空間幾乎就是源程序虛拟記憶體空間的一個複制。

我們知道,程序空間可以簡單地分為程式段(正文段)、資料段、堆和棧四部分(簡單這樣了解)。采用寫時複制的fork函數,當執行完fork後的一定時間内,新的程序(子程序)和源程序的程序空間關系如下圖:

system()、exec()、fork()三個與程式有關的函數的比較

如上圖,fork執行時,Linux核心會為新的程序P2建立一個虛拟記憶體空間,而新的虛拟空間中的内容是對P1虛拟記憶體空間中的内容的一個拷貝。而P2和P1共享原來P1的實體記憶體空間。

當然要了解“寫時複制”中,上圖中所展示的狀态是會發生變化的。什麼時候回發生變化呢?就是,父子兩個程序中任意一個程序對資料段、棧區、堆區進行寫操作時,上圖中的狀态就會被打破,這個時候就會發生實體記憶體的複制,這也就是叫“寫時複制”的原因。發生的狀态轉變如下:

system()、exec()、fork()三個與程式有關的函數的比較

我們發現,P2有了屬于自己的實體記憶體空間。值得注意的是,各個段之間發生的變化應當是獨立的,也就是說,如果隻有資料段發生了寫操作那麼就隻有資料段進行寫時複制。而堆、棧區域依然是父子程序共享。還有一個需要注意的是,正文段(程式段)不會發生寫時複制,這是因為通常情況下程式段是隻讀的。子程序和父程序從fork之後,基本上就是獨立運作,互不影響了。

此外需要特别注意的是,父子程序的檔案描述符表也會發生寫時複制。

還有一個叫vfork的函數,這個做法更加火爆,核心連子程序的虛拟位址空間結構也不建立了,直接共享了父程序的虛拟空間,當然了,這種做法就順水推舟的共享了父程序的實體空間

system()、exec()、fork()三個與程式有關的函數的比較

首先比較一下exec()函數和fork()。這兩個函數一個是換藥不換湯(execl函數),另一個是換湯不換藥(fork函數)。那麼什麼是湯、什麼又是藥呢?我們知道程序是個很複雜的東西。從task_struct 結構體的代碼量上就可以看出來(task_struct是Linux核心中用來描述程序的一個結構體,這個結構體光代碼貌似就有好幾屏)。我們可以把程序的PID、PPID和nice值等看作是湯,而把程序空間(簡單了解就是正文段、資料段、堆、棧等)看作是藥。

exec()函數是換藥不換湯,就是說執行exec函數後,并沒有産生新的程序,也就是湯還是那些湯,程序的PID、PPID和nice值等沒有發生變化。但是exec()函數卻将藥換了,也就是将程序空間換掉了,新的程序空間是為了執行新的程式所準備的,是以新的程序空間與原程序空間并沒有什麼關系。

fork()函數是換湯不換藥,意思是執行fork()函數後,産生了新的程序,新的程序的PID、PPID與原來原來的程序不同,說明父子程序是兩個不同的程序,但是fork并沒有把藥換掉,而是将藥複制了一份給子程序。fork剛執行後的一段時間内,父子程序有着相同的狀态(程序空間中的東西都一樣,因為fork采用“寫時複制”,一開始父子程序共享實體記憶體空間)。但是一旦父子程序中有一個程序試圖修改程序空間,這時父子程序就各自擁有了各自的程序空間,簡單地了解,從這一時刻器,父子程序就是兩個獨立的程序,誰都不會影響誰(實際上還是有一定影響的,在這裡可以忽略),父子程序之間的關聯僅剩下它們共享的代碼段了。

對于system函數,我們可以先看一下它的源代碼:

我們看到system()函數實際上就是先執行了fork函數,然後新産生的子程序立刻執行了exec函數,我們前面說個fork函數換湯不換藥,exec函數換藥不換湯,那麼system函數就是既換湯也換了藥,也就是system函數會産生新程序,這就意味着新程序的PID、PPID等與原程序不同。system也會産生新的程序空間,而且新的程序空間是為新的程式準備的,是以和原程序的程序空間沒有任何關系(不像fork新程序空間是對原程序空間的一個複制)。還要注意的是,system函數代碼中else部分執行了wait函數,這就意味着,原程序會等待子程序執行完畢(阻塞)

最後還要注意的一個問題是關于檔案描述符的。

exec函數執行後,原來打開的檔案描述符依然存在。

fork函數執行後,原來打開的檔案描述符會複制一份到新的程序中,之後兩個程序之間的檔案描述符就相對獨立了。

system函數先執行fork函數,這之後兩個程序的檔案描述符就相對獨立了。之後exec函數并不影響檔案描述符。

繼續閱讀