1. 程式人生 > >linux C之exec函式族

linux C之exec函式族

exec函式族用來執行一個程式(execute program).
下述中的引用內容如無特別說明, 均來自man page
一共有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 *filename, char *const argv[], char *const envp[]);

其中execve為系統呼叫, 另外5個為c庫函式.

The functions described in this manual page are front-ends for execve(2).

其中

  • l
    的意思是list args, 即列出引數
  • p的意思是使用環境變數PATH
  • v的意思是argument vector, 引數向量, 也就是引數用陣列來表示(argv, envp)
  • e的意思傳入環境變數(由envp指定)

exec函式族中的函式會用新程序的映像替換當前程序的映像.

The exec() family of functions replaces the current process image with a new process image.

所以exec**執行成功後, 後面的程式碼不會執行, 因為已經被新程序的程式碼替換了.

下面用這6個函式來實現ls -l

的功能.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main(int argc, const char *argv[])
{
    pid_t pid;
    int ret;

    pid = fork();

    if (pid < 0) {
        perror("fork() error");
        exit(1);
    }

    if (pid == 0) { // child
        char* const argv[] = {"ls", "-l", NULL}; // 1
        char* const envp1[] = {"PATH=.", "XX=YY", NULL}; // 2
        char* const envp2[] = {"PATH=/bin:/sbin", "XX=YY", NULL}; // 3
        char* const envp3[] = {"AA=BB", "XX=YY", NULL}; // 4

        // execlp("ls", "ls", "-l", NULL);
        // execl("/bin/ls", "ls", "-l", NULL);
        // execle("ls", "ls", "-l", NULL, envp);

        // execvp("ls", argv);
        // execv("/bin/ls", argv);
        // https://stackoverflow.com/questions/33598869/execve-not-taking-environment-parameters
        // execve("ls", argv, envp2); // 5 evnp is useless, why?
        // execve("/bin/ls", argv, envp2); // 6
        // execve("envp.out", argv, envp3); // 7

        // 如果exec**執行失敗, 才會到這來
        perror("child exec error occurred");
        exit(2);
    } else { // parent
         ret = wait(NULL); // wait for child change state
         if (ret == -1) {
             perror("wait() error");
         }
    }
    return 0;
}

我們重點關注這個execve, 因為它是系統呼叫, 其他幾個都是呼叫這個函式實現的.

  • 句子5(註釋5對應的那一句)
    execve("ls", argv, envp2), 在envp2中指定了PATH環境變數,
    看它能不能找到ls, 然而執行會報錯, 說是沒有找到ls在哪裡.
    然後SF上有人說, 這個envp不會起實質的作用, 參見連結: https://stackoverflow.com/questions/33598869/execve-not-taking-environment-parameters.
    所以用execve執行系統命令, 還是要寫絕對路徑.

  • 句子7
    execve("envp.out", argv, envp3), 這裡執行了自己寫的一個程式, 原始碼如下:

// envp.c
#include <stdio.h>

// https://stackoverflow.com/questions/10321435/is-char-envp-as-a-third-argument-to-main-portable
int main(int argc, char* argv[], char* env[])
{
    int i = 0;
    for (i = 0; i < argc; i++) {
        printf("evnp.c, argv[%d], %s\n", i, argv[i]);
    }

    for (i = 0; env[i]; i++) {
        printf("evnp.c, env[%d], %s\n", i, env[i]);
    }

    return 0;
}

envp.c檔案只是為了列印傳入的argvenv的值.
注意這個env不是POSIX標準的, 但是卻被廣泛使用. 參見: https://stackoverflow.com/questions/10321435/is-char-envp-as-a-third-argument-to-main-portable

開啟語句7, 執行exec.out, 輸出如下:

evnp.c, argv[0], ls
evnp.c, argv[1], -l
evnp.c, env[0], AA=BB
evnp.c, env[1], XX=YY

也就是說, envp.out在被執行時, 收到了傳入的char* const argv[] = {"ls", "-l", NULL}char* const envp3[] = {"AA=BB", "XX=YY", NULL}.

此時我們就明白了這個argv和envp是如何傳入的了.

事實上, 這個execve我們經常用到, 不過它是系統呼叫, 一般看不出來, 用strace命令可以打印出執行shell命令時產生的系統呼叫, 如ls的產生的系統呼叫可以使用strace ls來檢視(只截取了部分):
strace ls

發現, 執行ls產生的第一個系統呼叫就是execve("/bin/ls", ["ls"], [/* 28 vars */]), 看它傳入的也是絕對路徑/bin/ls.

總結:

  • exec函式族中的函式執行成功時會用新程序的映像替換當前程序的映像.
  • p: PATH, l: list args, e: env, v: vector, 這樣好記.
  • main函式還有第三個引數, 雖然不標準,但是廣泛支援.

參考:

歡迎補充指正!