exec()函式

2puT發表於2016-07-07

(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 *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 *path, char *const argv[], char *const envp[])

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

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

函式返回值

成功:函式不會返回

出錯:返回-1,失敗原因記錄在error中

這6 個函式在函式名和使用語法的規則上都有細微的區別,下面就可執行檔案查詢方式、參數列傳遞方式及環境變數這幾個方面進行比較說明。

①    查詢方式:上表其中前4個函式的查詢方式都是完整的檔案目錄路徑,而最後2個函式(也就是以p結尾的兩個函式)可以只給出檔名,系統就會自動從環境變數“$PATH”所指出的路徑中進行查詢。

②    引數傳遞方式:exec函式族的引數傳遞有兩種方式,一種是逐個列舉的方式,而另一種則是將所有引數整體構造成指標陣列進行傳遞。

在這裡引數傳遞方式是以函式名的第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程式的所有環境變數。

             

                摘錄自《深入淺出Linux工具與程式設計》

相關文章