一、孤儿进程
父进程先于子进程结束,则子进程称为孤儿进程,子进程的父进程成为 init 进程,称 init 进程领养进程孤儿进程,init 进程称为孤儿院。
#include
#include
#include
int main(void)
{
pid_t pid;
pid = fork();
if( pid == -1 )
{
perror("fork");
exit(1);
}
else if( pid > 0)
{
printf("I am parent,my pid = %d\n",getpid());
sleep(9);
printf("--------------parent going to die------------------\n");
}
else
{
while(1)
{
printf("I am child,my parent pid = %d\n",getppid());
sleep(1);
}
}
return 0;
}
此时程序中的打印信息会一直进行打印,即使你按下 ctrl+c 也不会退出正在执行的程序 ./orpan;此时需要使用 ps -ef ,查看正在打印的程序信息;然后使用 kill -9 + 进程ID(./orphan 的ID)
二、 僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。
注意:
僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令只是用来终止进程的,而僵尸进程已经终止。#include
#include
#include
int main(void)
{
pid_t pid;
pid = fork();
if( pid == -1 )
{
perror("fork");
exit(1);
}
else if( pid > 0)
{
printf("I am parent,my pid = %d\n",getpid());
sleep(9);
printf("--------------parent going to die------------------\n");
}
else
{
while(1)
{
printf("I am child,my parent pid = %d\n",getppid());
sleep(1);
}
}
return 0;
}
父子进程打印出的信息如下:
可通过 ps -ef 命令来查看进程信息,可以查看出僵尸进程:
三、wait 函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB 还保留着,内核在其中保留了一些信息。如果是正常终止,则保存退出状态;如果是异常终,则保存着导致该进程终止的信号是哪一个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息 ,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量 $? 查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或者 waitpid 得到它的退出状态同时彻底清除掉这个进程。
父进程调用 wait 函数可以回收子进程终止信息。该函数有三个功能:
(1)、阻塞等待子进程退出
(2)、回收子进程残留资源
(3)、获取子进程结束状态
pid_t wait(int *status); 成功:返回清理掉的子进程 ID; 失败:-1(没有子进程)
(1)、关闭所有文件描述符
(2)、释放用户空间分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。(正常终止---->退出值;异常终止------->终止信号)
可使用 wait 函数传出参数 status 来保存进程的退出状态,借助宏函数来进一步判断进程终止的具体原因,宏函数可分为如下三组:
(1)、WIFEXITED(status) 为非 0 -------------> 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 ------------------> 获取进程退出状态(exit 的参数)
(2)、WIFSIGNALED(status) 为非 0 -------------> 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 ------------------> 取的使进程终止的那个信号的编号。
(3)、WIFSTOPPED(status) 为非 0 -------------> 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 ------------------>取的使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 -------------------> 进程暂停后继续运行
#include
#include
#include
#include
#include
int main(void)
{
pid_t pid,wpid;
int status;
pid = fork();
if( pid == -1 )
{
perror("fork");
exit(1);
}
else if( pid == 0)
{
execl("abnor","abnor",NULL);
printf("I am child,my parent pid = %d,going to sleep 10s\n",getppid());
sleep(20);
printf("-------------child die------------------\n");
// exit(76);
return 100;
}
else
{
// 回收子进程,避免出现僵尸进程
wpid = wait(&status);
if( wpid == -1 )
{
perror("wait error:");
exit(1);
}
// 正常退出
if( WIFEXITED(status) )
{
printf("child exit with %d\n",WEXITSTATUS(status));
}
// 异常退出
if(WIFSIGNALED(status))
{
printf("child exit with %d\n",WTERMSIG(status));
}
while(1)
{
printf("I am parent,my pid = %d,my son = %d\n",getpid(),pid);
sleep(1);
}
}
return 0;
}
作用同 wait ,但可指定 pid 进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, int options);
返回值:成功:返回清理掉的子进程 ID;
失败:-1(无子进程)
特殊参数和返回情况:
参数1 pid:
>0 回收指定 ID 的子进程
-1 回收任意子进程(相当于 wait)
=0 回收和当前调用 waitpid 一个组的所有子进程
<-1 回收指定进程组内的任意子进程
参数 2:status
参数 3 : 0 <===> wait 参数为0,等价为 wait ,阻塞回收。
WNOHNAG 非阻塞回收(轮询)
返回值 :成功:返回 pid 失败:返回 -1
返回 0 值: 参 3 传了 WNOHNAG,并且子进程尚未结束。
注意:
一次 wait 或者 waitpid 调用只能清理一个子进程,清理若干个子进程应该使用循环。
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int n=5,i; // 默认创建 5 个子进程
pid_t p,q;
pid_t wid;
if( argc == 2 )
{
n=atoi(argv[1]);
}
for( i=0;i0 )
{
n--;
}
// if wid == 0 说明子进程正在运行
sleep(1);
}while( n> 0);
printf("wait finish\n");
while(1);
}
else
{
sleep(i);
printf("I am %dth child,pid = %d,gpid = %d\n",i+1,getpid(),getppid());
}
return 0;
}
/*
需求:
父进程 fork 3 个子进程,三个子进程 一个调用 ps 命令,一个调用自定义程序1(正常),一个调用自定义程序1(会出错误)。
父进程会使用 waitpid 对子进程进行回收。
*/
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int n=3,i;
pid_t pid;
pid_t wid;
for( i=0;i 0 )
{
i--;
}
sleep(1);
}while( i > 0 );
printf(" wait finnaly!\n");
sleep(10);
}
return 0;
}