Linux程序替換--函式 exec
對 fork 函式,我們知道是建立子程序的,那建立子程序幹嗎?一般有下面兩種情況:
- 一個父程序希望複製自己,使父程序和子程序同時執行不同的程式碼段。
- 一個程序要執行不同的程式,在這種情況下,子程序從 fork 返回之後立即呼叫 exec 系列
程序替換
當一個程序呼叫一種 exec 函式時,該程序執行的程式完全替換為新程式,而新程式從其 main 函式開始執行。 呼叫 exec 並不建立新程序, 前後的程序 ID 並未改變, 只是用磁碟上的一個新程式替換當前程序的正文段、資料段、堆段和棧段。 exec 函式族
#include <unistd.h> 使用 exec 系列函式必須包含
execl、execlp、execle execv、execvp、execvpe
成功沒有返回值 失敗返回 -1
當指定 filename 作為引數時: 如果 filename 中包含 / ,則就將其視為路徑名 否則就按 PATH 環境變數,在其所指定的各目錄中搜尋可執行檔案
PATH 變數包含了一張目錄表(稱為路徑字首),目錄之間用冒號(:)分隔。 在 bash 視窗可以使用 echo $PATH 列印PATH /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/zbq/.local/bin:/home/zbq/bin
l : 可變引數列表 v : 陣列–字串指標陣列 p : 環境變數的 PATH e : 環境變數
在執行 exec 之後,程序 ID 沒有改變,新程式從呼叫程序繼承了下列屬性:
- 程序 ID 和父程序 ID
- 實際使用者 ID 和實際組 ID
- 附屬組 ID
- 程序組 ID
- 會話 ID
- 控制終端
- 鬧鐘尚餘留的時間
- 當前工作目錄
- 根目錄
- 檔案模式建立遮蔽字
- 檔案鎖
- 程序訊號遮蔽
- 未處理訊號
- 資源限制
- nice 值
- tms_utime、tms_stime、tms_cutime 以及 tms_cstime 值
接下來我們一個個的看看
- execl 函式 (1) 函式原型: int execl(const char * path, const char * arg, …); (2) 這裡的 path 是可執行程式的路徑 取路徑名作為引數 (3) arg 是所要替換程式的引數 (4) 返回值: 成功沒有返回值,失敗返回 -1 (5) 我們寫個程式測試一下
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
// ls 列印根目錄下的文
int res = execl("/usr/bin/ls", "ls", "-l", "/", NULL);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
程式編譯(編譯環境 CentOS 7)執行結果:
並且我們發現,並沒有打印出 “hahaha” 這個字串,當然也沒有出錯 ? 我們在這裡將程式稍加修改:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
// 只更改了這裡
int res = execl("ls", "ls", "-l", "/", NULL);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
函式在 execl 處出錯,返回值為 1, 沒有這個檔案的原因是我們沒有把路徑加上,他找不到這個程式,自然失敗了! 2. execlp 函式 (1) 函式原型: int execlp(const char * file, const char * arg, …); (2) 這裡 file 引數可以是一個 可執行程式名,並且可以不用指定全路徑,它會自動在 PATH 中的一個找到該可執行檔案,如果該檔案不是由連線編譯器產生的機器可執行檔案,則就認為該檔案是一個 shell 指令碼,於是試著呼叫 /bin/sh, 並以該 filename 作為 shell 的輸入, 取檔名作為引數 (3) arg 還是可執行程式的引數 (4) 返回值: 成功返回 (5) 我們寫個簡單的程式碼,看看執行結果
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int res = execlp("ls", "ls", "-l", "/", NULL);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
這次即使沒加全路徑,也執行成功
- execle 函式 (1) 函式原型: int execle(const char * path, const char * arg, …, char * const envp[]); (2)這裡的最後一個引數是一個自己構建的環境,是一個字串指標陣列,取路徑名作為引數 (3) 寫個小程式
// filename: process_exec.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char *env[] = {
"AAAA=aaaa",
NULL
};
int res = execle("./exec_test", "./exec_test", NULL, env);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
// filename: test_exec.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("AAAA = %s\n", getenv("AAAA")/*需要包含 stdlib.h */);
return 0;
}
程式編譯執行結果:
我們看到,已經把設定進去的環境變數獲取到。
- execv 函式 (1) 函式原型: int execv(const char * path, char * const argv[]); (2) 這和 execl 函式一樣,區別就是,execl 函式的第二個引數為可變引數列表,而 execv 函式的第二個引數為字串指標陣列。取路徑名作為引數 (3) 寫個小程式看看
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char * arg[] = {
"ls",
"-l",
"/",
NULL
};
int res = execv("/usr/bin/ls", arg);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
5. execvp 函式 (1) 函式原型: int execvp(const char * file, char * const argv[]); (2) 與函式 execlp 一樣,取檔名作為引數 (3) 看程式碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char * arg[] = {
"ls",
"-l",
"/",
NULL
};
int res = execvp("ls", arg);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
程式執行結果:
- execve 函式 (1) 函式原型: int execve(const char * file, char * const argv[], char * const envp[]); (2) 與函式 execle 函式一樣, 取路徑名作為引數 (3) 看程式碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char * arg[] = {
"./exec_test",
NULL
};
char * env[] = {
"AAAA=aaaa",
NULL
};
int res = execve("./exec_test", arg, env);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
程式執行結果:
以上就是 exec 函式族了
- fexecle 函式 這也是一個程序替換函式,依賴呼叫程序來完成這項工作。呼叫程序可以使用檔案描述符驗證所需要的檔案並且無競爭地執行該檔案。