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檔案只是為了列印傳入的argv
和env
的值.
注意這個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
來檢視(只截取了部分):
發現, 執行ls
產生的第一個系統呼叫就是execve("/bin/ls", ["ls"], [/* 28 vars */])
, 看它傳入的也是絕對路徑/bin/ls
.
總結:
- exec函式族中的函式執行成功時會用新程序的映像替換當前程序的映像.
p: PATH
,l: list args
,e: env
,v: vector
, 這樣好記.- main函式還有第三個引數, 雖然不標準,但是廣泛支援.
參考:
- https://stackoverflow.com/questions/33598869/execve-not-taking-environment-parameters
- https://stackoverflow.com/questions/10321435/is-char-envp-as-a-third-argument-to-main-portable
歡迎補充指正!