歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux中用於進程控制的exec函數族

1、簡介

在Linux中,並不存在exec()函數,exec指的是一組函數,一共有6個,分別是:

#include <unistd.h>

extern char **environ;

int execl(const 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 argv[]);

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

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

詳細定義:

execl(執行文件)

相關函數

fork,execle,execlp,execv,execve,execvp

表頭文件

#include<unistd.h>

定義函數

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

函數說明

execl()用來執行參數path字符串所代表的文件路徑,接下來的參數代表執行該文件時傳遞過去的argv(0)、argv[1]……,最後一個參數必須用空指針(NULL)作結束。

返回值

如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存於errno中。

范例

#include<unistd.h>

main()

{

execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);

}

執行

/*執行/bin/ls -al /etc/passwd */

-rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd

execlp(從PATH 環境變量中查找文件並執行)

相關函數

fork,execl,execle,execv,execve,execvp

表頭文件

#include<unistd.h>

定義函數

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

函數說明

execlp()會從PATH 環境變量所指的目錄中查找符合參數file的文件名,找到後便執行該文件,然後將第二個以後的參數當做該文件的argv[0]、argv[1]……,最後一個參數必須用空指針(NULL)作結束。

返回值

如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。

錯誤代碼

參考execve()。

范例

/* 執行ls -al /etc/passwd execlp()會依PATH 變量中的/bin找到/bin/ls */

#include<unistd.h>

main()

{

execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0);

}

執行

-rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd

 

execv(執行文件)

相關函數

fork,execl,execle,execlp,execve,execvp

表頭文件

#include<unistd.h>

定義函數

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

函數說明

execv()用來執行參數path字符串所代表的文件路徑,與execl()不同的地方在於execve()只需兩個參數,第二個參數利用數組指針來傳遞給執行文件。

返回值

如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。

錯誤代碼

請參考execve()。

范例

/* 執行/bin/ls -al /etc/passwd */

#include<unistd.h>

main()

{

char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char*) }};

execv(“/bin/ls”,argv);

}

執行

-rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd

 

execve(執行文件)

相關函數

fork,execl,execle,execlp,execv,execvp

表頭文件

#include<unistd.h>

定義函數

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

函數說明

execve()用來執行參數filename字符串所代表的文件路徑,第二個參數系利用數組指針來傳遞給執行文件,

argv要傳遞給程序的完整參數列表,包括argv[0],它一般是執行程序的名字;最後一個參數則為傳遞給執行文件的新環境變量數組。

返回值

如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。

錯誤代碼

EACCES

1. 欲執行的文件不具有用戶可執行的權限。

2. 欲執行的文件所屬的文件系統是以noexec 方式掛上。

3.欲執行的文件或script翻譯器非一般文件。

EPERM

1.進程處於被追蹤模式,執行者並不具有root權限,欲執行的文件具有SUID 或SGID 位。

2.欲執行的文件所屬的文件系統是以nosuid方式掛上,欲執行的文件具有SUID 或SGID 位元,但執行者並不具有root權限

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

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

函數名與參數的關系:

細看一下,這6個函數都是以exec開頭(表示屬於exec函數組),前3個函數接著字母l的,後3個接著字母v的,我的理解是l表示list(列舉參數),v表示vector(參數向量表)

。它們的區別在於,execv開頭的函數是以"char *argv[]"(vector)形式傳遞命令行參數,而execl開頭的函數采用了羅列(list)的方式,把參數一個一個列出來,然後以一個NULL表示結束。這裡的NULL的作用和argv數組裡的NULL作用是一樣的。

字母p是指在環境變量PATH的目錄裡去查找要執行的可執行文件。2個以p結尾的函數execlp和execvp,看起來,和execl與execv的差別很小,事實也如此,它們的區別從第一個參數名可以看出:除execlp和execvp之外的4個函數都要求,它們的第1個參數path必須是一個完整的路徑,如"/bin/ls";而execlp和execvp的第1個參數file可以僅僅只是一個文件名,如"ls",這兩個函數可以自動到環境變量PATH指定的目錄裡去查找。

字母e是指給可執行文件指定環境變量。在全部6個函數中,只有execle和execve使用了char *envp[]傳遞環境變量,其它的4個函數都沒有這個參數,這並不意味著它們不傳遞環境變量,這4個函數將把默認的環境變量不做任何修改地傳給被執行的應用程序。而execle和execve用指定的環境變量去替代默認的那些。

返回值

與一般情況不同,exec函數族的函數執行成功後不會返回,因為調用進程的實體,包括代碼段,數據段和堆棧等都已經被新的內容取代,只有進程ID等一些表面上的信息仍保持原樣。調用失敗時,會設置errno並返回-1,然後從原程序的調用點接著往下執行。

與其他系統調用比起來,exec很容易失敗,被執行文件的位置,權限等很多因素都能導致調用失敗。因此,使用exec函數族時,一定要加錯誤判斷語句。最常見的錯誤:

找不到文件或路徑,此時errno被設置為ENOENT;

數組argv和envp忘記用NULL結束,此時errno被設置為EFAULT;

沒有對要執行文件的運行權限,此時errno被設置為EACCES。

2、應用

如果一個進程想執行另一個程序,它就可以fork或vfork出一個新進程,然後調用任何一個exec函數。

為此,Linux還專門對fork作了優化:通常fork會將調用進程的所有內容原封不動的拷貝到新產生的子進程中去,這些拷貝的動作很消耗時間,而如果fork完之後我們馬上就調用exec,那這些辛辛苦苦拷貝來的東西就會被立刻抹掉,這看起來非常不劃算,於是人們設計了一種"寫時復制(copy-on-write)"技術,使得fork結束後並不立刻復制父進程的內容到子進程,而是到了真正使用時才復制,這樣如果下一條語句是exec,它就不會作無用功了。其實"寫時復制"還是有復制,進程的mm結構、頁表都還是被復制了("寫時復制"也必須由這些信息來支撐。否則內核捕捉到CPU訪存異常,怎麼區分 這是“寫時復制”引起的,還是真正的越權訪問呢?)。

而vfork就把事情做絕了,所有有關於內存的東西都不復制了,父子進程的內存是完全共享的。但是這樣一來又有問題了,雖然用戶程序可以設計很多方法來避免父子進程間的訪存沖突。但是關鍵的一點,父子進程共用著棧,這可不由用戶程序控制的。一個進程進行了關於函數調用或返回的操作,則另一個進程的調用棧 (實際上就是同一個棧)也被影響了。這樣的程序沒法運行下去。所以,vfork有個限制,子進程生成後,父進程在vfork中被內核掛起,直到子進程有了自己的內存空間(exec**)或退出(_exit)。並且, 在此之前,子進程不能從調用vfork的函數中返回(同時,不能修改棧上變量、不能繼續調用除_exit或exec系列之外的函數,否則父進程的數據可能 被改寫)。

盡管限制很多,vfork後馬上exec效率會比fork高不少。

/* exec.c */
#include <unistd.h>
void main(void)
{
    char *envp[] = {"PATH=/tmp", "USER=lingdxuyan", "STATUS=testing", NULL};
    char *argv_execv[] = {"echo", "excuted by execv", NULL};
    char *argv_execvp[] = {"echo", "executed by execvp", NULL};
    char *argv_execve[] = {"env", NULL};
    
    if (fork() == 0)
        if (execl("/bin/echo", "echo", "executed by execl", NULL) < 0)
            perror("Err on execl");
    
    if (fork() == 0)
        if (execlp("echo", "echo", "executed by execlp", NULL) < 0)
            perror("Err on execlp");
    
    if (fork() == 0)
        if (execle("/usr/bin/env", "env", NULL, envp) < 0)
            perror("Err on execle");
    
    if (fork() == 0)
        if (execv("/bin/echo", argv_execv) < 0)
            perror("Err on execv");
    
    if (fork() == 0)
        if (execvp("echo", argv_execvp) < 0)
            perror("Err on execvp");
    
    if (fork() == 0)
        if (execve("/usr/bin/env", argv_execve, envp) < 0)
            perror("Err on execve");
}

由於各個子進程執行的順序無法控制,所以有可能出現一個比較混亂的輸出--各子進程打印的結果交雜在一起,而不是嚴格按照程序中列出的次序。若將程序中fork都改為vfork,則各個exec執行的程序將按序執行。

Copyright © Linux教程網 All Rights Reserved