1. 程式人生 > >Linux 多工程式設計——程序替換:exec 函式族

Linux 多工程式設計——程序替換:exec 函式族

在 Windows 平臺下,我們可以通過雙擊執行可執行程式,讓這個可執行程式成為一個程序;而在 Linux 平臺,我們可以通過 ./ 執行,讓一個可執行程式成為一個程序。

但是,如果我們本來就執行著一個程式(程序),我們如何在這個程序內部啟動一個外部程式,由核心將這個外部程式讀入記憶體,使其執行起來成為一個程序呢?這裡我們通過 exec 函式族實現。

exec 函式族,顧名思義,就是一簇函式,在 Linux 中,並不存在 exec() 函式,exec 指的是一組函式,一共有 6 個:


#include <unistd.h>
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[]);


其中只有 execve() 是真正意義上的系統呼叫,其它都是在此基礎上經過包裝的庫函式。


exec 函式族提供了六種在程序中啟動另一個程式的方法。exec 函式族的作用是根據指定的檔名或目錄名找到可執行檔案,並用它來取代呼叫程序的內容,換句話說,就是在呼叫程序內部執行一個可執行檔案。

程序呼叫一種 exec 函式時,該程序完全由新程式替換,而新程式則從其 main 函式開始執行。因為呼叫 exec 並不建立新程序,所以前後的程序 ID (當然還有父程序號、程序組號、當前工作目錄……)並未改變。exec 只是用另一個新程式替換了當前程序的正文、資料、堆和棧段(程序替換)。

exec 函式族的 6 個函式看起來似乎很複雜,但實際上無論是作用還是用法都非常相似,只有很微小的差別。


l(list):引數地址列表,以空指標結尾。

v(vector):存有各引數地址的指標陣列的地址。

p(path):按 PATH 環境變數指定的目錄搜尋可執行檔案。

e(environment):存有環境變數字串地址的指標陣列的地址。

exec 函式族裝入並執行可執行程式 path/file,並將引數 arg0 ( arg1, arg2, argv[], envp[] ) 傳遞給此程式。

exec 函式族與一般的函式不同,exec 函式族中的函式執行成功後不會返回,而且,exec 函式族下面的程式碼執行不到。只有呼叫失敗了,它們才會返回 -1,失敗後從原程式的呼叫點接著往下執行。

execl() 示例程式碼:


#include <stdio.h>
#include <unistd.h>
 
int main(int argc, char *argv[])
{
    printf("before exec\n\n");
    
    /* /bin/ls:外部程式,這裡是/bin目錄的 ls 可執行程式,必須帶上路徑(相對或絕對)
       ls:沒有意義,如果需要給這個外部程式傳參,這裡必須要寫上字串,至於字串內容任意
       -a,-l,-h:給外部程式 ls 傳的引數
       NULL:這個必須寫上,代表給外部程式 ls 傳參結束
    */
    execl("/bin/ls", "ls", "-a", "-l", "-h", NULL);
    
    // 如果 execl() 執行成功,下面執行不到,因為當前程序已經被執行的 ls 替換了
    perror("execl");
    printf("after exec\n\n");
    
    return 0;
}
執行結果如下:

execv()示例程式碼:

execv() 和 execl() 的用法基本是一樣的,無非將列表傳參,改為用指標陣列。


#include <stdio.h>
#include <unistd.h>
 
int main(int argc, char *argv[])
{
    // execv() 和 execl() 的用法基本是一樣的,無非將列表傳參,改為用指標陣列
    // execl("/bin/ls", "ls", "-a", "-l", "-h", NULL);
    
    /* 指標陣列
       ls:沒有意義,如果需要給這個外部程式傳參,這裡必須要寫上字串,至於字串內容任意
       -a,-l,-h:給外部程式 ls 傳的引數
       NULL:這個必須寫上,代表給外部程式 ls 傳參結束
    */
    char *arg[]={"ls", "-a", "-l", "-h", NULL};
    
    // /bin/ls:外部程式,這裡是/bin目錄的 ls 可執行程式,必須帶上路徑(相對或絕對)
    // arg: 上面定義的指標陣列地址
    execv("/bin/ls", arg);
    
    perror("execv");
        
    return 0;
}

execlp() 或 execvp() 示例程式碼:
execlp() 和 execl() 的區別在於,execlp() 指定的可執行程式可以不帶路徑名,如果不帶路徑名的話,會在環境變數 PATH指定的目錄裡尋找這個可執行程式,而 execl() 指定的可執行程式,必須帶上路徑名。


#include <stdio.h>
#include <unistd.h>
 
int main(int argc, char *argv[])
{
    // 第一個引數 "ls",沒有帶路徑名,在環境變數 PATH 裡尋找這個可執行程式
    // 其它引數用法和 execl() 一樣
    execlp("ls", "ls", "-a", "-l", "-h", NULL);
    
    /*
    char *arg[]={"ls", "-a", "-l", "-h", NULL};
    execvp("ls", arg);
    */
    
    perror("execlp");
    
    return 0;
}


execle() 或 execve() 示例程式碼:

execle() 和 execve() 改變的是 exec 啟動的程式的環境變數(只會改變程序的環境變數,不會影響系統的環境變數),其他四個函式啟動的程式則使用預設系統環境變數。


execle()示例程式碼:


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> // getenv()
 
int main(int argc, char *argv[])
{
    // getenv() 獲取指定環境變數的值
    printf("before exec:USER=%s, HOME=%s\n", getenv("USER"), getenv("HOME"));
    
    // 指標資料
    char *env[]={"USER=MIKE", "HOME=/tmp", NULL};
    
    /* ./mike:外部程式,當前路徑的 mike 程式,通過 gcc mike.c -o mike 編譯
        mike:這裡沒有意義
        NULL:給 mike 程式傳參結束
        env:改變 mike 程式的環境變數,正確來說,讓 mike 程式只保留 env 的環境變數
     */
    execle("./mike", "mike", NULL, env);
    
    /*
    char *arg[]={"mike", NULL};        
    execve("./mike", arg, env);    
    */
    
    perror("execle");
    
    return 0;
}

外部程式,mike.c 示例程式碼:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(int argc, char *argv[])
{
    printf("\nin the mike fun, after exec: \n");
    printf("USER=%s\n", getenv("USER"));
    printf("HOME=%s\n", getenv("HOME"));
    
    return 0;
}

執行結果如下:

 

 

--------------------- 
作者:Mike__Jiang 
來源:CSDN 
原文:https://blog.csdn.net/tennysonsky/article/details/46004367 
版權宣告:本文為博主原創文章,轉載請附上博文連結!