僵尸进程与孤儿进程

一.fork( )函数

头文件:#include<unistd.h>   说明:<unistd.h>头文件是Linux中最重要的头文件,里面包含了操作系统对程序员提供的应用程序编程接口(API)。

函数原型:pid_t fork(void);

用途:用于创建一个子进程。

原理:父进程创建子进程的过程,就是以父进程为模板,把父进程的PCB(进程控制块,描述进程的数据结构)复制过来,稍加修改(通常改变pid/ppid),子进程和父进程的内存指针基本相同,上下文也基本相同(EIP),执行同一段代码。因为EIP(指令寄存器)也相同,故fork执行后,父子进程都要从fork之后的位置执行下去。而父进程先执行还是子进程先执行,取决于操作系统调度器的具体实现。

返回值:fork执行成功,父进程返回子进程的pid,子进程返回0。fork执行失败,返回-1.

创建失败的原因归结为:1.内存不够。2.创建的子进程太多。

#include<stdio.h>
#include<unistd.h>
int main()
{
    int ret=fork();
    if(ret>0) 
    {
       printf("father:%d\n",ret);  
    }
    else if(ret==0)
    {
      printf("child:%d\n:,ret);
    }
   else
   {
      perror("fork");
      return -1;

   }

   return 0;
}

二.僵尸进程(Z状态)

#include<stdio.h>
#include<unistd.h>
int main()
{
    int ret=fork();
    if(ret>0) 
    {
       printf("father:%d\n",ret);  
       while(1)
       {
       }
    }
    else if(ret==0)
    {
      printf("child:%d\n:,ret);
    }
   else
   {
      perror("fork");
      return -1;

   }

   return 0;
}

查看僵尸进程状态:

1.现象

子进程执行完,而父进程未读取子进程的返回结果,进程状态为Z。子进程为僵尸进程。

2.成因

一个进程结束了,但是它的父进程没有等待它,读取它的返回结果,这个进程就会变为僵尸进程。僵尸进程其实是子进程向父进程汇报工作结果的一种机制。在父进程查看结果之前,这样的结果不应该被释放掉,结果就保存在子进程的PCB中。如果父进程能够及时读取子进程的返回结果,僵尸进程也就没有了。

3.危害

子进程的PCB未被父进程读取,占用着一块内存,导致内存泄露。如果有大量的僵尸进程,则会造成一大块内存被占用,从而没办法再创建新的进程。

4.解决办法

(1)父进程通过wait函数和waitpid函数等待子进程结束,从而获取子进程的返回信息,释放子进程的PCB。

(a)wait函数:pid_t wait(int *status)

头文件:#include<sys/types.h>  和  #include<sys/wait.h>

参数:status参数是一个输出型参数,由操作系统填充。用于获取子进程的退出状态,不关心则可以设置为NULL。

返回值:成功返回子进程的pid,失败返回-1.失败的原因:没有子进程。

(b)waitpid函数:pid_t waitpid(pid_t pid,int *status,int options)

头文件:#include<sys/types.h> 和 #include<sys/wait.h>

参数:第一个参数pid为要等待的子进程pid。第二个参数status同wait函数。第三个参数是选项,可以是WNOHANG,WUNTRACED,WCONTINUED等参数。通常使用WNOHANG,含义是设置为非阻塞式等待。

返回值:若pid指定的子进程没有结束,则waitpid函数返回0,不予以等待。若正常结束,则返回子进程的pid。

说明:1.wait函数是阻塞式等待,通俗地说,就是父进程一直在等子进程,直到子进程结束才会返回。

而waitpid函数既可以阻塞式等待,也可以非阻塞式等待。通俗地说,非阻塞式等待指子进程未结束,也可以返回。

阻塞式与非阻塞式等待的比较:阻塞式等待比较省心,非阻塞式等待可以循环等待,可以在返回的时候充分利用时间,做其它事情。

2.waitpid函数如果第一个参数为-1,则任何一个子进程结束,都会被等待。

3.子进程的status:wait和waitpid都有status参数,该参数是一个输出型参数,由操作系统填充。如果status设置为NULL,表示不关心子进程的返回结果。否则,操作系统会根据此参数将子进程的退出信息反馈给父进程。status不能简单地当作整型来处理,可以当作位图来看待,我们只关心status的低16位比特位。

根据进程终止时status的不同,可以编写代码判断进程是否正常终止。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
   pid_t ret=fork();
   if(ret<0) 
   {

     perror("fork");
     return -1;

    }
    if(ret==0)
    {
       sleep(1);
      }
    if(ret>0)
    {
     sleep(5);
     int status=0;
     wait(&status);
     if(status&0xff)
     {

        printf("进程异常终止,终止信号=%d\n",status&0x7f);
      }
     else
     {
        printf("进程正常终止!退出码为:%d\n",(status>>8)&0xff);
      }
     }
     return 0;
}
     

用wait解决僵尸进程:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    int ret=fork();
    if(ret>0) 
    {
       wait(NULL);
       sleep(10);
       printf("pid:%d\n,getpid());//查看进程id
       printf("father:%d\n",ret);  
    }
    else if(ret==0)
    {
      sleep(1);
      printf("ppid:%d\n",getppid());//查看子进程的父进程id
      printf("child:%d\n:,ret);
    }
   else
   {
      perror("fork");
      return -1;

   }

   return 0;
}

用waitpid函数阻塞式等待,解决僵尸进程:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    pid_t ret=fork();
    if(ret<0)
    {
      perror("fork");
      return -1;
     }
    else if(ret==0)
         {
            printf("child:%d\n",ret);//子进程返回0
            printf(ppid:%d\n",getppid());//查看子进程的父进程id,判断是否是父进程接收。
            sleep(1);
         }
         else
         {
            printf("father:%d\n",ret);
            printf("pid:%d\n",getpid());
            sleep(10);
          } 
    return 0;
}

结果为:

用waitpid函数非阻塞式等待:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    pid_t ret1=fork();
    if(ret1<0)
    {
      perror("fork");
      return -1;
     }
    else if(ret1==0)
         {
            printf(ppid1:%d\n",getppid());//查看子进程的父进程id,判断是否是父进程接收。
            sleep(1);
         }
    pid_t ret2=fork();
    if(ret2<0)
    {
      perror("fork");
      return -1;
     }
    else if(ret2==0)
         {
            printf(ppid2:%d\n",getppid());//查看子进程的父进程id,判断是否是父进程接收。
            sleep(1);
         }
   while(1)
   {
      pid_t ret=waitpid(-1,NULL,WNOHANG);//ret>0,说明等到了子进程退出,但是我们可能不知道
//要等多少个子进程,所以需要继续尝试等。若ret=0,说明有子进程需要等待,只是子进程还没有退出,仍然
//需要尝试等。
      if(ret<0)//说明没有子进程可以等了,即把所有的子进程都已接收。
      {
         break;
      }
     //这里可以根据需要做一些其它的事情。
    }   
 return 0;
}

结果发现是同一个父进程接收了这两个子进程:

注意:这种情况下,如果调用的是wait函数,则调用wait函数的次数应该和创建子进程的个数相同。

(2)通过信号

子进程退出时向父进程发出SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理过程中调用wait函数处理僵尸进程。

(3)fork两次

通过fork两次,将子进程设为孤儿进程。从而孤儿进程的父进程变为init进程,通过init进程处理僵尸进程。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
   pid_ t ret=fork();//创建第一个子进程
   if(ret<0)
   {
     perror("fork");
     exit(0); 
   }

   else if(ret==0)
   {
      printf("here is the first child processID:%d\n",getpid());
      ret=fork();//创建第二个子进程
      if(ret<0)
      {
          perror("fork");
          exit(0);
       }
       else if(ret>0) 
      {//第一个子进程退出
         printf("the first process will be exited\n");
         exit(1);//退出后第二个进程变为了孤儿进程,所以第二个进程的父亲是init进程。
      }
       sleep(3);
       printf("here is the second process pid %d ppid:%d\n",getpid(),getppid());
       exit(0);
     }

       //父进程处理第一个子进程退出
       if(waitpid(ret,NULL,0)!=ret)
       { 
           perror("waitpid error:");
           exit(1);
       }

       exit(0);

       return 0;
}

三.孤儿进程

#include<stdio.h>
#include<unistd.h>
int main()
{
    int ret=fork();
    if(ret>0) 
    {
       printf("father:%d\n",ret);  
    }
    else if(ret==0)
    {
      sleep(10);
      printf("%d\n",getppid());//可以通过这行的输出查看子进程的父进程id.
      printf("child:%d\n:,ret);
    }
   else
   {
      perror("fork");
      return -1;

   }

   return 0;
}

1.现象

父进程结束,而子进程还在运行。父进程未读取到子进程的返回结果,子进程即为孤儿进程。

2.成因

父进程由于正常工作而退出或者被终止,但是它的子进程仍然在运行。

3.解决办法

操作系统的init进程(进程号为1)将这些孤儿进程接收,从而避免孤儿进程退出时无法释放所占用的资源引起的内存泄漏问题。因此不会有什么危害。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值