linux 作業系統緊緊依賴程序建立來滿足使用者的需求。例如,只要使用者輸入一條命令,shell 程序就建立乙個新程序,新程序執行 shell 的另乙個拷貝並執行使用者輸入的命令。linux 系統中通過 fork/vfork 系統呼叫來建立新程序。本文將介紹如何使用 fork/vfork 系統呼叫來建立新程序並使用 exec 族函式在新程序中執行任務。
要建立乙個程序,最基本的系統呼叫是 fork:
# include pid_t fork(呼叫 fork 時,系統將建立乙個與當前程序相同的新程序。通常將原有的程序稱為父程序,把新建立的程序稱為子程序。子程序是父程序的乙個拷貝,子程序獲得同父程序相同的資料,但是同父程序使用不同的資料段和堆疊段。子程序從父程序繼承大多數的屬性,但是也修改一些屬性,下表對比了父子程序間的屬性差異:void
);pid_t vfork(
void);
繼承屬性
差異uid,gid,euid,egid
程序 id
程序組 id
父程序 id
session id
子程序執行時間記錄
所開啟檔案及檔案的偏移量
父程序對檔案的鎖定
控制終端
設定使用者 id 和 設定組 id 標記位
根目錄與當前目錄
檔案預設建立的許可權掩碼
可訪問的記憶體區段
環境變數及其它資源分配
下面是乙個常見的演示 fork 工作原理的 demo(筆者的環境為 ubuntu 16.04 desktop):
#include #include把上面的**儲存到檔案 forkdemo.c 檔案中,並執行下面的命令編譯:#include
#include
int main(void
)
if(pid == 0
)
else
return0;
}
$ gcc forkdemo.c -o forkdemo然後執行編譯出來的 forkdemo 程式:
$ ./forkdemofork 函式的特點是 "呼叫一次,返回兩次":在父程序中呼叫一次,在父程序和子程序中各返回一次。在父程序中返回時的返回值為子程序的 pid,而在子程序中返回時的返回值為 0,並且返回後都將執行 fork 函式呼叫之後的語句。如果 fork 函式呼叫失敗,則返回值為 -1。
我們細想會發現,fork 函式的返回值設計還是很高明的。在子程序中 fork 函式返回 0,那麼子程序仍然可以呼叫 getpid 函式得到自己的 pid,也可以呼叫 getppid 函式得到父程序 pid。在父程序中用 getpid 函式可以得到自己的 pid,如果想得到子程序的pid,唯一的辦法就是把 fork 函式的返回值記錄下來。
注意:執行 forkdemo 程式時的輸出是會發生變化的,可能先列印父程序的資訊,也可能先列印子程序的資訊。
vfork 系統呼叫和 fork 系統呼叫的功能基本相同。vfork 系統呼叫建立的程序共享其父程序的記憶體位址空間,但是並不完全複製父程序的資料段,而是和父程序共享其資料段。為了防止父程序重寫子程序需要的資料,父程序會被 vfork 呼叫阻塞,直到子程序退出或執行乙個新的程式。由於呼叫 vfork 函式時父程序被掛起,所以如果我們使用 vfork 函式替換 forkdemo 中的 fork 函式,那麼執行程式時輸出資訊的順序就不會變化了。
使用 vfork 建立的子程序一般會通過 exec 族函式執行新的程式。接下來讓我們先了解下 exec 族函式。
使用 fork/vfork 建立子程序後執行的是和父程序相同的程式(但有可能執行不同的**分支),子程序往往需要呼叫乙個 exec 族函式以執行另外乙個程式。當程序呼叫 exec 族函式時,該程序的使用者空間**和資料完全被新程式替換,從新程式的起始處開始執行。呼叫 exec 族函式並不建立新程序,所以呼叫 exec 族函式前後該程序的 pid 並不改變。
exec 族函式一共有六個:
#include int execl(const函式名字中帶字母 "l" 的表示其引數個數不確定,帶字母 "v" 的表示使用字串陣列指標 ar** 指向引數列表。char *path, const
char *arg, ...);
int execlp(const
char *file, const
char *arg, ...);
int execle(const
char *path, const
char *arg, ..., char *const
envp);
int execv(const
char *path, char *const
ar**);
int execvp(const
char *file, char *const
ar**);
int execve(const
char *path, char *const ar**, char *const envp);
函式名字中含有字母 "p" 的表示可以自動在環境變數 path 指定的路徑中搜尋要執行的程式。
函式名字中含有字母 "e" 的函式比其它函式多乙個引數 envp。該引數是字串陣列指標,用於指定環境變數。呼叫這樣的函式時,可以由使用者自行設定子程序的環境變數,存放在引數 envp 所指向的字串陣列中。
事實上,只有 execve 是真正的系統呼叫,其它五個函式最終都呼叫 execve。這些函式之間的關係如下圖所示(此圖來自網際網路):
exec 族函式的特徵:呼叫 exec 族函式會把新的程式裝載到當前程序中。在呼叫過 exec 族函式後,程序中執行的**就與之前完全不同了,所以 exec 函式呼叫之後的**是不會被執行的。
下面讓我們通過 vfork 和 execve 函式實現在子程序中執行 ls 命令:
#include #include把上面的**儲存到檔案 subprocessdemo.c 檔案中,並執行下面的命令編譯:#include
#include
int main(void
)
else
if(pid==0
) ;
char *envp[ ]=;
if(execve("
/bin/ls
", ar**, envp) < 0
)
//子程序要麼從 ls 命令中退出,要麼從上面的 exit(1) 語句退出
//所以**的執行路徑永遠也走不到這裡,下面的 printf 語句不會被執行
printf("
you should never see this message.");
}else
return0;
}
$ gcc subprocessdemo.c -o subprocessdemo然後執行編譯出來的 subprocessdemo程式:
fork/vfork 函式和 exec 族函式都是 linux 系統中非常重要的概念。本文試圖通過簡單的 demo 來演示這些函式的基本用法,為理解 linux 系統中父程序與子程序的概念提供一些直觀的感受。
參考:
linux c 程式設計一站式學習
《linux 環境下 c 程式設計指南》
《深入理解 linux 核心》
linux c 建立子程序 執行任務
目錄 fork 系統呼叫 vfork 系統呼叫 exec 族函式 在子程序中執行任務 總結 linux 作業系統緊緊依賴程序建立來滿足使用者的需求。例如,只要使用者輸入一條命令,shell 程序就建立乙個新程序,新程序執行 shell 的另乙個拷貝並執行使用者輸入的命令。linux 系統中通過 fo...
Linux 建立子程序執行任務的實現方法
linux 作業系統緊緊依賴程序建立來滿足使用者的需求。例如,只要使用者輸入一條命令,shell 程序就建立乙個新程序,新程序執行 shell 的另乙個拷貝並執行使用者輸入的命令。linux 系統中通過 fork vfork 系統呼叫來建立新程序。本文將介紹如何使用 fork vfork 系統呼叫來...
linux建立子程序
include include include intmain int argc,char ar else if pid 0 else if pid 0 return0 include include include intmain int argc,char ar else if pid 0 el...