进程编程中的孤儿和僵尸进程–wait/waitpid函数
孤儿进程
如果父进程先退出,子进程还没退出那么子进程就变为孤儿进程,他的父进程将变为init进程。(注:任何一个进程都必须有父进程)
僵尸进程
如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,如果父进程还没有查询子进程的状态,这个时候子进程就成为僵尸进程。
利用ps -ef
可查看进程状态,进程后有<defunct>
字样的进程即为僵尸进程。
14234 pts/18 00:00:00 dm02_fork <defunct>
避免僵尸进程的一个方法是使用信号:
#include <signal.h>
signal(SIGCHLD, SIG_IGN);
wait和waitpid出现的原因
当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)
子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。
- 父进程查询子进程的退出状态可以用wait/waitpid函数
wait函数
- 头文件:
<sys/types.h>和<sys/wait.h>
- 函数功能:当我们用fork启动一个进程时,子进程就有了自己的生命,并将独立地运行。有时,我们需要知道某个子进程是否已经结束了,我们可以通过wait安排父进程在子进程结束之后/父进程等待子进程结束!。
- 函数原型
pid_t wait(int *status)
- 函数参数
status:该参数可以获得你等待子进程的信息 - 返回值:
成功等待子进程,函数返回等待子进程的ID
- wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
- 返回的是子进程的PID,它通常是结束的子进程
- 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
- 如果status不是一个空指针,状态信息将被写入它指向的位置,Wait获取status后进行处理。
相关宏定义:
- WIFEXITED(status) 如果子进程正常结束,返回一个非零值
WEXITSTATUS(status) 如果WIFEXITED非零,返回子进程退出码
WIFSIGNALED(status) 子进程因为捕获信号而终止,返回非零值
WTERMSIG(status) 如果WIFSIGNALED非零,返回信号代码
WIFSTOPPED(status) 如果子进程被暂停/停止,返回一个非零值
- WSTOPSIG(status) 如果WIFSTOPPED非零,返回一个信号代码
- 示例代码
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
int main()
{
pid_t pid ;
int ret ;
int status ;
pid = fork();
if(pid == -1)
{
perror("fork");
exit(-1);
}
else if(pid == 0)
{
printf("child\n");
//exit(125);
abort();//子进程执行到这里就结束
}
printf("parent!\n");
ret = wait(&status);//参数非空指针--存储状态信息
if(ret == -1)
{
perror("wait");
exit(-1);
}
printf("ret : %d\t parent pid: %d\n",ret,getpid());
if(WIFEXITED(status))//正常退出--exit/return
{
printf("exit normaly with : %d\n",WEXITSTATUS(status));//获取正常退出的状态码
}
else if(WIFSIGNALED(status))//被信号终止--signal
{
printf("receive a signal with : %d\n",WTERMSIG(status));//获取信号码
}
else if(WIFSTOPPED(status))//程序停止了/接收到停止信号--ctrl +c
{
printf("process stoped: %d\n",WSTOPSIG(status));//获取信号码
}
else//其他方式的停止
{
printf("other exit..!\n");
}
return 0;
}
waitpid函数
- 函数功能:用来等待某个特定进程的结束
- 函数原型:
pid_t waitpid(pid_t pid, int *status,int options)
- 参数:
status:如果不是空,会把状态信息写到它指向的位置
options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起 - 返回值:如果成功返回等待子进程的ID,失败返回-1
对于waitpid的p I d参数的解释与其值有关:
- pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。
- pid > 0 等待其进程I D与p I d相等的子进程。
- pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。
- pid < -1 等待其组I D等于p I d的绝对值的任一子进程。
示例代码
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
int main()
{
pid_t pid ;
int ret ;
int status ;
pid = fork();
if(pid == -1)
{
perror("fork");
exit(-1);
}
else if(pid == 0)
{
printf("child\n");
//exit(125);
abort();//子进程执行到这里就结束
}
printf("parent!\n");
ret = waitpid(-100,&status,0);//参数非空指针--存储状态信息
//监听进程组ID为100的进程组
if(ret == -1)
{
perror("wait");
//ret = waitpid(-1,&status,0);//监听任意子进程
//ret = waitpid(pid,&status,0);//监听指定的子进程
ret = waitpid(0,&status,0);//参数非空指针--存储状态信息
//监听同组进程
if(ret == -1)
{
perror("wait");
exit(-1);
}
}
printf("ret : %d\t parent pid: %d\n",ret,getpid());
if(WIFEXITED(status))//正常退出--exit/return
{
printf("exit normaly with : %d\n",WEXITSTATUS(status));//获取正常退出的状态码
}
else if(WIFSIGNALED(status))//被信号终止--signal
{
printf("receive a signal with : %d\n",WTERMSIG(status));//获取信号码
}
else if(WIFSTOPPED(status))//程序停止了/接收到停止信号--ctrl +c
{
printf("process stoped: %d\n",WSTOPSIG(status));//获取信号码
}
else//其他方式的停止
{
printf("other exit..!\n");
}
return 0;
}
Wait和waitpid区别和联系
- 在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
- waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
- 实际上wait函数是waitpid函数的一个特例。
如何避免僵尸进程
当一个子进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行或者父进程调用了wait才告终止。
进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。它将称为一个“僵进程”
- 调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。相当于收尸!
- 如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
- 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
void Test(int var)
{
printf("Aloha!Fork()....%d\n",var);
}
int main()
{
int loop_num = 0;
int proc_num = 0;
int i = 0,j = 0;
pid_t pid ;
int ret ;
printf("Input the procnum:\n");
scanf("%d",&proc_num);
printf("Input the loopnum:\n");
scanf("%d",&loop_num);
for(i= 0; i < proc_num;i++)
{
pid = fork();
if(0 == pid)
{
printf("child : %d\tparent : %d\n",getpid(),getppid());//父进程如果退出以后,会将创建的子进程托孤给init进程--1号进程--父进程的PID就是1
for(j = 0;j < loop_num;j++)
{
Test(j);
}
exit(0);//子进程结束--保证没有孙子产生
}
else if(pid == -1)
{
perror("fork");
return -1;
}
}
while(1)
{
ret = wait(NULL);//父进程阻塞等待子进程结束--阻塞过程中有可能被别的信号中断,需要再做异常处理
if(ret == -1)//返回-1表示没有子进程了/子进程全部结束了 或者是父进程被信号中断--否则返回值是子进程的PID
{
if(errno == EINTR)//被信号中断--继续阻塞监听
{
continue;
}
break;//不是被信号中断--表明确实没有可监听的子进程--父进程可以准备结束了
}
}
printf("parent over!!!\n");
return 0;
}