天天看點

Linux程序控制函數之exec()函數的學習

           轉載位址:http://www.linuxidc.com/Linux/2011-02/32125p6.htm,   紅色部分, 是我自己的注解。

     當我們看恐怖片時,經常會有這樣的場景:當一個人被鬼上身後,這個人的身體表面上還和以前一樣,但是他的靈魂和思想已經被這個鬼占有了,是以它會控制這個人做他自己想做的事情--那麼在程序中也有這樣的情景。那麼是如何實作的呢?現在我們來學習exec()函數族。這段很形象。

一.exec()函數族

1. 首先我們在終端輸入指令:man exec 可以看到函數的原型:

#include <unistd.h>

int execl(const char *path, const char *arg, ...);

int execv(const char *path, char *const argv[]);

int execle(const char *path, const char *arg,..., char * const envp[]);

int execve(const char*pathname, const char *argv[], char *const envp[]);

int execlp(const char *file, const char *arg, ...);

int execvp(const char *file, char *const argv[]);

這些函數之間的

第一個差別是前4個取路徑名做參數,後兩個則取檔案名做參數。

當指定filename做參數時:

a. 如果filename中包含/,則将其視為路徑名

b. 否則就按PATH環境變量搜尋可執行檔案。

PATH=/bin:/usr/bin:/user/local/bin:.

最後的路徑字首表示目前目錄。零字首表示目前目錄(在name=value開始處可用:表示,在中間用::表示,在行尾用:表示)

第二個差別 與參數表的傳遞有關(l表示list,v表示矢量vector)。函數execl、execlp和execle要求将新程式的每個指令行參數都說明為一個單獨的參數,這中參數表以空指針結尾。而execv、execve和execvp則要先構造一個指向各參數的指針數組,然後将該數組的位址作為這三個函數的位址。

第三個差別 與向新程式傳遞環境表相關。函數execve和execle可以傳遞一個指向環境字元串指針數組的指針。

Tiger-John總結: 

1>.其中隻有execve是真正意義上的系統調用,其它都是在此基礎上經過包裝的庫函數。

2>.exec函數族的作用是根據指定的檔案名找到可執行檔案,并用它來取代調用程序的内容,換句話說,就是在調用程序内部執行一個可執行檔案。這裡的可執行檔案既可以是二進制檔案,也可以是任何Linux下可執行的腳本檔案。

但是若為Shell腳本時必須遵守以下的格式開頭:

第一行必須為: #!interpretername[arg]。其中interpretername可以時 shell或其他解釋器。例如,/bin/sh或/usr/bin/perl,arg是傳遞個解釋器的參數。

3>記憶方法:

l : 表示使用參數清單(list)

e:表示使用新的環境變量,不從目前繼承

p: 表示使用檔案名,并從PATH環境進行搜尋

4>exec()函數族成功後是不會傳回值的,因為程序的執行映像已經被替換,沒有接收傳回值的地方了。但是若有一個錯誤的事件,将會傳回-1.這些錯誤通常是有檔案名或參數錯誤引起的。

2.exec()函數的功能:

1>exec()函數調用并沒有生成新程序,一個程序一旦調用exec函數,它本省就“死亡了”(我備注, 其實沒有死, 隻是像死了似的)--就好比被鬼上身一樣,身體還是你的,但靈魂和思想已經被替換了 --系統把代碼段替換成新的程式的代碼,廢棄原有的資料段和堆棧段,并為新程式配置設定新的資料段與堆棧段,唯一保留的就是程序ID。也就是說,對系統而言,還是同一個程序,不過執行的已經是另外一個程式了。

2>執行exec()函數後的程序除了保持了原來的程序ID,父程序ID,實際使用者ID和實際組ID之外,程序還保持了其他許多原有特征,主要有

a.目前工作目錄

b.根目錄

c.建立檔案時使用的屏蔽字

d.程序信号屏蔽字。

e. 未決警告

f.和程序相關的使用處理器的時間

g.控制終端

h.檔案鎖

Tiger-John說明:

1>.此處要厘清隻有fork()或vfork()函數才能建立一個新程序,而exec()函數時不能建立程序的。

2>.是以在使用exec()函數之前,先要使用fork()或vfork()建立子程序後,子程序調用exec()函數來執行另外一個程式。

3.exec()函數族的具體實作

1>當程序調用一種exec()函數時,該程序執行的程式完全替換為新程式,而新程式則從main函數開始執 行 。因為調用exec()并不建立新程序,是以前後的程序ID不變。函數exec()隻是用一個全新的程式替換目前程序的正文、資料、堆和棧段。

2>無論是哪個exec()函數,都是将可執行程式的路徑,指令行參數和環境變量3個參數傳遞個可執行程式的main()函數 。

3>具體介紹exec()函數族是如何main()函數需要的參數傳遞個它的。

a.execv()函數:execv()函數是通過路徑名方式調用可執行檔案作為新的程序映像。它的argv參數用來提供給main()函數的argv參數。argv參數是一個以NULL結尾的字元串數組

b.execve()函數:參數pathname時将要執行的程式的路徑名,參數argv,envp 與main()函數的argv,envp對應 。

c.execl()函數:次函數與execv函數用法類似。隻是在傳遞argv 參數的時候,每個指令行參數都聲明為一個單獨的參數(參數中使用“......"說明參數的個數是不确定的),要注意的是這些參數要以一個空指針作為結束。

d.execle()函數:該函數與execl函數用法類似,隻是要顯示指定環境變量。環境變量位于指令行參數最後一個參數的後面,也就是位于空指針之後。

e.execvp函數:該函數和execv函數用法類似,不同的是參數filename。該參數 如果包含/,則将其視為路徑名,否則就按PATH環境變量搜尋可執行檔案。

f.execlp()函數:該函數于execl函數類似,它們的差別和execvp與execv的差別一樣。

----------------------------------------------

通過以上學習,我們來編個程式來體驗下它的執行過程

4.函數執行個體

exec.c

  1 #include<stdio.h>

  2 #include<sys/types.h>

  3 #include<unistd.h>

  4 #include<stdlib.h>

  5 int main(int argc, char *argv[], char ** environ)   // 現在, main用兩個參數, 更普遍

  6 {

  7         pid_t pid;

  8         int status;

  9         printf("Exec example!\n");

 10         pid = fork();

 11         if(pid < 0){

 12                 perror("Process creation failed\n");

 13                 exit(1);

 14         }

 15         else if(0 == pid){

 16                 printf("child process is running\n");

 17                 printf("My pid = %d ,parentpid = %d\n",getpid(),getpid());  //後面這個應該是getppid

 18                 printf("uid = %d,gid = %d\n",getuid(),getgid());

 19                 execve("processimage",argv,environ);

 20                 printf("process never go to here!\n");  // 這一句不會被執行到

 21                 exit(0);

 22         }

 23         else {

 24                 printf("Parent process is runnig\n");

 25         }

 26         wait(&status);

 27         exit(0);

 28 }

processimage.c

 1 #include<stdio.h>

  2 #include<sys/types.h>

  3 #include<unistd.h>

  4 

  5 int main(int argc,char * argv[],char ** environ)   // 現在, main用兩個參數, 更普遍

  6 {

  7         int i;

  8 

  9         printf("I am a process image!\n");

 10         printf("My pid =%d,parentpid = %d\n",getpid(),getppid());

 11         printf("uid = %d,gid = %d\n",getuid(),getpid()); // 後面這個應該是getgid

 12 

 13         for(i = 0;i<argc;i++){

 14                 printf("argv[%d]:%s\n",i,argv[i]);

 15         }

 16        // 不要用什麼預設模式, 還是寫個return 0;吧

 17 }

函數經過編譯:

think@Ubuntu:~/work/process_thread/exec1$ gcc processimage.c -o processimage

[email protected]:~/work/process_thread/exec1$ gcc exec.c -o exec

[email protected]:~/work/process_thread/exec1$ ./exec test exec

函數執行結果:

Exec example!

Parent process is runnig

child process is running

My pid = 5949 ,parentpid = 5949   // 原作者把程式寫錯了,結果導緻後者是5949

uid = 1000,gid = 1000

I am a process image!

My pid =5949,parentpid = 5948

uid = 1000,gid = 5949               // 原作者把程式寫錯了,結果導緻後者是5949

argv[0]:./exec

argv[1]:test 

argv[2]:exec 

Tiger-John說明:

1.通過上面程式的執行,我們可以看到執行新程式的程序保持了原來程序的程序ID,父程序ID,實際使用者ID和實際組ID。

2. 當調用execve()函數後,原來的子程序的映像被替代,不再執行。

繼續閱讀