一.孤儿进程
孤儿进程可以理解为一个子进程的父进程英年早逝(父进程先于子进程退出),就将这样的一个进程称为孤儿进程,在linux
操作系统上。孤儿进程被init进程收养,此时孤儿进程的ppid==1
,即init进程的pid == 1
。也就是说init进程变成孤儿进程的父进程(干爹)。
下面举例说明什么是孤儿进程:
代码语言:javascript复制#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
int main()
{
pid_t pid = fork();
if(pid == 0)//子进程
{
sleep(1);//目的是让父进程先于子进程执行完毕
printf("son process startn");
printf("my pid is %d,my ppid is %dn",getpid(),getppid());
printf("son process endn");
}
else
{
printf("father process startn");
printf("my pid is %dn",getpid());
printf("father process endn");
}
return 0;
}
从执行结果来看,此时由pid == 3363
父进程创建的子进程,其输出的父进程pid == 1
,说明当其为孤儿进程时被init进程回收。
我们查证一下init进程的pid是否为1
操作系统为什么要给孤儿进程分配init进程收养孤儿进程? 其目的只有一个,就是为了释放系统资源 进程结束之后,能够释放用户区空间。但不能释放pcb(进程控制块),即内核资源。pcb必须由子进程的父进程进行释放。
二.僵尸进程 (1)父进程成功创建子进程,且子进程先于父进程退出。 (2)子进程需要父进程回收其所占资源,释放pcb。但是父进程不作为,不去释放已经退出子进程的pcb。 (3)这样的子进程变为僵尸进程。 (4)僵尸进程是一个已经死掉了的进程。
下面举例验证什么是僵尸进程:
代码语言:javascript复制#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t pid = fork();//创建子进程
if(pid == 0)
{
printf("my pid is %dn",getpid());
printf("my ppid is %dn",getppid());
}
else
{
while(1)
{
;//死循环,阻塞 不处理已经退出的子进程,致使子进程称为僵尸进程
}
}
return 0;
}
从图可以看出,子进程执行完毕。父进程处于阻塞状态(陷入死循环)。
再开启另外一个bash窗口,使用ps -aux | grep 3749
命令查看僵尸进程的状态。
defunct是已死的,僵尸的意思。 也就是说pid值为3749的进程为僵尸进程,是因为其父亲不对已经结束的子进程做进程回收。但是已经结束的子进程的资源必须由其父进程进行回收,因而产生了僵尸进程。
试想一下,如果有大量的僵尸进程驻在系统之中,必然消耗大量的系统资源。但是系统资源是有限的,因此当僵尸进程达到一定数目时,系统因缺乏资源而导致奔溃。所以在实际编程中,避免和防范僵尸进程的产生显得尤为重要。
三.进程回收
(1)回收僵尸进程的资源,一种比较暴力的做法是将其父进程杀死,那么子进程资源也将被回收。但是这种做法在大多数情况下都是不可取的,如父进程是一个服务器程序,如果为了回收其子进程的资源,而杀死服务器程序,那么将导致整个服务器崩溃,得不偿失。显然这种回收进程的方式是不可取的,但其也有一定的存在意义。那么有没有更好的解决方案呢,且看下边的两种方式。
注:kipp -9 (父进程的pid)
是第一种回收子进程资源的方式。
(2)wait系统调用函数 所需头文件:
代码语言:javascript复制#include<sys/types.h>
#include<sys/wait.h>
函数原型: pid_t wait(int* status)
,是一个阻塞函数。
返回值:如果为-1,回收失败,已经没有子进程可以回收了。 如果 > 0,返回值为子进程对应的pid。
参数:子进程的退出状态,是一个传出参数。 判断子进程是如何死的 (1)正常退出 (2)被信号杀死
(1)WIFEXITED(status):为非0,进程正常结束。 WEXITSTATUS(status):如果上宏为真,使用此宏,获取进程的退出状态(exit/return的参数)。 (2)WIFEXITEDWIFSIGNALED(status):为非0,进程异常终止。 WTERMSIG(status):如上宏为真,使用此宏,获取使进程终止的那个信号的编号。
注意:pid_t类型即为int类型。调用一次,只能回收一个子进程,如果回收多个子进程,就需要多次调用wait函数。 函数功能: (1)阻塞并等待子进程退出。 (2)回收子进程残留资源。 (3)获取子进程结束状态(退出原因)。
实例一:不关心子进程的退出状态
代码语言:javascript复制#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(0 == pid)
{
printf("my pid is %dn",getpid());
}
else
{
sleep(1);//目的是让子进程先于父进程结束
/*wait函数是阻塞函数,会等到子进程结束回收资源*/
pid_t dpid = wait(NULL);//对子进程的退出状态不关心
printf("the died son process pid is %dn",dpid);
}
return 0;
}
执行结果:
实例二:利用参数int* status
获取子进程的退出状态
1.获取正常退出的状态
代码语言:javascript复制#include<sys/types/h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pid = fork();
if(0 == pid)
{
printf("my pid is %dn",getpid());
}
else
{
sleep(1);
int status = 0;//初始化为0
pid_t dpid = wait(&status);
if(WIFEXITED(status))
{
printf("exit value:%dn",WEXITSTATUS(status));
}
printf("the died son process pid is %dn",dpid);
}
return 10;//返回值为10
}
执行结果:
2.获取异常退出的状态。
代码语言:javascript复制#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pd = fork();
if(pid == 0)
{
while(1)
{
printf("my pid is %dn",getpid());
sleep(2);//控制执行速度不要太快
}
else
{
sleep(1);
int status = 0;//初始化为0
wait(&status);
if(WIFSIGNALED(status))
{
printf("exit by signal : %dn",WTEEMSIG(status));
}
}
}
return 0;
}
查找子进程的pid值,并杀死。
再次查看执行结果
使用kill -l
命令可以擦看所有信号,其中编号为9的信号是SIGKILL
。
子进程被编号为9的信号杀死。 (3)waitpid系统调用函数 函数功能:和wait函数相同。 所需头文件
代码语言:javascript复制#include<sys/types.h>
#include<sys/wait.h>
函数原型: pid_t waitpid(pid_t pid,int* status,int options)
返回值:
(1)>0:返回清理掉的子进程的PID。
(2)-1:无子进程可以回收。
(3)=0:参数3为WNOHANG,且子进程正在运行。
参数:
1.pid 可以有选择的回收
(1)pid:pid == -1,等待任意子进程,与wait等效。
(2)pid > 0,等待其进程ID与pid相等的子进程。
(3)pid == 0,等待其组ID等于调用进程的组ID的任一子进程。
(4)pid < -1,等待其组ID等于pid的绝对值的任一子进程。
2.status:子进程的退出状态,用法同wait函数。
3.options:设置为WNOHANG,函数非阻塞。设置为0,阻塞函数。