UNIX再学习 -- exec 函数族
我們在講,文件I/O的時候,簡單提到過 exec 函數(shù),講到 vfork 的時候,也有用到。下面我們來詳細介紹下它。
參看:UNIX再學習 -- 文件I/O?
參看:UNIX再學習 -- 函數(shù) fork 和 vfork
一、exec 函數(shù)族概述
參看:維基百科 -- exec函數(shù)族與 fork 或 vfork 函數(shù)不同,exec 函數(shù)不是創(chuàng)建調(diào)用進程的子進程,而是創(chuàng)建一個新的進程取代調(diào)用進程自身。新進程會用自己的全部地址空間,覆蓋調(diào)用進程的地址空間,但進程的 PID 保持不變。exec 只是用磁盤上的一個新程序替換了當前進程的正文段、數(shù)據(jù)段、堆段和棧段。
exec 不是一個函數(shù)而是一堆函數(shù),一般稱為 exec 函數(shù)族。它們的功能是一樣的,用法也很相近,只是參數(shù)的形式和數(shù)量略有不同。
exec函數(shù)族的作用:根據(jù)指定的文件名找到可執(zhí)行文件,并用它來取代調(diào)用進程的內(nèi)容,換句話說,就是在調(diào)用進程內(nèi)部執(zhí)行一個可執(zhí)行文件。這里的可執(zhí)行文件既可以是二進制文件,也可以是任何Linux下可執(zhí)行的腳本文件。
這些函數(shù)之間的第一個區(qū)別:
4 個函數(shù)取路徑名作為參數(shù),2 個函數(shù)則取文件名作為參數(shù),最后一個取文件描述符作為參數(shù)。當指定 file 作為參數(shù)時:如果 file 中包含 /,則就將其視為路徑名。否則就按 PATH 環(huán)境變量,在它所指定的各目錄中搜索可執(zhí)行文件。
環(huán)境變量,我們之前專門講過,參看:UNIX再學習 -- 環(huán)境變量
PATH 變量包含了一張目錄表(稱為路徑前綴),目錄之間用冒號(:)分隔。例如,下面 name = value 環(huán)境字符串指定在 4 個目錄中進程搜索。
PATH=/bin:/usr/bin:/usr/local/bin:.
最后的路徑前綴 . 表示當前目錄。(零長前綴也表示當前目錄。在 value 的開始處可用 : 表示,在行中間則要用 :: 表示,在行尾以 : 表示)。
第二個區(qū)別與參數(shù)表的傳遞有關(l 表示表 (list), v 表示矢量(vector))。
函數(shù) execl、execlp 和 execle 要求將新程序的每個命令行參數(shù)都說明為一個單獨的參數(shù),這種參數(shù)以空指針結尾。對于另外 4 個函數(shù) (execv、execvp、execve、fexecve),則應先構造一個指向各參數(shù)的指針數(shù)組,然后將該數(shù)組地址作為這 4 個函數(shù)的參數(shù)。
第三個區(qū)別與向新程序傳遞環(huán)境表相關。
以 e 結尾的 3 個函數(shù)(execle 和 execve 和 fexecve)可以傳遞一個指向字符串指針數(shù)組的指針。其他四個函數(shù)則使用調(diào)用進程中的 environ 變量為新程序復制現(xiàn)存的環(huán)境。
這些函數(shù),它們的函數(shù)名都是在 exec 后面加上一到兩個字符后綴,不同的字符后綴代表不同的含義。
-l: ?即 list ,新進程的命令行參數(shù)以字符指針列表 (const char *arge, ...) 的形式傳入,列表以空指針結束。
-p: 即 path,若第一個參數(shù)中不包含“/”,則將其視為文件名,并根據(jù) PATH 環(huán)境變量搜索該文件。
-e: ?即 environment,新進程的環(huán)境變量以字符指針數(shù)組 (cahr *const envp[]) 的形式傳入,數(shù)組以空指針結束,不指定環(huán)境變量則從調(diào)用進程復制。?
-v: ?即 vector,新進程的命令行參數(shù)以字符指針數(shù)組 (char *const argv[]) 的形式傳入,數(shù)組以空指針結束。
在很多 UNIX 實現(xiàn)中,這 7 個函數(shù)中只有 execve 是內(nèi)核的系統(tǒng)調(diào)用。另外 6 個只是庫函數(shù),它們最終都要調(diào)用該系統(tǒng)調(diào)用。這 7 個函數(shù)之間的關系如下圖:
在這種安排中,庫函數(shù) execlp 和 execvp 使用 PATH 環(huán)境變量,查找第一個包含名為 filename 的可執(zhí)行文件的路徑名前綴。fexecve 庫函數(shù)使用 /proc 把文件描述符參數(shù)轉(zhuǎn)換成路徑名,execve 用該路徑名去執(zhí)行程序。
到此,將 exec 函數(shù)族 7 個函數(shù)的區(qū)別和關系,簡單講了一下。下面我們就一一介紹:
二、execl 函數(shù)
#include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); 若出錯,返回 -1, 若成功,不返回1、函數(shù)解析
execl()其中后綴 "l" 代表 list 也就是參數(shù)列表的意思,第一參數(shù) path 字符指針所指向要執(zhí)行的文件路徑, 接下來的參數(shù)代表執(zhí)行該文件時傳遞的參數(shù)列表:argv[0],argv[1]... 最后一個參數(shù)須用空指針NULL作結束。2、示例說明
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { printf ("父進程開始執(zhí)行\(zhòng)n"); pid_t pid = vfork (); if (pid == -1) perror ("vfork"), exit (1); if (pid == 0) { printf ("子進程開始執(zhí)行\(zhòng)n"); if (execl ("/bin/ls", "ls", "-l", NULL) == -1) perror ("execl"), _exit (1); } sleep (1); printf ("父進程執(zhí)行結束\n"); return 0; } 輸出結果: 父進程開始執(zhí)行 子進程開始執(zhí)行 總用量 16 -rwxr-xr-x 1 root root 7380 Apr 20 10:22 a.out -rw-r--r-- 1 root root 383 Apr 20 10:22 test.c -rw-r--r-- 1 root root 151 Apr 20 09:56 test.c~ 父進程執(zhí)行結束3、示例解析
上例為 vfork 函數(shù)的典型用法。在所創(chuàng)建的子進程里直接調(diào)用 exec 函數(shù)啟動另外一個進程取代其自身,這比調(diào)用 fork 函數(shù)完成同樣的工作要快得多。 if (execl ("/bin/ls", "ls", "-l", NULL) == -1) ?解釋:/bin/ls ?為 ls 指令文件路徑;ls -l 為執(zhí)行 ls 指令和選項;NULL 最后一個參數(shù);失敗返回 -1. 有時也會看到如下的寫法: if (execl ("ls", "ls", "-l", (char *)0) == -1) ?
將第三個參數(shù)寫為 (char*)0 ,我們之前講過空指針,參看:C語言再學習 -- NUL和NULL的區(qū)別 如果用常數(shù) 0 來表示一個空指針,則必須將它強制轉(zhuǎn)換為一個字符指針,否則它將解釋為整形參數(shù),如果一個整形數(shù)的長度與 char * 的長度不同,那么 exec 函數(shù)的實際參數(shù)就將出錯。如果函數(shù)調(diào)用成功,進程自己的執(zhí)行代碼就會變成加載程序的代碼,execl()后邊的代碼也就不會執(zhí)行了。舉例說明:#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { printf ("父進程開始執(zhí)行\(zhòng)n"); pid_t pid = vfork (); if (pid == -1) perror ("vfork"), exit (1); if (pid == 0) { printf ("子進程開始執(zhí)行\(zhòng)n"); if (execl ("/bin/ls", "ls", "-l", 0) == -1) perror ("execlp"), _exit (1); } sleep (1); printf ("父進程執(zhí)行結束\n"); return 0; } 編譯: 警告: 函數(shù)調(diào)用中缺少哨兵 [-Wformat]
三、execlp 函數(shù)
#include <unistd.h> int execlp(const char *file, const char *arg, ...); 若出錯,返回 -1, 若成功,不返回1、函數(shù)解析
execlp()會從 PATH 環(huán)境變量所指的目錄中查找符合參數(shù) file 的文件名,找到后便執(zhí)行該文件,然后將第二個以后的 參數(shù)當做該文件的argv[0]、argv[1]……,最后一個參數(shù)必須用空指針(NULL)作結束。2、示例說明
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { printf ("父進程開始執(zhí)行\(zhòng)n"); pid_t pid = vfork (); if (pid == -1) perror ("vfork"), exit (1); if (pid == 0) { printf ("子進程開始執(zhí)行\(zhòng)n"); if (execlp ("ls", "ls", "-l", NULL) == -1) perror ("execlp"), _exit (1); } sleep (1); printf ("父進程執(zhí)行結束\n"); return 0; } 輸出結果: 父進程開始執(zhí)行 子進程開始執(zhí)行 總用量 16 -rwxr-xr-x 1 root root 7381 Apr 26 15:02 a.out -rw-r--r-- 1 root root 480 Apr 26 15:02 test.c -rw-r--r-- 1 root root 846 Apr 25 17:01 test.c~ 父進程執(zhí)行結束3、示例解析
開始就講到,當指定 file 作為參數(shù)時,如果 file 中包含 /,則就將其視為路徑名。否則就按 PATH 環(huán)境變量,在它所指定的各目錄中搜索可執(zhí)行文件。這也就是 execlp 和 execl 的區(qū)別了。四、execle 函數(shù)
#include <unistd.h> int execle(const char *path, const char *arg, ..., char * const envp[]); 若出錯,返回 -1, 若成功,不返回1、函數(shù)解析
execl是用來執(zhí)行參數(shù) path 字符串所代表的文件路徑,并為新程序復制最后一個參數(shù)所指示的環(huán)境變量。接下來的參數(shù)代表執(zhí)行該文件時傳遞過去的argv(0)、argv[1]……,最后一個參數(shù)必須用空指針(NULL)作結束。2、示例說明
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { printf ("父進程開始執(zhí)行\(zhòng)n"); pid_t pid = vfork (); if (pid == -1) perror ("vfork"), exit (1); if (pid == 0) { printf ("子進程開始執(zhí)行\(zhòng)n"); if (execle ("/bin/ls", "ls", "-l", NULL, NULL) == -1) perror ("execle"), _exit (1); } sleep (1); printf ("父進程執(zhí)行結束\n"); return 0; } 輸出結果: 父進程開始執(zhí)行 子進程開始執(zhí)行 total 16 -rwxr-xr-x 1 root root 7381 Apr 26 15:32 a.out -rw-r--r-- 1 root root 491 Apr 26 15:32 test.c -rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~ 父進程執(zhí)行結束3、示例解析
倒數(shù)第二個參數(shù)為,傳遞一個指向字符串指針數(shù)組的指針。五、execv 函數(shù)
#include <unistd.h> int execv(const char *path, char *const argv[]); 若出錯,返回 -1, 若成功,不返回1、函數(shù)解析
execv() 用來執(zhí)行參數(shù) path 字符串所代表的文件路徑,與 execl() 不同的地方在于 execve() 只需兩個參數(shù),第二個參數(shù)利用數(shù)組指針來傳遞給執(zhí)行文件。2、示例說明
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { char *arg[] = {"ls", "-l", NULL};printf ("父進程開始執(zhí)行\(zhòng)n"); pid_t pid = vfork (); if (pid == -1) perror ("vfork"), exit (1); if (pid == 0) { printf ("子進程開始執(zhí)行\(zhòng)n"); if (execv ("/bin/ls", arg) == -1) perror ("execv"), _exit (1); } sleep (1); printf ("父進程執(zhí)行結束\n"); return 0; } 輸出結果: 父進程開始執(zhí)行 子進程開始執(zhí)行 總用量 16 -rwxr-xr-x 1 root root 7380 Apr 26 15:48 a.out -rw-r--r-- 1 root root 505 Apr 26 15:48 test.c -rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~ 父進程執(zhí)行結束3、示例解析
arg 是一個以 NULL 結尾的字符串數(shù)組的指針。六、execvp 函數(shù)
#include <unistd.h> int execvp(const char *file, char *const argv[]); 若出錯,返回 -1, 若成功,不返回1、函數(shù)解析
execvp()會從 PATH 環(huán)境變量所指的目錄中查找符合參數(shù) file 的文件名,找到后便執(zhí)行該文件,然后將第二個參數(shù)argv 傳給該欲執(zhí)行的文件。2、示例說明
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { char *arg[] = {"ls", "-l", NULL};printf ("父進程開始執(zhí)行\(zhòng)n"); pid_t pid = vfork (); if (pid == -1) perror ("vfork"), exit (1); if (pid == 0) { printf ("子進程開始執(zhí)行\(zhòng)n"); if (execvp ("ls", arg) == -1) perror ("execvp"), _exit (1); } sleep (1); printf ("父進程執(zhí)行結束\n"); return 0; } 輸出結果: 父進程開始執(zhí)行 子進程開始執(zhí)行 總用量 16 -rwxr-xr-x 1 root root 7381 Apr 26 15:54 a.out -rw-r--r-- 1 root root 502 Apr 26 15:54 test.c -rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~ 父進程執(zhí)行結束3、示例解析
當指定 file 作為參數(shù)時,如果 file 中包含 /,則就將其視為路徑名。否則就按 PATH 環(huán)境變量,在它所指定的各目錄中搜索可執(zhí)行文件。這也就是 execvp 和 execv 的區(qū)別了。 execlp 或 execvp 使用路徑前綴中的一個找到了一個可執(zhí)行文件,但是該文件不是由連接編輯器產(chǎn)生的機器可執(zhí)行文件,則就認為該文件是一個 shell 腳本,于是試著調(diào)用 /bin/sh,并以該 file 作為 shell 的輸入。七、execve 函數(shù)
#include <unistd.h> int execvpe(const char *file, char *const argv[], char *const envp[]); 若出錯,返回 -1, 若成功,不返回1、函數(shù)解析
execve() 用來執(zhí)行參數(shù) file 字符串所代表的文件路徑,第二個參數(shù)是利用指針數(shù)組來傳遞給執(zhí)行文件,并且需要以空指針(NULL)結束,最后一個參數(shù)則為傳遞給執(zhí)行文件的新環(huán)境變量數(shù)組。2、示例說明
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { char *arg[] = {"ls", "-l", NULL};printf ("父進程開始執(zhí)行\(zhòng)n"); pid_t pid = vfork (); if (pid == -1) perror ("vfork"), exit (1); if (pid == 0) { printf ("子進程開始執(zhí)行\(zhòng)n"); if (execve ("/bin/ls", arg, NULL) == -1) perror ("execve"), _exit (1); } sleep (1); printf ("父進程執(zhí)行結束\n"); return 0; } 輸出結果: 父進程開始執(zhí)行 子進程開始執(zhí)行 total 16 -rwxr-xr-x 1 root root 7381 Apr 26 16:12 a.out -rw-r--r-- 1 root root 513 Apr 26 16:12 test.c -rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~ 父進程執(zhí)行結束3、示例解析
第一個參數(shù)為 file,最后一個參數(shù)為 NULL,這沒什么講的了。 重點是 7 個函數(shù)中只有 execve 是內(nèi)核的系統(tǒng)調(diào)用,另外 6 個只是庫函數(shù),它們最終都要調(diào)用該系統(tǒng)調(diào)用。八、fexecve 函數(shù)?
#include <unistd.h> int fexecve(int fd, char *const argv[], char *const envp[]); 若出錯,返回 -1, 若成功,不返回1、函數(shù)解析
fexecve ( )執(zhí)行與 execve?相同的任務,區(qū)別在于通過文件描述符 fd?指定要執(zhí)行的文件,而不是通過路徑名。 文件描述符 fd 必須以只讀方式打開,并且調(diào)用方必須具有執(zhí)行權限它所指的文件。 fexecve 庫函數(shù)使用 /proc 把文件描述符參數(shù)轉(zhuǎn)換成路徑名,execve 用該路徑名去執(zhí)行程序。 暫時不講,找不到相關示例。九、find 和 xargs 命令相關內(nèi)容?
每個系統(tǒng)對參數(shù)表和環(huán)境表的總長度都有一個限制。這種限制是由 ARG_MAX 給出的。在 POSIX.1 系統(tǒng)中,此值至少是 4096 字節(jié)。而我用的 Ubuntu 12.04 在該系統(tǒng)中默認為?2097152,以通過 getconf ARG_MAX?可查看系統(tǒng)當前設置的值。參看:getconf命令及查看Linux32位和64位命令 當我們執(zhí)行某些命令有時候會報“Argument list too long”錯誤,例如,當前目錄文件很多時執(zhí)行mv * 或rm *,該錯誤表示執(zhí)行命令的參數(shù)太長,超過系統(tǒng)允許的最大值。 出現(xiàn)這種情況時,通常有兩種解決辦法:1、更改命令執(zhí)行方式。 例如執(zhí)行rm * 時使用如下命令替換:find . -name "*" | xargs rm {}
2、修改 ARG_MAX 的大小 ?(了解,系統(tǒng)不對實現(xiàn)不了) 參看:ARG_MAX, maximum length of arguments for a new process 參看:lsattr 命令 (1)使用命令lsattr -El sys0 -a ncargs查看ncargs占有字節(jié),輸出結果:ncargs 512 ARG/ENV list size in 4K byte blocks True
(2)getconf ARG_MAX 查看ARG_MAX設置值大小,2097152
(3)調(diào)整ncargs占用字節(jié):chdev -l sys0 -a ncargs=8 表示設置ncargs占用8字節(jié),增加這個值就可以修改ARG_MAX參數(shù)的設置了
我們講 find 和 xargs 命令時,也涉及到 exec 選項。 參看:C語言再學習 -- Linux下find命令用法 參看:C語言再學習 -- Xargs用法詳解 -exec [commend] ? ? ? ?查找后執(zhí)行命令的時候不詢問用戶,直接執(zhí)行。
查找所有jpg文件,并重命名
root@zslf-virtual-machine:/mnt/test# ls abc.txt def.txt ef.jpg hh.jpg images.tar.gz sd.jpg root@zslf-virtual-machine:/mnt/test# find ./ -name "*.jpg" -type f | xargs -i cp {} {}.old root@zslf-virtual-machine:/mnt/test# ls abc.txt def.txt ef.jpg ef.jpg.old hh.jpg hh.jpg.old images.tar.gz sd.jpg sd.jpg.old或使用 -exec
root@zslf-virtual-machine:/mnt/test# ls abc.txt def.txt ef.jpg hh.jpg images.tar.gz sd.jpg root@zslf-virtual-machine:/mnt/test# find ./ -name "*.jpg" -exec cp {} {}.old \; root@zslf-virtual-machine:/mnt/test# ls abc.txt def.txt ef.jpg ef.jpg.old hh.jpg hh.jpg.old images.tar.gz sd.jpg sd.jpg.old
十、最常見的錯誤
大家在平時的編程中,如果用到了exec函數(shù)族,一定記得要加錯誤判斷語句。因為與其他系統(tǒng)調(diào)用比起來,exec很容易受傷,被執(zhí)行文件的位置,權限等很多因素都能導致該調(diào)用的失敗。最常見的錯誤是:找不到文件或路徑,此時errno被設置為ENOENT;
數(shù)組argv和envp忘記用NULL結束,此時errno被設置為EFAULT;
沒有對要執(zhí)行文件的運行權限,此時errno被設置為EACCES。
總結
以上是生活随笔為你收集整理的UNIX再学习 -- exec 函数族的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UNIX再学习 -- exit 和 wa
- 下一篇: UNIX再学习 -- 函数 system