1. wait函数
前面在僵尸进程中说过子进程结束后,需要由父进程回收子进程,所以父进程肯定是不能先于子进程结束的,父进程怎么才能知道子进程结束了呢?
当子进程结束后会通知父进程(子进程会给父进程发送SIGCHILD信号),然后父进程接收到通知就会处理子进程了,这时就需要用到wait函数了。
函数原型:
<sys/wait.h>
pid_t wait(int *status);
返回值:调用成功返回清理的子进程pid,失败则返回-1 (没有要回收的子进程)
参数status:用于保存子进程的退出状态等信息(传出参数)。
wait函数可使用传出参数status来保存子进程退出状态,然后借助宏函数来进一步判断进程终止的具体原因,宏函数如下所示:
1.WIFEXITED(status) //返回非0表示进程正常结束
WEXITSTATUS(status) //如上面宏函数为真,则使用此宏函数获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) //返回非0表示进程异常终止
WTERMSIG(status) //如上面宏函数为真,使用此宏函数取得使进程终止的那个信号的编号
3. WIFSTOPPED(status) //返回非0表示进程处于暂停状态
WSTOPSIG(status) //如上面宏函数为真,使用此宏函数取得使进程暂停执行的那个信号的编号
WIFCONTINUED(status) //如上面宏函数为真,使用此宏函数让进程暂停后已经恢复运行
父进程在调用wait函数回收子进程时,wait函数将会执行以下几个动作:
- 阻塞等待子进程退出
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)
2. 程序示例
wait_process程序获取子进程退出状态
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void) {
pid_t pid, wpid;
int status;
pid = fork();
if (pid == 0) {
sleep(300);
printf("child, pid = %d\n", getpid());
return 19;
} else if (pid > 0) {
printf("parent, pid = %d\n", getpid());
//获取子进程的状态
//父进程一直在阻塞等待子进程结束
wpid = wait(&status);
printf("wpid ---- = %d\n", wpid);
if (WIFEXITED(status)) {
//获取进程退出状态
printf("exit with %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
//获取使进程异常终止的信号编号
printf("killed by %d\n", WTERMSIG(status));
}
}
}
程序执行结果:
当执行kill -9命令使子进程异常终止退出时,调用wait函数通过参数status就可以获取到子进程异常终止退出的信息。
3. waitpid函数
waitpid函数功能和wait一样,都是用来获取子进程状态或改变,但是waitpid函数更加强大,但可指定pid进程回收,可以不阻塞。
函数原型:
pid_t waitpid(pid_t pid, int *status, in options);
返回值说明:成功返回清理掉的子进程pid;失败则返回-1,设置errno
参数pid:指定回收子进程pid
pid > 0,回收指定pid的子进程
pid = -1,回收任意子进程(相当于wait)
pid = 0,回收和当前调用waitpid一个进程组内的所有子进程
pid < -1,回收指定进程组内为|pid|的所有子进程
参数status:传出参数,用于保存清理子进程的状态(如果不关心子进程的退出状态可传NULL)
参数options:设置回收状态阻塞或非阻塞
WUNTRACED:可获取子进程暂停状态,也就是可获取stopped状态
WCONTINUED(linux2.6.10内核):可获取子进程恢复执行的状态,也就是可获取continued状态
WNOHANG:设置非阻塞,如果参数pid指定的子进程运行正常未发生状态改变,则立即返回0,如果调用进程没有与pid匹配的子进程,waitpid则出错,设置errno为ECHILD。
参数options是一个位掩码,可以使用|运算符操作组合以上几个标志,如果options = NULL(也就是这三个开关都不打开),调用waitpid函数会以阻塞方式回收子进程
,这点需要注意。
另外,对于waitpid的参数status值也可以使用WIFEXITED,WIFSIGNALED,WIFSTOPPED等宏函数来进一步判断进程终止的具体原因。
使用waitpid函数的时候需要注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环
4. 程序示例
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
/*
主要思路:熟悉waitpid函数原型,参数作用和返回值
*/
int main(void) {
pid_t pid, wpid;
int flg = 0;
pid = fork();
//fork进程失败
if(pid == -1){
perror("fork error");
exit(1);
//子进程
} else if(pid == 0){
printf("I'm process child, pid = %d\n", getpid());
sleep(5);
exit(4);
//父进程
} else {
do {
//WNOHANG非阻塞回收指定子进程
wpid = waitpid(pid, NULL, WNOHANG);
//wpid = wait(NULL);
printf("---wpid = %d--------%d\n", wpid, flg++);
//如果wpid == 0说明参数3为WNOHANG非阻塞回收,且子进程正在运行中
if(wpid == 0){
printf("NO child exited\n");
sleep(1);
}
//每次循环前,判断子进程是否可回收
} while (wpid == 0);
//如果为真,可回收指定子进程
if(wpid == pid){
printf("I'm parent, I catched child process, pid = %d\n", wpid);
//回收的任意子进程
} else {
printf("other...\n");
}
}
return 0;
}
程序执行结果:
父进程在fork完之后,调用了waitpid函数并sleep了1秒,这个时候子进程获得了CPU的执行权,打印I’m process child,pid = 1965后sleep了5秒,之后父进程一直在以非阻塞方式等待回收子进程,5秒后子进程执行结束,父进程成功回收子进程。
5. waitid函数
与waitpid函数类似,waitid函数是用于获取一个子进程更加详细的状态或改变。waitid是源于System V下的系统调用,现在已加入到2.6.9 linux内核中。
函数原型:
#include <sys/wait.h>
int waitid( idtype_t idtype, id_t id, siginfo_t *infop, int options );
返回值:成功返回0,失败返回-1并设置errno
参数idtype和id指定需要获取那些子进程的状态:
1.如果idtype = P_ALL,则获取任意子进程状态,并忽略参数id
2.如果idtype = P_PID,获取参数id指定的子进程状态
3.如果idtype = P_PGID,则获取进程组为id的所有子进程
waitid函数和waitpid函数最大的区别在于,waitid可以通过参数options更加精准详细的获取子进程的状态或改变:
WEXITED:获取正常终止的子进程状态
WSTOPPED:获取因信号而暂停的子进程
WCONTINUED:获取由SIGCONT信号恢复执行的子进程
WNOHANG:同waitpid函数中的选项意义相同
WNOWAIT:从“可等待状态”的子进程处返回,后面的wait依然可以获取子进程状态
调用waitid函数返回0会将子进程相关信息保存到参数infop中,infop是一个传出参数,它的数据类型是siginfo_t结构体,以下是siginfo结构体中的比较重要的成员信息:
siginfo_t {
int si_signo; /* Signal number */
int si_code; /* Signal code */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
. . . . . .
}
si_signo:表示信号,如SIGCHILD信号
si_code:一般有这几个值,CLD_EXITED表示子进程是调用_exit终止的,CLD_KILLED表示子进程因某个信号杀死的,CLD_STOPPED表示子进程因某个信号而终止,CLD_CONTINUED表示子进程收到SIGCONT信号恢复执行。
si_pid:发送信号的进程id
si_uid:发送信号的进程实际用户
si_status:子进程退出的原因,比如正常退出对应的值,异常退出对应的信号。子进程具体的退出原因可以通过si_code来进一步判断。
6. waitid函数的细节
在使用waitid函数需要注意的细节,如果参数options指定了WNOHANG,waitid函数会在以下几种情况下返回0:子进程的状态已经改变,或者子进程的状态没有改变。
如果子进程没有改变状态(这说明子进程还未退出),一些unix实现会包括linux会将siginfo_t结构体内容清0(但是有一些unix实现不会把siginfo_t结构体内容清空),然后我们就可以检查si_pid的值是否为0,以此来区分waitid函数返回0是哪一种情况。
为了确保区分这两种情况,最好是在调用waitid函数之前把siginfo_t结构体清空,代码如下:
siginfo_t info;
memset(&info , 0 , sizeof(siginfo_t));
waitid(idtype , id , &info , WNOHANG);
if(info.si_pid == 0){
/*没有任何子进程的状态发生改变*/
}else{
/*有子进程的状态发生改变*/
}
7. 程序示例
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
int main(void)
{
pid_t pid;
id_t id;
int status;
int ret;
pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
}
//子进程
else if(pid == 0){
printf("I'm child, pid = %d\n", getpid());
exit(8); //子进程以_exit方式退出,退出状态的值为8
}
//父进程
else{
siginfo_t info;
//清空siginfo结构体
memset(&info , 0 , sizeof(siginfo_t));
ret = waitid(P_ALL , id , &info , WEXITED);
if(ret < 0){
perror("waitid error:");
}
//判断子进程是否以_exit方式退出
if(info.si_code == CLD_EXITED)
{
printf("si_code = CLD_EXITED\n");
}
//打印子进程退出状态的值
printf("si_status = %d\n" , info.si_status);
}
return 0;
}
程序执行结果:
当子进程调用exit函数退出时,在exit函数内部会调用_exit系统调用,也就是说,实际上子进程会以_exit方式退出
。然后父进程调用waitid函数在获取子进程状态时,其参数info就会保存子进程退出状态,打印si_code = CLD_EXITED则说明子进程是以_exit方式退出的,si_status则保存了子进程退出时对应的返回值。
8. 总结
1 . 重点掌握wait和waitpid的用法
2 . 注意wait和waitpid的区别和使用场景
3 .了解waitid函数