一、exec替換進程映象
在進程的建立上Unix採用了一個獨特的方法,它將進程建立與載入一個新進程映象分離。這樣的好處是有更多的餘地對兩種操作進行管理。當我們建立了一個進程之後,通常將子進程替換成新的進程映象,這可以用exec系列的函數來進行。當然,exec系列的函數也可以將當前進程替換掉。
二、exec關聯函數組
包含標頭檔<unistd.h>
功能用exec函數可以把當前進程替換為一個新進程。exec名下是由多個關聯函數組成的一個完整系列,標頭檔<unistd.h>
原型
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
參數
path參數表示你要啟動程式的名稱包括路徑名
arg參數表示啟動程式所帶的參數
傳回值:成功返回0,失敗返回-1
execl,execlp,execle(都帶“l”)的參數個數是可變的,參數以一個null 指標結束。
execv、execvp和execvpe的第二個參數是一個字串數組,新程式在啟動時會把在argv數組中給定的參數傳遞到main
名字含字母“p”的函數會搜尋PATH環境變數去尋找新程式的可執行檔。如果可執行檔不在PATH定義的路徑上,就必須把包括子目錄在內的絕對檔案名稱做為一個參數傳遞給這些函數。
名字最後一個字母為"e"的函數可以自設環境變數。
這些函數通常都是用execve實現的,這是一種約定俗成的做法,並不是非這樣不可。
int execve(const char *filename, char *const argv[], char *const envp[]);
注意,前面6個函數都是C庫函數,而execve是一個系統調用。
樣本程式:
為了示範自設環境變數的功能,先寫個小程式,可以輸出系統的環境變數
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
/************************************************************************* > File Name: pid_env.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sun 24 Feb 2013 07:52:09 PM CST ************************************************************************/#include<stdio.h> #include<unistd.h> extern char **environ; int main(void) { printf("hello pid=%d\n", getpid()); int i; for (i = 0; environ[i] != NULL; i++) printf("%s\n", environ[i]); return 0; } |
其中environ是全域變數但沒有在標頭檔中聲明,所以使用前需要外部聲明一下。輸出如下:
simba@ubuntu:~/Documents/code/linux_programming/APUE/process$ ./pid_env
hello pid=5597
TERM=vt100
SHELL=/bin/bash
XDG_SESSION_COOKIE=0ba97773224d90f8e6cd57345132dfd0-1368605430.130657-1433620678
SSH_CLIENT=192.168.232.1 8740 22
SSH_TTY=/dev/pts/0
USER=simba
......................
即輸出了一些系統內容的變數,變數較多,省略輸出。
我們前面在講到fcntl 函數時未講到當cmd參數取F_SETFD時的情形,即設定檔案描述符的標誌,現結合exec系列函數講解如下:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
|
/************************************************************************* > File Name: process_.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 23 Feb 2013 02:34:02 PM CST ************************************************************************/ #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h>#define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) /* 這幾個庫函數都會調用execve這個系統調用 */ int main(int argc, char *argv[]) { char *const args[] = {"ls", "-l", NULL}; printf("Entering main ... \n"); // execlp("ls", "ls", "-l", NULL); // 帶p會搜尋PATH // execl("/bin/ls", "ls", "-l", NULL); // 帶l為可變參數 // execvp("ls", args); //args數組參數傳遞給main // execv("/bin/ls", args);
int ret; // ret = fcntl(1, F_SETFD, FD_CLOEXEC); /* FD_CLOSEXEC被置位為1(在開啟檔案時標誌為O_CLOEXEC也會置位), * 即在執行execve時將標準輸出的檔案描述符關閉, * 即下面替換的pid_env程式不會在螢幕上輸出資訊 */ // if (ret == -1) // perror("fcntl error");
char *const envp[] = {"AA=11", "BB=22", NULL}; ret = execle("./pid_env", "pid_enV", NULL, envp); // 帶e可以內建環境變數 // execvpe("ls", args, envp); if (ret == -1) perror("exec error"); printf("Exiting main ... \n"); return 0; } |
我們使用了exec系列函數進行舉例進程映像的替換,最後未被注釋的execle函數需要替換的程式正是我們前面寫的輸出系統內容變數的小程式,但因為execle可以自設環境變數,故被替換後的進程輸出的環境變數不是系統的那些而是自設的,輸出如下:
simba@ubuntu:~/Documents/code/linux_programming/APUE/process$ ./exec
Entering main ...
hello pid=5643
AA=11
BB=22
如果我們將上面 fcntl 函數的注釋開啟了,即設定當執行exec操作時,關閉標準輸出(fd=1)的檔案描述符,也就是說下面替換的pid_env程式不會在螢幕上輸出資訊。
因為如果替換進程映像成功,那麼直接到替換進程的main開始執行,不會返回,故不會輸出Exiting main ...
三、system函數
功能:system()函數調用“/bin/sh -c command”執行特定的命令,阻塞當前進程直到command命令執行完畢
原型: int system(const char *command);
傳回值:
如果無法啟動shell運行命令,system將返回127;出現不能執行system調用的其他錯誤時返回-1。如果system能夠順利執行,返回那個命令的退出碼。system函數執行時,會調用fork、execve、waitpid等函數。
我們可以自己實現一個my_system函數,如下:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
|
/************************************************************************* > File Name: process_.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 23 Feb 2013 02:34:02 PM CST ************************************************************************/ #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<sys/wait.h>#define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int my_system(const char *command); int main(int argc, char *argv[]) { /* 相當於調用 /bin/sh -c ls -l | wc -w */ // system("ls -l | wc -w"); my_system("ls -l | wc -w"); return 0; } int my_system(const char *command) { pid_t pid; int status; if (command == NULL) return 1; if ((pid = fork()) < 0) status = -1; else if (pid == 0) { execl("/bin/sh", "sh", "-c", command, NULL); exit(127); } else { while (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; status = -1; break; } } return status; } |
需要說明的是在while迴圈中,如果waitpid返回-1錯誤,則還需要判斷一下是否被訊號處理函數所中斷,如果是則繼續等待,否則跳出迴圈。man 7 signal 有如下解釋:
If a signal handler is invoked while a system call or library function call is blocked, then either:
* the call is automatically restarted after the signal handler returns; or
* the call fails with the error EINTR.
參考:《APUE》