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