5. 等待函数
(1)wait和waitpid
头文件 |
#include <sys/types.h> #include <sys/wait.h> |
函数 |
pid_t wait(int* status); pid_t waitpid(pid_t pid, int* status, int options); |
返回值 |
成功返回子进程ID,出错返回-1 |
功能 |
等待子进程退出并回收,防止僵尸进程的产生 |
参数 |
(1)status参数: ①为空时,代表任意状态结束的子进程; ②不为空时,则等待指定状态结束的子进程 (2)waitpid的pid参数 ①pid == -1 :等待任一子进程,功能与wait等效。 ②pid > 0 :等待其进程ID与pid相等的子进程 ③pid == 0:等待其组ID等于调用进程的组ID的任一子进程 ④pid < -1:等待其组ID等于pid的绝对值的任一子进程。 (3)options参数 ①WNOHANG:如果pid子进程未结束则立即返回,不会阻塞,此时返回值为0。如果pid进程己退出,则返回这个进程的pid。 ②WUNTRACED:使waitpid报告那些己经被停止的未报告子进程的状态。 |
备注 |
wait的waitpid的区别: ①在一个子进程终止前,wait使用调用者阻塞 ②waitpid的options可使调用者不阻塞 ③waitpid等待一个指定的子进程,而wait等待所有的子进程,返回任一终止子进程的状态。 |
(2)检查wait和waitpid函数返回的终止状态的宏
判断终止状态 |
获取终止状态值 |
说明 |
WIFEXITED(status) |
WEXITSTATUS(status) |
判断子进程是否正常终止及获取退出码 |
WIFSIGNAL(status) |
WTERMSIG(status) |
判断子进程是否异常终止及获取异常终止的信号编码 |
WIFSTOPED(status) |
WSTOPSIG(status) |
判断子进程是否被暂停及获取暂停的信号编码 |
【编程实验】判断进程的终止状态
//process_wait.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> void out_status(int status) { if(WIFEXITED(status)){ printf("normal exit: %d\n", WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("abnormal terminate: %d\n", WTERMSIG(status)); }else if(WIFSTOPPED(status)){ printf("Stopped signal: %d\n", WSTOPSIG(status)); }else{ printf("unknow signal\n"); } } int main(void) { int status; pid_t pid; //第1个子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid == 0){ //child process printf("pid: %d, ppid: %d\n", getpid(), getppid()); exit(0); //正常终止 } //等待子进程结束(也可防止僵尸进程) if(wait(&status) != pid) perror("wait error"); out_status(status); printf("----------------------------------------\n"); //第2个子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid == 0){ //child process printf("pid: %d, ppid: %d\n", getpid(), getppid()); abort(); //异常终止 } //等待子进程结束(也可防止僵尸进程) if(wait(&status) != pid) perror("wait error"); out_status(status); printf("----------------------------------------\n"); //第3个子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid == 0){ //child process printf("pid: %d, ppid: %d\n", getpid(), getppid()); int i = 3, j = 0; int k = i /j; //除0 printf("k: %d\n", k); } if(wait(&status) != pid) perror("wait error"); out_status(status); printf("----------------------------------------\n"); //第4个子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid == 0){ //child process printf("pid: %d, ppid: %d\n", getpid(), getppid()); pause(); //暂停,需要用带WUNTRACED的waipid来等待。 //同时,该子进程需发kill -SIGSTOP 来结束 } //阻塞方式 waitpid(pid, &status, WUNTRACED); // //非阻塞方式 // do // { // //当waitpid返回0,表示子进程未结束 // pid = waitpid(pid, &status, WNOHANG | WUNTRACED); // // if (pid == 0) sleep(1); // }while(pid == 0); out_status(status); return 0; } /*输出结果: pid: 1686, ppid: 1685 normal exit: 0 ---------------------------------------- pid: 1687, ppid: 1685 abnormal terminate: 6 ---------------------------------------- pid: 1688, ppid: 1685 abnormal terminate: 8 ---------------------------------------- pid: 1689, ppid: 1685 Stopped signal: 19 */
6. exec函数
(1)exec函数的主要作用
①在fork函数创建子进程后,子进程往往要调exec函数来执行另一个程序。
②当进程调用exec函数时,该进程完全由新程序代换,替换原有进程的正文,而新程序则从其main函数开始执行。因为exec并不创建新进程,所以前后的进程ID并不改变。
③exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
(2)函数原型
头文件 |
#include <unistd.h> |
函数 |
int execl(const char* pathname, const char* arg0,…/*(char*)0*/); int execv(const char* pathname, char* const argv[]); int execle(const char* pathname, const char* arg0,…/*(char*)0,char* const envp[]*/); int execve(const char* pathname, char* const argv[],char* const envp[]); int execlp(const char* pathname, const char* arg0,…/*(char*)0*/); int execvp(const char* pathname ,char* const argv[]); |
返回值 |
出错返回-1,成功则不返回 |
功能 |
执行程序 |
参数 |
(1)argv参数为新程序执行main函数中传递的argv参数,最后一个元素为NULL。 (2)envp为进程环境表。 (3)execlp和execvp函数中的pathname,相对和绝对路径均可使用。其它四个函数中的pathname只能使用绝对路径。相对路径一定要在进程环境表对应的PATHT中。 |
备注 |
(1)execve函数为系统调用,其余为库函数。当成功执行execve后就不返回 execve后面的代码去执行,进程进入新的程序执行,最后从新程序中退出。但执行execve出错时,仍会返回原程序。 (2)函数名后带字母“l”,表示第2个参数为列表(List),列表中的第1个元素为要执行的程序名,最后一个参数必须为NULL。 (3)带“p”的函数,表示第1个参数可以是相对或绝对路径。如果是相对路径,则会在环境变量PATH指定的路径中搜索。p表示PATH。 (4)带字母“v”表示参数列为通过一个字符串数组来传递,相当于main函数的argv参数,数组中的第1个元素必须是程序名,最后一个参数也必须为NULL。 (5)带“e”的函数,用户可以自己设置程序接收一个设置环境变量的数组。 |
(3)6个函数的关系
(4)exec执行后新进程保留原进程的一些特性
①进程ID、父进程ID、实际用户和组ID、进程组ID
②会话ID、控制终端;
③当前工作目录、根目录
④文件锁,进程信号屏蔽、未处理信号、资源限制
⑤闹钟尚余留的时间、tms_utime、tms_stime、tms_cutime以及tms_cstime的值。
⑥对打开文件的处理与每个描述符的(close-on-exec)标志有关。如果此标志设置,则在执行exec时关闭该描述符,否则该描述符仍打开。(一般系统默认这个标志位是关闭,也可以用fcntl来设置该标志位)。
⑦在执行exec时会自动关闭打开的目录流(调用opendir函数打开的目录),也可以用fcntl函数为打开的目录流的描述符设置close-on-exec标志。
【编程实验】打开新程序
//process_exec.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> char* cmd1 = "cat"; //相对路径 char* cmd2 = "/bin/cat"; //绝对路径 char* argv1 ="/etc/passwd"; char* argv2 = "/etc/group"; int main(void) { pid_t pid; //第1个子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid == 0){ //child process //调用exec函数执行新的程序 //if(execl(cmd1, cmd1, argv1, argv2, NULL) < 0){ //错误,execl只能用绝对路径 if(execl(cmd2, cmd1, argv1, argv2, NULL) < 0){ perror("execl error"); exit(1); }else{ printf("execl %s success\n", cmd1); //不返回这里 } //当exec成功调用后,执行流进入新的程序,不返回到这里。 printf("after exec...\n"); } //只有父进程会执行到这里,子进程因exec进入了新的程序 wait(NULL); printf("--------------------------------\n"); //第2个子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid == 0){ //child process char* argv[4]={cmd1, argv1, argv2, NULL}; if(execvp(cmd1, argv) < 0){ perror("execvp error"); exit(1); }else{ printf("execvp %s success\n", cmd1); //不返回这里 } } wait(NULL); return 0; }
7. system函数
头文件 |
#include <stdlib.h> |
函数 |
int system(const char* command); |
返回值 |
成功返回执行命令的状态,出错返回-1 |
功能 |
简化exec函数的使用 |
备注 |
①system函数内部构建一个子进程,由子进程调用exec函数 ②/bin/bash -c "cmd"或者exec("bash","-c","cmd"); |
【编程实验】自定义system函数
//process_system.c
#include <unistd.h> #include <stdlib.h> const char* cmd1 = "date > s1.txt"; const char* cmd2 = "date > s2.txt"; //模仿system函数 void mysystem(const char* cmd) { pid_t pid; //1.创建子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid == 0){//子进程 //2. 通过exec函数创/bin/bash进程来执行命令 if(execlp("/bin/bash", "bin/bash","-c", cmd, NULL) < 0){ perror("execlp error"); exit(1); } }else{ //父进程 wait(0); } } int main(void) { system("clear"); //清屏 system(cmd1); mysystem(cmd2); //自定义的mysystem return 0; } /*输出结果 [email protected] 6.process]# cat s1.txt 2017年 01月 26日 星期四 19:26:23 CST [[email protected] 6.process]# cat s2.txt 2017年 01月 26日 星期四 19:26:23 CST [[email protected] 6.process]# */