1,进程状态
僵尸进程从宏观操作系统层面上理解进程状态
进程状态类型有运行、挂起、等待、阻塞、就绪、等待、停止、挂机、死亡等等
根据冯诺依曼体系结构得知,程序在运行时要加载进内存中,即把进程加载到内存中。而进程想在cpu上运行起来,那么cpu就得在内核中去维护一个运行队列(runqueue)。一般情况下,cpu的数量是远远少于进程数量的。而cpu只有一个,于是进程就在进程队列里排起了队伍。
所谓的进程不同的状态,本质是进程在不同的队列中等待某种资源。当挂起状态的进程可以被调度时,操作系统会把进程对应的代码和数据从磁盘加载到内存,在pcb把挂起状态改成运行状态。
2,僵尸状态和死亡状态
一个进程被创建出来是为了完成用户要求的任务,而进程完成任务的结果如何是由其父进程查看的,因此在进程退出时,不会立即释放该进程对应的资源,而是保存一段时间,让父进程或者操作系统来读取检查,读取后父进程或者操作系统才会回收该进程的所有资源。回收后该进程就是死亡状态(X—dead)了。而进程退出到还没被回收的期间的状态就是僵尸状态,也称将死亡状态(Z—zombie)
3,僵尸进程(Zombie Process)
是指一个子进程在结束时,但其父进程尚未调用 wait() 或 waitpid() 系统调用来获取子进程的退出状态信息,导致子进程的退出状态信息仍然保留在系统进程表中,此时的子进程被称为僵尸进程。
当一个进程终止时,内核会向其父进程发送一个 SIGCHLD 信号,告知子进程的终止状态。父进程在接收到该信号后,应该调用 wait() 或 waitpid() 系统调用来获取子进程的退出状态,释放子进程占用的资源,并从系统进程表中移除子进程的相关信息。
如果父进程没有及时处理子进程的退出状态,子进程就会一直处于僵尸状态。僵尸进程不占用实际的系统资源,但它们的存在可能会浪费一些系统进程表的空间,当大量的僵尸进程积累时,可能会影响系统的性能和稳定性。
为了避免僵尸进程的产生,父进程应该及时调用 wait() 或 waitpid() 系统调用来回收子进程的资源。
4,预防僵尸进程的产生
使用 fork() 创建子进程后,在父进程中调用 wait() 或 waitpid() 等待子进程的退出。
在父进程中捕获 SIGCHLD 信号,并在信号处理函数中调用 wait() 或 waitpid() 处理子进程的退出状态。
使用 fork() 创建子进程后,在子进程中调用 _exit() 或 exit() 来终止进程,而不是直接返回。
使用 fork() 创建子进程后,将子进程的信号处理设置为忽略 SIGCHLD 信号,让操作系统自动回收子进程。
综上所述,避免僵尸进程的产生需要父进程负责及时处理子进程的退出状态,释放子进程的资源。如果父进程不关心子进程的退出状态,可以将子进程的信号处理设置为忽略 SIGCHLD 信号。
5,僵尸进程示例
示例1:
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <wait.h>
using namespace std;
void sigchldHeadler(int signo)
{
pid_t pid;
int status;
while((pid=waitpid(-1,&status,WNOHANG))>0)
{
printf("回收成功:%d ok\n",pid);
if(WIFEXITED(status))
{
printf("退出状态:%d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("退出状态(信号):%d\n",WTERMSIG(status));
}
}
}
int main(void)
{
//创建10个子进程
int i;
for(i=0;i<10;i++)
{
pid_t pid = fork();
if(pid == 0)
{
break;
}
}
if(i < 10)
{
printf("I am child:%d\n",getpid());
sleep(1);
}
else if(i == 10)
{
struct sigaction act;
act.sa_handler = sigchldHeadler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,NULL);
while(1)
{
printf("I am parent:%d\n",getpid());
sleep(1);
}
}
return 0;
}
当父进程在执行信号捕捉函数时,有子进程死亡。或者有多个子进程死亡。我们知道信号集是位图机制,是不支持排队的。
while((pid=waitpid(-1,&status,WNOHANG))>0)
当父进程的信号捕捉函数还没有注册,就已经有子进程结束了。导致僵尸进程…
示例2:
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <wait.h>
using namespace std;
void sigchldHeadler(int signo)
{
pid_t pid;
int status;
while((pid=waitpid(-1,&status,WNOHANG))>0)
{
printf("回收成功:%d ok\n",pid);
if(WIFEXITED(status))
{
printf("退出状态:%d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("退出状态(信号):%d\n",WTERMSIG(status));
}
}
}
int main(void)
{
//设置阻塞
sigset_t set;
sigemptyset(&set);
sigdelset(&set,SIGCHLD);
sigprocmask(SIG_BLOCK,&set,NULL);
//创建10个子进程
int i;
for(i=0;i<10;i++)
{
pid_t pid = fork();
if(pid == 0)
{
break;
}
}
if(i < 10)
{
printf("I am child:%d\n",getpid());
//sleep(1);
}
else if(i == 10)
{
struct sigaction act;
act.sa_handler = sigchldHeadler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,NULL);
while(1)
{
printf("I am parent:%d\n",getpid());
sleep(1);
}
}
return 0;
}
在fork之前将SIGCHLD信号设置屏蔽。导致僵尸进程…
6,清理僵尸进程
特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
方法一:使用wait() 或 waitpid()
方法二:杀死他的父进程使其变成孤儿进程,进而被系统处理。
7,孤儿进程
父进程先结束,而子进程仍然存活,此时子进程成为孤儿进程,将由系统的init进程负责回收相关资源。