exec 函數族是 Linux 進程控制的核心:它**不創建新進程**,而是將調用進程的地址空間(代碼、數據、堆、棧)完全替換為新程序,**保持 PID 不變**。本文深度剖析八大變體差異、參數傳遞機制、環境變量處理、錯誤碼與典型誤區。
exec 前後進程 ID 相同,但內存映射、文件描述符表(受 FD_CLOEXEC 影響)、信號處理函數(恢復默認)重置。main 開始運行,原代碼不再執行。僅出錯返回 -1(errno 指示原因)。FD_CLOEXEC)、環境變量(無 envp 參數時)。| 函數 | 原型 | 路徑解析 | 參數傳遞 | 環境變量 | 後綴含義 |
|---|---|---|---|---|---|
execl |
int execl(const char *path, const char *arg0, ..., (char *)NULL); |
完整路徑 | 可變參數列表,以 NULL 結束 |
繼承調用者 environ |
l = list (參數列表) |
execv |
int execv(const char *path, char *const argv[]); |
完整路徑 | 指針數組 argv[],以 NULL 結束 |
繼承調用者 environ |
v = vector (數組) |
execlp |
int execlp(const char *file, const char *arg0, ..., (char *)NULL); |
搜索 PATH | 可變參數列表 | 繼承調用者 environ |
p = PATH 搜索 |
execvp |
int execvp(const char *file, char *const argv[]); |
搜索 PATH | 指針數組 | 繼承調用者 environ |
vp = vector + PATH |
execle |
int execle(const char *path, const char *arg0, ..., (char *)NULL, char *const envp[]); |
完整路徑 | 可變參數列表 | 指定 envp[] | le = list + env |
execve |
int execve(const char *filename, char *const argv[], char *const envp[]); |
完整路徑 | 指針數組 | 指定 envp[] | ve = vector + env (系統調用入口) |
execlpe |
int execlpe(const char *file, const char *arg0, ..., (char *)NULL, char *const envp[]); |
搜索 PATH | 可變參數列表 | 指定 envp[] | lpe = list + PATH + env |
execvpe |
int execvpe(const char *file, char *const argv[], char *const envp[]); |
搜索 PATH | 指針數組 | 指定 envp[] | vpe = vector + PATH + env (GNU 擴展) |
p 者搜索 PATH 環境變量(如 execlp("ls", ...)),不帶 p 需完整路徑(execl("/bin/ls", ...))。l 為可變參數(適合固定參數),v 為數組(適合動態構造參數,如 Shell 解析後的命令行)。e 者接受 envp[] 顯式指定新進程環境,**不繼承**調用者 environ**;不帶 e 者繼承**。execve 是系統調用入口,其餘均為 glibc 封裝,最終調用 execve。// 以當前環境運行 /usr/bin/python3 script.py arg1 arg2
execl("/usr/bin/python3", "python3", "script.py", "arg1", "arg2", (char *)NULL);
// 等價數組形式
char *argv[] = {"python3", "script.py", "arg1", "arg2", NULL};
execv("/usr/bin/python3", argv);
// 用戶輸入 "ls -l /tmp",解析後動態構造 argv
char *argv[] = {"ls", "-l", "/tmp", NULL};
execvp("ls", argv); // 自動搜索 /bin/ls, /usr/bin/ls 等
char *new_env[] = {
"PATH=/usr/local/bin:/usr/bin:/bin",
"LANG=en_US.UTF-8",
"MY_APP_CONFIG=/etc/myapp/prod.conf",
NULL
};
char *argv[] = {"./myapp", "--daemon", NULL};
execve("./myapp", argv, new_env); // 完全隔離環境,安全部署首選
// 等價 system("cmd") 但可控環境、參數
execlp("sh", "sh", "-c", "cmd1 | cmd2 > /tmp/out.log 2>&1", (char *)NULL);
// 或帶環境
char *env[] = {"PATH=/usr/bin:/bin", NULL};
execle("/bin/sh", "sh", "-c", "cmd", (char *)NULL, env);
| errno | 宏 | 原因 | 對策 |
|---|---|---|---|
| 2 | ENOENT | 文件/路徑不存在 | 檢查路徑拼寫、PATH 是否包含目標目錄 |
| 13 | EACCES | 權限不足(無執行權、掛載 noexec、腳本無讀權) | chmod +x、檢查掛載選項、Shebang 行解釋器可執行 |
| 8 | ENOEXEC | 非可執行格式(魔數錯誤、腳本缺 Shebang) | 確保二進制為 ELF、腳本首行 #!/bin/bash |
| 12 | ENOMEM | 內存不足(參數/環境過大、進程地址空間耗盡) | 精簡參數/環境、增大 RLIMIT_AS |
| 40 | ELOOP | 符號鏈接層級過深 | 避免循環鏈接、用實際路徑 |
| 36 | ENAMETOOLONG | 路徑/參數過長 | 縮短路徑、分批處理 |
FD_CLOEXEC(fcntl(fd, F_SETFD, FD_CLOEXEC))或用 open(O_CLOEXEC)、pipe2(O_CLOEXEC)、socket(SOCK_CLOEXEC) 創建。exec 前關閉無關 FD,防止子進程持有敏感句柄(數據庫連接、監聽套接字)。MAX_ARG_STRLEN (128KB/字符串)、MAX_ARG_STRINGS (0x7FFFFFFF)、棧大小 RLIMIT_STACK (默認 8MB)。超長參數用文件/管道傳遞。#!interpreter [arg] 由內核解析,execve 實際執行 interpreter 並將腳本路徑作參數。interpreter 必須是二進制可執行文件(不能是腳本),參數最多 1 個(Linux 限制)。exec 將所有信號處理函數恢復為 SIG_DFL(忽略的保持忽略)。子進程需自行建立信號處理邏輯。exec 會**終止所有其他線程**,僅保留調用線程。調用前務必確保無關鍵線程運行(或用 pthread_atfork 協調)。system():system() = fork() + execl("/bin/sh", ...) + waitpid(),效率低、引入 Shell 注入風險、阻塞調用者。優先用 execve + pipe + waitpid 實現進程間通信。| 場景 | 推薦函數 | 理由 |
|---|---|---|
| 固定路徑、固定參數、繼承環境 | execl | 代碼最簡潔 |
| 固定路徑、動態參數數組、繼承環境 | execv | 參數靈活構造 |
| 搜索 PATH、固定參數、繼承環境 | execlp | 用戶命令執行 |
| 搜索 PATH、動態參數、繼承環境 | execvp | Shell 命令解析執行 |
| 固定路徑、完全隔離環境 | execve | 安全、可控、容器/沙箱啟動 |
| 搜索 PATH、完全隔離環境 | execvpe (GNU) | 同上,更便捷 |
精通 exec 族函數,是編寫高性能、安全、可控的 Linux 多進程程序(Shell、容器運行時、任務調度器、守護進程管理器)的關鍵鑰匙。
Linux 系統管理核心命令速查:chkconfig、df、ps、netstat 參數詳解與實戰場景
Linux I2C 子系統驅動開發深度解析:總線、演算法與晶片驅動
單硬碟雙 Linux 系統安裝精簡版:Fedora 8 引導安裝 Fedora 9
Linux 內核配置系統深度解析:Kconfig、Makefile 架構與交叉編譯變數詳解
Linux 下使用 VirtualBox 虛擬機運行 Windows 解決網銀 U 盾難題完整教程
解決 Ubuntu 14.04.2 升級 Linux 3.16 核心後 NVIDIA 331 驅動建構失敗與 Bumblebee 問題
Linux 安裝 RAID 驅動程式:使用 USB 隨身碟載入驅動映像檔實戰