僵尸进程原理

2023-12-30 10:01:07

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,孤儿进程

父进程先结束,而子进程仍然存活,此时子进程成为孤儿进程,将由系统的init进程负责回收相关资源。

文章来源:https://blog.csdn.net/Bossking321/article/details/135195725
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。