一、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”)的參數個數是可變的,參數以一個空指針結束。
execv、execvp和execvpe的第二個參數是一個字符串數組,新程序在啟動時會把在argv數組中給定的參數傳遞到main
名字含字母“p”的函數會搜索PATH環境變量去查找新程序的可執行文件。如果可執行文件不在PATH定義的路徑上,就必須把包括子目錄在內的絕對文件名做為一個參數傳遞給這些函數。
名字最后一個字母為"e"的函數可以自設環境變量。
這些函數通常都是用execve實現的,這是一種約定俗成的做法,並不是非這樣不可。
int execve(const char *filename, char *const argv[], char *const envp[]);
注意,前面6個函數都是C庫函數,而execve是一個系統調用。
三、執行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》