exec函数详解
(1)exec函數說明
fork函數是用于創建一個子進程,該子進程幾乎是父進程的副本,而有時我們希望子進程去執行另外的程序,exec函數族就提供了一個在進程中啟動另一個程序執行的方法。它可以根據指定的文件名或目錄名找到可執行文件,并用它來取代原調用進程的數據段、代碼段和堆棧段,在執行完之后,原調用進程的內容除了進程號外,其他全部被新程序的內容替換了。另外,這里的可執行文件既可以是二進制文件,也可以是Linux下任何可執行腳本文件。
?
(2)在Linux中使用exec函數族主要有以下兩種情況
當進程認為自己不能再為系統和用戶做出任何貢獻時,就可以調用任何exec 函數族讓自己重生。
如果一個進程想執行另一個程序,那么它就可以調用fork函數新建一個進程,然后調用任何一個exec函數使子進程重生。
?
(3)exec函數族語法
實際上,在Linux中并沒有exec函數,而是有6個以exec開頭的函數族,下表列舉了exec函數族的6個成員函數的語法。
所需頭文件 | #include <unistd.h> |
函數說明 | 執行文件 |
函數原型 | int execl(const char *pathname, const char *arg, ...) |
int execv(const char *pathname, char *const argv[]) | |
int execle(const char *pathname, const char *arg, ..., char *const envp[]) | |
int execve(const char *pathname, char *const argv[], char *const envp[]) | |
int execlp(const char *filename, const char *arg, ...) | |
int execvp(const char *filename, char *const argv[]) | |
函數返回值 | 成功:函數不會返回 |
出錯:返回-1,失敗原因記錄在error中 |
這6 個函數在函數名和使用語法的規則上都有細微的區別,下面就可執行文件查找方式、參數表傳遞方式及環境變量這幾個方面進行比較說明。
①??? 查找方式:上表其中前4個函數的查找方式都是完整的文件目錄路徑(pathname),而最后2個函數(也就是以p結尾的兩個函數)可以只給出文件名,系統就會自動從環境變量“$PATH”所指出的路徑中進行查找。
前4個取路徑名做參數,后兩個則取文件名做參數。
當指定filename做參數時:
a. 如果filename中包含/,則將其視為路徑名
b. 否則就按PATH環境變量搜索可執行文件。
②??? 參數傳遞方式:exec函數族的參數傳遞有兩種方式,一種是逐個列舉(l)的方式,而另一種則是將所有參數整體構造成指針數組(v)進行傳遞。
在這里參數傳遞方式是以函數名的第5位字母來區分的,字母為“l”(list)的表示逐個列舉的方式,字母為“v”(vertor)的表示將所有參數整體構造成指針數組傳遞,然后將該數組的首地址當做參數傳給它,數組中的最后一個指針要求是NULL。讀者可以觀察execl、execle、execlp的語法與execv、execve、execvp的區別。
③??? 環境變量:exec函數族使用了系統默認的環境變量,也可以傳入指定的環境變量。這里以“e”(environment)結尾的兩個函數execle、execve就可以在envp[]中指定當前進程所使用的環境變量替換掉該進程繼承的所以環境變量。
?
(3)PATH環境變量說明
PATH環境變量包含了一張目錄表,系統通過PATH環境變量定義的路徑搜索執行碼,PATH環境變量定義時目錄之間需用用“:”分隔,以“.”號表示結束。PATH環境變量定義在用戶的.profile或.bash_profile中,下面是PATH環境變量定義的樣例,此PATH變量指定在“/bin”、“/usr/bin”和當前目錄三個目錄進行搜索執行碼。
PATH=/bin:/usr/bin:.
export $PATH
?
(4)進程中的環境變量說明
??? 在Linux中,Shell進程是所有執行碼的父進程。當一個執行碼執行時,Shell進程會fork子進程然后調用exec函數去執行執行碼。Shell進程堆棧中存放著該用戶下的所有環境變量,使用execl、execv、execlp、execvp函數使執行碼重生時,Shell進程會將所有環境變量復制給生成的新進程;而使用execle、execve時新進程不繼承任何Shell進程的環境變量,而由envp[]數組自行設置環境變量。
?
(5)exec函數族關系
第4位 | 統一為:exec | |
第5位 | l:參數傳遞為逐個列舉方式 | execl、execle、execlp |
v:參數傳遞為構造指針數組方式 | execv、execve、execvp | |
第6位 | e:可傳遞新進程環境變量 | execle、execve |
p:可執行文件查找方式為文件名 | execlp、execvp | |
????? 事實上,這6個函數中真正的系統調用只有execve,其他5個都是庫函數,它們最終都會調用execve這個系統調用,調用關系如下圖12-11所示:
?????????????????
??????????????????????????????????????? 圖12-11 exec函數族關系圖
?
(6)exec調用舉例如下
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);execv("/bin/ps", ps_argv);execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);execve("/bin/ps", ps_argv, ps_envp);execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);execvp("ps", ps_argv);請注意exec函數族形參展開時的前兩個參數,第一個參數是帶路徑的執行碼(execlp、execvp函數第一個參數是無路徑的,系統會根據PATH自動查找然后合成帶路徑的執行碼),第二個是不帶路徑的執行碼,執行碼可以是二進制執行碼和Shell腳本。
?
(7)exec函數族使用注意點
在使用exec函數族時,一定要加上錯誤判斷語句。因為exec很容易執行失敗,其中最常見的原因有:
①??? 找不到文件或路徑,此時errno被設置為ENOENT。
②??? 數組argv和envp忘記用NULL結束,此時errno被設置為EFAULT。
③??? 沒有對應可執行文件的運行權限,此時errno被設置為EACCES。
?
(8)exec后新進程保持原進程以下特征
????? 環境變量(使用了execle、execve函數則不繼承環境變量);
????? 進程ID和父進程ID;
????? 實際用戶ID和實際組ID;
????? 附加組ID;
????? 進程組ID;
????? 會話ID;
????? 控制終端;
????? 當前工作目錄;
????? 根目錄;
????? 文件權限屏蔽字;
????? 文件鎖;
????? 進程信號屏蔽;
????? 未決信號;
????? 資源限制;
????? tms_utime、tms_stime、tms_cutime以及tms_ustime值。
對打開文件的處理與每個描述符的exec關閉標志值有關,進程中每個文件描述符有一個exec關閉標志(FD_CLOEXEC),若此標志設置,則在執行exec時關閉該描述符,否則該描述符仍打開。除非特地用fcntl設置了該標志,否則系統的默認操作是在exec后仍保持這種描述符打開,利用這一點可以實現I/O重定向。
?
(9)execlp函數舉例
execlp.c源代碼如下:
#include <stdio.h> #include <unistd.h> int main() { if(fork()==0){ if(execlp("/usr/bin/env","env",NULL)<0) { perror("execlp error!"); return -1 ; } } return 0 ; }編譯 gcc execlp.c –o execlp。執行 ./execlp,執行結果如下:
HOME=/home/test
DB2DB=test
SHELL=/bin/bash
……
由執行結果看出,execlp函數使執行碼重生時繼承了Shell進程的所有環境變量,其他三個不以e結尾的函數同理。
(10)execle函數舉例
利用函數execle,將環境變量添加到新建的子進程中去。execle.c源代碼如下:
#include <unistd.h> #include <stdio.h> int main() { /*命令參數列表,必須以 NULL 結尾*/ char *envp[]={"PATH=/tmp","USER=sun",NULL}; if(fork()==0){ /*調用 execle 函數,注意這里也要指出 env 的完整路徑*/ if(execle("/usr/bin/env","env",NULL,envp)<0) { perror("execle error!"); return -1 ; } } return 0 ; }
編譯:gcc execle.c –o execle。
執行./execle,執行結果如下:
PATH=/tmp
USER=sun
可見,使用execle和execve可以自己向執行進程傳遞環境變量,但不會繼承Shell進程的環境變量,而其他四個exec函數則繼承Shell進程的所有環境變量。
總結示例exec.c:
/* exec函數族的語法 */ #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<errno.h>int main(int argc, char *argv[]) {/* 字符串指針數組傳遞參數,使用包含v的exec函數參數 */char *arg[] = {"ls","-a",NULL};char *arg1[] = {"env",NULL}; //只用于execve函數char *envp[] = {"NAME=amoscykl","EMAIL=xxxx@xx.com","PATH=/tmp",NULL};char **ptr; //指向環境表// 打印出環境表printf("自定義環境表\n");for (ptr = envp; *ptr != 0; ptr++)printf("%s \n",*ptr);printf("\n");sleep(2);/* 子進程調用execl函數 */if (fork() == 0){//child1printf("1-----execl-----\n");if (execl("/bin/ls","ls","-a",NULL) == -1){perror("execl error!");exit(1);}}sleep(2);/* 子進程調用execv函數 */if (fork() == 0){//child2printf("2-----execv-----\n");if (execv("/bin/ls",arg) == -1){perror("execv error!");exit(1);}}sleep(2);/* 子進程調用execlp函數 */if (fork() == 0){//child3printf("3-----execlp-----\n");if (execlp("ls","ls","-a",NULL) == -1){perror("execlp error!");exit(1);}}sleep(2);/* 子進程調用execvp函數 */if (fork() == 0){//child4printf("4-----execvp-----\n");if (execvp("ls",arg) == -1){perror("execvp error!");exit(1);}}sleep(2);/* 子進程調用execle函數 */if (fork() == 0){//child5printf("5-----execle-----\n");if (execle("/usr/bin/env","env",NULL,envp) == -1) //使用自定義的環境表,并打印出自定義環境變量{perror("execle error!");exit(1);}}sleep(2);/* 子進程調用execve函數 */if (fork() == 0){//child6printf("6-----execve-----\n");if (execve("/usr/bin/env",arg1,envp) == -1) //使用自定義的環境表,并打印出自定義環境變量{perror("execve error!");exit(1);}}sleep(2);printf("over!\n");return 0; }運行結果:
總結
- 上一篇: [zigbee][z-Stack]协议栈
- 下一篇: 序列号生成器