一、函数wait和waitpid
今天我们继续通过昨天那个死爹死儿子的故事来讲(便于记忆),现在看看wait和waitpid函数。
#include<sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid , int *statloc , int options); 若成功,返回进程ID,若出错,返回0或-1
wait系列函数的作用就是通知父亲儿子的死讯的,父进程一般接收到SIGCHLD信号而调用wait,(等待终止),可能有三种情况发生:
(1).子进程都在Running,则阻塞
(2).如果一个子进程终止,则父进程获取其终止状态(statloc指针指向的空间),然后立即返回
(3).如果它没有任何子进程,则立即出错返回
wait可以返回的四个终止状态的互斥的宏,分别为:
WIFEXITED(status) 正常终止
WIFSIGNALED(status) 异常终止
WIFSTOPPED(status) 暂停子进程
WIFCONTINUED(status) 暂停后已经继续的子进程
例子:
#include "apue.h" #include <sys/wait.h> void pr_exit(int status) { if ( WIFEXITED(status) ) printf("normal termination,exit status = %d ", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("abnormal termination,signal number = %d ", WTERMSIG(status)); else if (WIFSTOPPED(status)) printf("child stopped, signal number = %d ", WSTOPSIG(status)); } int main(void) { pid_t pid; int status; if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ exit(7); if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ abort(); /* generates SIGABRT */ if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ status /= 0; /* divide by 0 generates SIGFPE */ if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ exit(0); }
waitpid函数,顾名思义,其作用就在于可以等待特定的pid的子进程终止。此外,waitpid的options参数提供了WNOHANG(相当于不阻塞的wait)、WCONTINUED、WUNTRACED(支持作业控制)三个常量选项。
下面是一个非常经典的fork子、孙进程的示例:(两次fork,有效的避免僵死进程)
#include "apue.h" #include <sys/wait.h> int main(void) { pid_t pid; if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { /* first child */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid > 0) exit(0); /* parent from second fork == first child */ /* * We're the second child; our parent becomes init as soon * as our real parent calls exit() in the statement above. * Here's where we'd continue executing, knowing that when * we're done, init will reap our status. */ sleep(60); printf("second child, parent pid = %ld ", (long)getppid()); exit(0); } if (waitpid(pid, NULL, 0) != pid) /* wait for first child */ err_sys("waitpid error"); /* * We're the parent (the original process); we continue executing, * knowing that we're not the parent of the second child. */ sleep(30); exit(0); }
为了便于测试效果明显,我将父进程sleep了30秒,然后退出,将孙进程sleep了60秒,然后退出。在这段代码中,最先退出的是子进程,为了防止孙进程僵死,我们让它sleep的时间更长,而父进程执行完毕退出后,孙进程被init进程收养,孙进程执行自己的任务,执行完毕后正常退出,使得该过程有效的避免了僵死进程。
此外,waitid类似于waitpid,但更为灵活,wait3、wait4的附加参数可以返回所有子进程使用的资源概况,不在此详细说明。
二、函数exec
当我们fork一个进程后,新的进程往往要调用exec执行一个启动例程,如第七章所述图:
因此,调用exec并不创建新的进程,只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段、栈段。
7个不同的exec函数:
#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 *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
返回值:成功不返回,出错返回-1
七个exec函数的区别:
(1).前四个取路径名作为参数,后两个取文件名作为参数,最后一个以fd作为参数
(2).参数表的传递不同(l表示列表list,v表示矢量vector)包含l和包含v
(3).向新程序传递环境表不同。包含e和不含e
七个exec直接的关系如下图,只有execve是内核的系统调用,另外6个只是库函数,它们最终都要调用该execve系统调用。
exec函数使用例程:
#include "apue.h" #include <sys/wait.h> char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL }; int main(void) { pid_t pid; if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { /* specify pathname, specify environment */ if (execle("/home/webber/test/echoall", "echoall", "myarg1", "MY ARG2", (char *)0, env_init) < 0) err_sys("execle error"); } if (waitpid(pid, NULL, 0) < 0) err_sys("wait error"); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { /* specify filename, inherit environment */ if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0) err_sys("execlp error"); } exit(0); }
echoall.c文件代码如下:
#include "apue.h" int main(int argc, char *argv[]) { int i; char **ptr; extern char **environ; for (i = 0; i < argc; i++) /* echo all command-line args */ printf("argv[%d]: %s ", i, argv[i]); for (ptr = environ; *ptr != 0; ptr++) /* and all env strings */ printf("%s ", *ptr); sleep(100); exit(0); }
这里同样是为了测试,我们sleep了100秒,模拟子进程的工作时间。
注意,需要预先把echoall.c文件编程,gcc echoall.c -o echoall ,否则,当我们exec找到该文件后无法执行。这样,我们就完成了启动子进程的一整个流程