標籤:unix linux 進程管理 多進程
建立進程的函數fork()、vfork()和execl()
本次內容主要介紹Unix/linux下2個建立進程的函數fork和vfork以及它們的差別。
1.fork函數
(1)函數的格式
#include <unistd.h>
pid_t fork(void);
函數功能:
主要用於以複製正在運行進程的方式來建立新的進程,其中新進程叫做子進程,正在啟動並執行進程叫做父進程。
返回值:
函數調用成功時,父進程返回子進程的PID,子進程返回0,函數調用出錯時,父進程返回-1,子進程沒有被建立。
注意:
此函數的返回值有2個,主要原因是因為在fovk執行結束前,運行到x行 就已經建立出了一個子進程,所以父子進程都會會執行x行到return語句的代碼。為了能找到自己的兒子,所以父進程會返回子進程的pid,子進程能通過getpid()找到父進程,因此子進程返回0即可。
(2)fork建立子進程的工作方式
1)fork函數之前的代碼,父進程執行依次。
2)fork函數之後的代碼,父子進程各自執行一次。
3)fork函數的返回值,父子進程各自返回一次。
(3)fork建立的父子進程之間的關係
1)父進程啟動子進程,父子進程同時啟動,如果子進程先結束,會給父進程發訊號,讓父進程協助子進程回收資源。
2)如果父進程現在結束,子進程變成孤兒進程,子進程會變更父進程(重新指定init進程為新的父進程),init進程也叫做孤兒院。
3)如果子進程先結束,但是父進程由於各種原因並沒有收到子進程發來的訊號,也就是沒有幫子進程回收資源,那麼子進程會變成殭屍進程。
(4)fork函數建立的父子進程之間的記憶體資源關係
進程的記憶體地區劃分:代碼區、全域區、堆區、棧區
子進程是父進程的副本,子進程獲得父進程資料區段和堆棧段(包括I/O流緩衝區)的拷貝,但子進程共用父進程的程式碼片段。 父進程的檔案描述符表也會被複製到子進程中,二者共用同一個檔案表。所以如果在父子進程中,有不需要的描述符的話,最好要close。
------------------------------------------------------------------------------------------------------------------------
2.vfork和execl函數
(1)vforK函數
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
函數功能:
該函數的功能與fork函數類似,返回值和出錯原因等參數fork函數即可,但是所不同的是,該函數建立的子進程不會複製父進程中的記憶體地區,而是直接佔用,導致父進程進入阻塞狀態,直到子進程結束或者調用exec系列函數為止,而子進程的結束應該去調用_exit()函數。
注意:
vfork函數保證建立的子進程先執行。
(2)exec系列函數
int execl(const char *path, const char *arg, ...);
第一個參數:執行的檔案路徑
第二個參數:執行的參數,一般給檔案名稱即可
第三個參數:可變長參數,最後使用NULL結尾
函數功能:
主要用於按照參數指定的路徑和方式區執行檔案。
如:
使用execl函數執行ls -l 命令時:
execl("/bin/ls", "ls", "-l", NULL);
注意:
vfork函數本身沒有太大的實際意思,一般與exec系列函數搭配使用,而vfork函數負責建立子進程,exec系列函數負責執行新的代碼。
fork函數也可以和exec系列函數搭配使用,一般很少使用。
------------------------------------------------------------------------------------------------------------------------
fork與vfork的差別及其應用
從概念上說來,可以將fork()認為對父進程的副本,除了文本區是共用之外,其它都是建立拷貝的。這是早期fork函數的實現,這種做法大大的耗費了記憶體的資源,相比之下vfork()建立的子進程則是共用了父進程的記憶體,效率遠比fork函數高。在fork()採用寫時複製前,vfork()可以說完全取代fork()。
為瞭解決fork函數造成不必要的浪費,核心對父進程的資料區、BBS段、堆區和棧區採用了寫時複製(copy-on-write)技術來處理。這種技術大致可以理解為,父子進程先共用記憶體,如果整個程式結束前,父子進程都沒有對記憶體進行寫操作,那麼子進程就沒必要對父進程的記憶體進行拷貝。最初,核心做了一些設定,令這些段的頁表項指向父進程相同的實體記憶體業,並將這些頁面自身標為唯讀。調用fork()後,核心會捕捉所有父進程或子進程針對這些頁面的修改企圖,並為將要修改的頁面建立拷貝。系統將心的頁面拷貝分配給遭核心捕獲的進程,還會對子進程的相應頁表項做適當調整。從這一刻起,父、子進程可以分別修改各自的頁拷貝,不再相互影響。
相比與fork(),vfork()是為子進程立即執行exec()系列的程式而專門設計的。主要原因如下
1:vfork()無需為子進程複製虛擬記憶體或頁表。相反,子進程共用父進程記憶體,直至其成功執行了exec()或者調用-exit()退出。
2:vfork()建立的子進程在調用exec()或_exit()之前,將暫停執行父進程。
fork與vfork的運用情境
一般fork與vfork、exec配合到進程終止的函數一起使用的,關於進程的函數這裡就不多討論。下面我舉個類比銀行的例子,通俗的對這2個函數的基本使用進行一個大致的描繪。
假設這類比個銀行只有取款與存款這麼兩個功能,要求是程式可以同時執行取款和存款這2個程式,並且能實現多個客戶同時存款和取款。
同時執行存款和取款的話,使用vfork()+exec()函數最適合不過,父進程建立2個子進程,子進程A負責存款業務,子進程B負責取款業務。如此類推,有多少個業務,父進程就使用vfork()+execl()建立多少個子進程。
在存款進程中,A會等待存款訊息,當捕獲了一個存款訊息後,進程A就fovk()一個子進程,子進程就負責處理這個訊息,那麼在存款進程中,父進程負責的是捕獲訊息,具體的處理工作都交給了子進程,這樣就能實現多個進程的並發處理,加快業務處理的效率。
取款進程同上。
總結:fovk()+execl()是建立多個功能不一樣的進程處理不同的業務,fovk則是建立多個功能一樣的進程去處理相同的業務!
(最後關於這個類比銀行的練習代碼,不久再整理出來好了)
【歸納總結】Unix/linux下的進程管理(二):建立進程的函數及其應用、對比