1、exec替換進程映象linux
在進程的建立上Unix採用了一個獨特的方法,它將進程建立與加載一個新進程映象分離。這樣的好處是有更多的餘地對兩種操做進行管理。當咱們建立shell
了一個進程以後,一般將子進程替換成新的進程映象,這能夠用exec系列的函數來進行。固然,exec系列的函數也能夠將當前進程替換掉。
ubuntu
2、exec關聯函數組小程序
包含頭文件<unistd.h>
數組
功能用exec函數能夠把當前進程替換爲一個新進程。exec名下是由多個關聯函數組成的一個完整系列,頭文件<unistd.h>
bash
原型
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[]);
ui
參數
path參數表示你要啓動程序的名稱包括路徑名
arg參數表示啓動程序所帶的參數url
返回值:成功返回0,失敗返回-1
spa
execl,execlp,execle(都帶「l」)的參數個數是可變的,參數以一個空指針結束。
execv、execvp和execvpe的第二個參數是一個字符串數組,新程序在啓動時會把在argv數組中給定的參數傳遞到main
名字含字母「p」的函數會搜索PATH環境變量去查找新程序的可執行文件。若是可執行文件不在PATH定義的路徑上,就必須把包括子目錄在內的絕對文件名作爲一個參數傳遞給這些函數。
名字最後一個字母爲"e"的函數能夠自設環境變量。
這些函數一般都是用execve實現的,這是一種約定俗成的作法,並非非這樣不可。
int execve(const char *filename, char *const argv[], char *const envp[]);
注意,前面6個函數都是C庫函數,而execve是一個系統調用。
3、執行exec函數,下面屬性是不發生變化的:
- 進程ID和父進程ID(pid, ppid)
- 實際用戶ID和實際組ID(ruid, rgid)
- 附加組ID(sgid)
- 會話ID
- 控制終端
- 鬧鐘餘留時間
- 當前工做目錄
- 根目錄
- umask
- 文件鎖
- 進程信號屏蔽
- 未處理信號
- 資源限制
- 進程時間
而下面屬性是發生變化的:
- 文件描述符若是存在close-on-exec標記的話,那麼打開的文件描述符會被關閉。
- 若是可執行程序文件存在SUID和SGID位的話,那麼有效用戶ID和組ID(euid, egid)會發生變化
程序啓動的時候,全部的信號處理方式都是默認的。而後fork來講,由於子進程和父進程的地址空間是同樣的,因此信號處理方式保留了下來。 接下來進行exec,會將全部設置成爲捕捉的信號都修改爲爲默認處理,而原來已經設置成爲忽略的信號就不發生改變。
示例程序:
爲了演示自設環境變量的功能,先寫個小程序,能夠輸出系統的環境變量
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系列函數講解以下:
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)的文件描述符,也就是說
由於若是替換進程映像成功,那麼直接到替換進程的main函數開始執行,不會返回,故不會輸出Exiting main ...
原型: int system(const char *command);
若是沒法啓動shell運行命令,system將返回127;出現不能執行system調用的其餘錯誤時返回-1。若是system可以順利執行,返回那個命令的退出
碼。system函數執行時,會調用fork、execve、waitpid等函數。
咱們能夠本身實現一個my_system函數,以下:
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》