1、替換原理:
用fork建立子程序後執行的是和父程序相同的程式,也有可能執行不同的代碼分支,子程序往往要調用一種exec函數以執行另一個程式。當程序調用一種exec函數時,該程序的使用者空間代碼和資料完全被新程序替換,從新程式的啟動例程(main函數)開始執行。記住:調用exec并不建立新程序,是以調用exec前後該程序的id并為改變。exec隻是用磁盤上的一個新程式替換了目前程序的正文段、資料段、堆段和棧段。
2、替換函數
其實,有六種以exec開頭的函數,統稱exec函數:
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
3、函數解釋
(1)傳回值:這些函數如果調用成功,則加載新程式從啟動代碼開始執行,不再傳回。如果調用出錯,則傳回-1。可知,exec函數隻有出錯的傳回值,而沒有成功的傳回值。
(2)參數:前四個函數取路徑名作為參數,後兩個函數則取檔案名作為參數。
(3)參數表的傳遞:
1)函數execl、execle、execlp要求将新程式的每個指令行參數都說明為一個單獨的參數,這種參數表以空指針結尾。
2)對于另外3個函數,則先構造一個指向各參數的指針數組,然後将該數組位址作為這3個函數的參數。
(4)傳遞環境表:
1)以e結尾的2個函數(execle和execve)可以傳遞一個指向環境字元串指針數組的指針。
2)其他4個函數則使用調用程序中的environ變量為新程式複制現有的環境。
3)通常,一個程序允許将其環境傳播給其子程序,但有時程序想為子程序指定某一個确定的環境。
4、命名了解
這些函數原型看起來很容易混,但隻要掌握了規律就很好記。
(1)l(list):表示參數采用清單。
(2)v(vector):參數用數組。
(3)p(path):自動搜尋環境變量PATH。
(4)e(env):自己維護環境變量。
5、exec調用舉例如下:
#include<stdio.h>
int main()
{
char *const argv[] = { "ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
//帶p的,可以使用環境變量PATH,無需寫全路徑
execlp("ps", "ps", "-ef", NULL);
//帶e 的,需要自己組裝環境變量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
//帶p的,可以使用環境變量PATH,無需寫全路徑
execvp("ps", argv);
//帶e的,需要自己組裝環境變量
execve("/bin/ps", argv, envp);
exit(0);
}
注:事實上,隻有exec是真正的系統調用,其他五個函數最終都調用execve,是以exexcve在man手冊第二節,其他函數在man手冊第三節。這些函數之間的關系如下圖所示:
6、替換的過程
(1)擷取指令行
(2)解析指令行
(3)建立一個子程序(fork)
(4)替換子程序(execvp)
(5)父程序等待子程序退出(wait)
【例】
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
char *argv[8];
int argc = 0;
void do_parse(char *buf)
{
int i;
int status = 0;
for(argc=i=0; buf[i]; i++)
if(!isspace(buf[i]) && status == 0){
argv[argc++] = buf+i;
status = 1;
}else if (isspace(buf[i])){
status = 0;
buf[i] = 0;
}
}
argv[argc] = NULL;
}
void do_execute(void)
{
pid_t pid = fork();
switch(pid){
case -1;
perror("fork");
exit(EXIT_FAILURE);
break;
case 0;
execvp(argv[0], argv);
perror("execvp");
exit(EXIT_FAILURE);
default:
{
int st;
while(wait(&st) != pid)
;
}
}
}
int main()
{
char buf[1024] = {};
while(1){
printf("myshell>");
scanf("%[^\n]%*c", buf);
do_parse(buf);
do_execute();
}
}
7、函數和程序之間的相似性
一個C程式有很多函數組成,exec/exit就像call/return。一個函數可以調用另外一個函數,同時傳遞給它一些參數。被調用的函數執行一定的操作,然後傳回一個值。每個函數都有它的局部變量,不同的函數通過call/return進行通信。這種通過參數和傳回值在擁有私有資料的函數間通信的模式是結構化設計的基礎。Linux鼓勵将這種應用于程式之内的模式擴充到程式之間。如下圖所示:
釋:一個C程式可以fork/exec另一個程式,并傳給它一些參數。這個被調用的程式執行一定的操作,然後通過exit(n)來傳回值。調用它的程序可以通過wait(&ret)來擷取exit的傳回值。
【例1】示範exec函數
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5 #include<errno.h>
6
7 char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL};
8
9 int main(void)
10 {
11 pid_t pid;
12 if((pid = fork()) < 0){
13 perror("fork error"),exit(1);
14 }else if(pid == 0){
15 if(execle("/home/sar/bin/echoall", "echoall", "myarg1","MY ARG2", (char*)0, env_init) < 0){
16 perror("execle error!"),exit(1);
17 }
18 }
19
20 if(waitpid(pid, NULL, 0) < 0){
21 perror("wait error"),exit(1);
22 }
23
24 if((pid = fork()) < 0){
25 printf("fork error");
26 }else if(pid == 0){
27 if(execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
28 perror("execlp error"),exit(1);
29 }
30 return 0;
31 }
釋:在程式中先調用execle,它要求一個路徑名和一個特定的環境。下一個調用的是execlp,它用一個檔案名,并将調用者的環境傳送給新程式。
【例2】回顯所有指令行參數和所有環境字元串
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5
6 int main(int argc, char *agrv[])
7 {
8 int i;
9 char **ptr;
10 extern char **environ;
11
12 //echo all command-line args
13 for(i = 0; i < argc; i++)
14 printf("argv[%d]:%s\n", i, argv[i]);
15
16 //and all env strings
17 for(ptr = environ; *ptr != 0; ptr++)
18 printf("%s\n", *ptr);
19
20 exit(0);
21 }