wait、waitpid的作用:
- 当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。
- 父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数 (信号处理程序)。对于这种信号的系统默认动作是忽略它
- 当父进程接收到子进程发来的SIGCHLD信号时,可调用wait、waitpid函数来接受子进程的终止状态
终止状态判断宏
- 概念:wait()、waitpid()的参数可以保存子进程的终止状态,#include<sys/wait.h>提供了4个互斥的宏,把终止状态当做宏参数,哪一个宏的值为真,就可选用其他宏来取得退出状态、信号编号等
WIFEXITED(status) | 若为正常终止子进程返回的状态,则为真 对于这种情况可执行WEXITSTATUS(status)取子进程传送给exit、_exit、_Exit参数的低8位 |
WIFSIGNALED(status) | 若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号) 对于 这种情况,可执行WTERMSIG(status) 取使子进程终止的信号编号 另外,有些实现(非Signle UNIX Specification)定义宏WCOREDUMP(status),若已产生终止进程的core文件,则它返回真 |
WIFSTOPPED(status) | 若为当前暂停子进程的返回的状态,则为真 对于这种情况,可执行WSTOPSIG(status)获取使子进程暂停的信号编号 |
WIFCONTINUED(status) | 若在作业控制暂停后已经继续的子进程返回了状态,则为真(POSIX.1的XSI扩展;仅用于waitpid) |
一、wait()
#include <sys/wait.h>
pid_t wait(int *statloc);
//返回:成功返回子进程ID;出错,返回0/-1
1.wait的参数与返回值
参数:
- 一个int值,用来接受子进程的终止状态
返回值:
- 返回终止子进程的进程ID
注意事项:
- wait()如果不关心子进程的终止状态,参数可以设置为NULL
- 因为返回值为子进程的进程ID,所以父进程就可以根据这个返回值来判断是哪个子进程终止的
2.wait的特点
- 如果一个进程没有任何子进程,若该进程调用wait(),则wait()立即出错返回
- wait()调用被一个信号中断时,也可能返回另一种出错
- 如果父进程的子进程都还在运行时调用wait(),则父进程就处于阻塞,等待子进程终止并返回一个终止状态
- 如果父进程调用wait(),当任意一个子进程终止时,wait()就获得该子进程的终止状态立即返回
3.演示案例
- wait()获取子进程终止状态,然后使用pr_exit打印终止状态的类型
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
void pr_exit(int exit)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n",WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",WTERMSIG(status));
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",WSTOPSIG(status));
}
int main(void)
{
pid_t pid;
int status;
if((pid=fork()<0){
perror("fork error");
}
else if(pid==0)
exit(7);
if(wait(&status)!=pid)
perror("wait error");
pr_exit(status);
if((pid=fork()<0){
perror("fork error");
}
else if(pid==0)
abort();
if(wait(&status)!=pid)
perror("wait error");
pr_exit(status);
if((pid=fork()<0){
perror("fork error");
}
else if(pid==0)
status /=0;
if(wait(&status)!=pid)
perror("wait error");
pr_exit(status);
return 0;
}
- 运行结果:
二、waitpid()
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);
//返回:成功返回子进程ID;出错,返回0/-1
1.与wait()的不同之处
- 父进程调用wait(),当任意一个子进程终止时,wait()就获得该子进程的终止状态立即返回。因此用来等待指定的子进程。早起的UNIX必须调用wait(),然后将其返回的进程ID和所期望的进程ID相比较,直至等待到特定的进程时才获取相关信息,比较麻烦
- waitpid()提供等待指定子进程的功能
2.参数
参数1:
- pid== -1:等待任一子进程。于是在这一功能方面waitpid与wait等效
- pid>0:等待其进程ID与pid相等的子进程
- pid==0:等待其组ID等于调用进程的组ID的任一子进程
- pi <-1:等待其组ID等于pid 的绝对值的任一子进程
参数2:
- 存放子进程的终止状态
参数3:
- options参数使我们能进一步控制waitpid的操作。此参数或者是0,或者是下表中常数的按位或运算的结果
WCONTINUED 若实现支持作业控制,那么由pid指定的任一子进程在停止后已经继续,但其状态尚未报告,则返回其状态(POSIX.1的XSI扩展) WNOHANG 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0 WUNTRACED 若某实现支持作业控制,而由pid指定的任一子进程已处于停止状态,并且其状态自停止依赖还未报告过,则返回其状态,WIFSTOPPED宏确定返回值是否对应于一个停止的子进程
3.特点
- 对于waitpid,如果指定的进程或进程组不存在,或者参数pid指定的进程不是调用进程的子进程,都可能出错
- 相对于wiat(),waitpid提供了一个wait的非阻塞版本,有时候希望获取一个子进程的状态,但不想阻塞
- waitpid通过WUNTRACED、WCONTINUED选项支持作业控制
4.演示案例
- 代码注释:我们不想等待子进程终止,也不希望第一个子进程处于僵死状态直到父进程终止,实现这一要求的诀窍是调用fork两次
- 第二个子进程调用sleep以保证在打印父进程ID时,第一个子进程已终止
- 在fork之后,父、子进程都可以继续执行,并且无法预知哪一个会先执行。在fork之后,如果不使第2个子进程休眠,那么它可能比其父进程先执行,于是它先打印的父进程ID将是创建它的父进程,而不是init进程
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
perror("fork error");
}
else if (pid == 0) /* first child */
{
if ((pid = fork()) < 0)
perror("fork error");
else if (pid > 0)
exit(0);
sleep(2);
printf("second child, parent pid = %ld\n", (long)getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
perror("waitpid error");
exit(0);
}
运行结果:
- 当原先的进程(也就是exec本程序的进程)终止时,shell打印其命令行指示符:因为主进程通过waitpid等待的是第一个子进程,第一个子进程比第二个父进程先结束,所以当第一个子进程结束时,父进程通过waitpid接收到终止状态也接着结束了。所以程序结束先打印命令行提示符(因为主进程结束了)
- 当打印完命令行提示符之后:后面的语句再打印出来,因为第二个子进程被init接管了,最后执行打印,并且会让你再按一下回车结束
三、waitid()
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
//返回值:成功返回0;出错,返回-1
1.功能
- 与waitpid相似,waitid允许一个进程指定要等待的子进程,但它使用两个单独的参数表示要等待的子进程所属的类型,而不是将此与进程ID或进程组ID组合成一个参数。
- id参数的作用域idtype的值相关
2.参数
参数1:
P_PID 等待一特定进程,id包含要等待子进程的进程ID P_PGID 等待一特定进程组的任一子进程;id包含要等待子进程的进程组ID P_ALL 等待任一子进程;忽略id
- 参数2:
- 参数3:指向siginfo结构的指针。该结构包含了造成子进程状态改变有关信号的详细信息
参数4:
- 下面各标志的按位或运算,指示调用者关注哪些状态变化
- WCONTINUED、WEXITED、WSTOPPED这3个常量之一必须在options参数中指定
WCONTINUED 等待一进程,它以前曾被停止,此后又已继续,但其状态尚未报告 WEXITED 等待已退出的进程 WNOHANG 如无可用的子进程退出状态,立即返回而非阻塞 WNOWAIT 不破坏子进程退出状态。该子进程退出状态可由后续的wait、waitid、waitpid调用取得 WSTOPPED 等待一进程,它已经停止,但其状态尚未报告
3.注意事项
- Linux 3.2.0、Mac OS X 10.6.8、Solaris 10支持waitid。Mac OS X 10.6.8并没有设置siginfo结构中的所有信息
四、wait3()、wait4()
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
//返回值:成功返回进程ID。出错,返回-1
1.功能
- 大多数UNIX系统提供了另外两个函数wait3和wait4。这两个函数是从UNIX系统的BSD分支延袭下来的。它们提供的功能比POSIX.函数wait和waitpid、waitod所提供的分别要多一个,这与附加参数rusage有关。该参数要求内核返回由终止进程及其所有子进程使用的资源概况
- 资源信息包括用户CPU时间总量、系统CPU时间总量、缺页次数、接收到信号的次数等
2.注意事项
- 有关细节请参阅getrusage(2)手册页。这些资源信息只包括终止子进程,并不包括处于停止状态的子进程(这种资源信息与7.11节中所述的资源限制不同)
3.列出了各个wait函数所支 持的不同的参数