1. exec函數說明
fork()函數通過系統調用創建一個與原來進程(父進程)幾乎完全相同的進程(子進程是父進程的副本,它將獲得父進程數據空間、堆、棧等資源的副本。注意,子進程持有的是上述存儲空間的“副本”,這意味着父子進程不共享這些存儲空間。linux將複製父進程的地址空間內容給子進程,因此,子進程由了獨立的地址空間。),也就是這兩個進程做完全相同的事。
在fork後的子進程中使用exec函數族,可以裝入和運行其它程序(子進程替換原有進程,和父進程做不同的事)。
exec函數族可以根據指定的文件名或目錄名找到可執行文件,並用它來取代原調用進程的數據段、代碼段和堆棧段。在執行完後,原調用進程的內容除了進程號外,其它全部被新程序的內容替換了。另外,這裏的可執行文件既可以是二進制文件,也可以是Linux下任何可執行腳本文件。
2.在Linux中使用exec函數族主要有一下兩種情況
當進程認爲自己不能再爲系統和用戶做出任何貢獻時,就可以調用任何exec函數族讓自己重生;
如果一個進程想執行另外一個程序,那麼它就可以調用fork函數新建一個進程,然後調用任何一個exec函數使子進程重生;
3.exec函數族語法
實際上,在Linux中並沒有exec函數,而是有6個以exec開頭的函數族,下表列舉了exec函數族的6個成員函數的語法。
這6個函數在函數名和使用語法的規則上都有細微的區別,下面就可執行文件查找方式、參數傳遞方式及環境變量這幾個方面進行比較說明。
查找方式:上表中前4個函數的查找方式都是完整的文件目錄路徑(即絕對路徑),而最後兩個函數(也就是以p結尾的兩個函數)可以只給出文件名,系統就會自動從環境變量“$PATH”所指出的路徑中進行查找。
參數傳遞方式:有兩種方式,一種是逐個列舉的方式,另一種是將所有參數整體構造成一個指針數組進行傳遞。(在這裏,字母“l”表示逐個列舉的方式,字母“v”表示將所有參數整體構造成指針數組進行傳遞,然後將該數組的首地址當做參數傳遞給它,數組中的最後一個指針要求時NULL)
環境變量:exec函數族使用了系統默認的環境變量,也可以傳入指定的環境變量。這裏以“e”結尾的兩個函數就可以在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函數去執行執行碼。Sehll進程堆棧中存放着該用戶下的所有環境變量,使用不帶“e”的4個函數使執行碼重生時,Shell進程會將所有環境變量複製給生成的新進程;而使用帶“e”的兩個函數時新進程不繼承任何Shell進程的環境變量,而由envp[]數組自行設置環境變量。
5.exec函數族關係
事實上,這6個函數中真正的系統調用只有execve,其他5個都是庫函數,它們最終都會調用execve這個系統調用,調用關係如下圖所示:
6.exec函數族調用舉例如下
char *const ps_argv[] = {"ps", "-o", "pid, ppid, 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.各個函數使用舉例
#ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <string.h>
9 #include <errno.h>
10
11 int main(int argc, char *argv[])
12 {
13 //以NULL結尾的字符串數組的指針,適合包含v的exec函數參數
14 char *arg[] = {"ls", "-a", NULL};
15
16 /**
17 * 創建子進程並調用函數execl
18 * execl 中希望接收以逗號分隔的參數列表,並以NULL指針爲結束標誌
19 */
20 if( fork() == 0 )
21 {
22 // in clild
23 printf( "1------------execl------------\n" );
24 if( execl( "/bin/ls", "ls","-a", NULL ) == -1 )
25 {
26 perror( "execl error " );
27 exit(1);
28 }
29 }
30
31 /**
32 *創建子進程並調用函數execv
33 *execv中希望接收一個以NULL結尾的字符串數組的指針
34 */
35 if( fork() == 0 )
36 {
37 // in child
38 printf("2------------execv------------\n");
39 if( execv( "/bin/ls",arg) < 0)
40 {
41 perror("execv error ");
42 exit(1);
43 }
44 }
45
46 /**
47 *創建子進程並調用 execlp
48 *execlp中
49 *l希望接收以逗號分隔的參數列表,列表以NULL指針作爲結束標誌
50 *p是一個以NULL結尾的字符串數組指針,函數可以DOS的PATH變量查找子程序文件
51 */
52 if( fork() == 0 )
53 {
54 // in clhild
55 printf("3------------execlp------------\n");
56 if( execlp( "ls", "ls", "-a", NULL ) < 0 )
57 {
58 perror( "execlp error " );
59 exit(1);
60 }
61 }
62
63 /**
64 *創建子里程並調用execvp
65 *v 望接收到一個以NULL結尾的字符串數組的指針
66 *p 是一個以NULL結尾的字符串數組指針,函數可以DOS的PATH變量查找子程序文件
67 */
68 if( fork() == 0 )
69 {
70 printf("4------------execvp------------\n");
71 if( execvp( "ls", arg ) < 0 )
72 {
73 perror( "execvp error " );
74 exit( 1 );
75 }
76 }
77
78 /**
79 *創建子進程並調用execle
80 *l 希望接收以逗號分隔的參數列表,列表以NULL指針作爲結束標誌
81 *e 函數傳遞指定參數envp,允許改變子進程的環境,無後綴e時,子進程使用當前程序的環境
82 */
83 if( fork() == 0 )
84 {
85 printf("5------------execle------------\n");
86 if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 )
87 {
88 perror("execle error ");
89 exit(1);
90 }
91 }
92
93 /**
94 *創建子進程並調用execve
95 * v 希望接收到一個以NULL結尾的字符串數組的指針
96 * e 函數傳遞指定參數envp,允許改變子進程的環境,無後綴e時,子進程使用當前程序的環境
97 */
98 if( fork() == 0 )
99 {
100 printf("6------------execve-----------\n");
101 if( execve( "/bin/ls", arg, NULL ) == 0)
102 {
103 perror("execve error ");
104 exit(1);
105 }
106 }
107 return EXIT_SUCCESS;
108 }
運行結果(linux):
1------------execl------------
. .. .deps exec exec.o .libs Makefile
2------------execv------------
. .. .deps exec exec.o .libs Makefile
3------------execlp------------
. .. .deps exec exec.o .libs Makefile
4------------execvp------------
. .. .deps exec exec.o .libs Makefile
5------------execle------------
. .. .deps .libs Makefile exec exec.o
6------------execve-----------
. .. .deps .libs Makefile exec exec.o
————————————————
版權聲明:本文爲CSDN博主「零下10度C_zjw」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zjwson/article/details/53337212