僵尸进程与孤儿进程

预备知识

fork函数

fork() 是 Unix 和 Unix-like 操作系统(如 Linux)中的一个系统调用,它用于创建一个新的进程,这个新的进程被称为子进程(child process),而原有的进程则被称为父进程(parent process)。

fork() 的基本特性:

  1. 复制当前进程fork() 会创建一个与父进程几乎完全相同的子进程,包括父进程的内存布局、环境变量、打开的文件描述符等。但是,子进程有自己的 PID(进程 ID)和 PPID(父进程 ID),并且有自己的独立地址空间(虽然一开始是父进程的副本)。
  2. 返回两次fork() 在父进程中返回一个正整数,这个整数是子进程的 PID;在子进程中,fork() 返回 0。如果发生错误,fork() 在父进程中返回 -1。
  3. 并发执行:父进程和子进程是并发执行的,它们的执行顺序取决于操作系统的调度策略。

使用示例(C 语言):

#include <stdio.h>  
#include <unistd.h>  
  
int main() {  
    pid_t pid = fork();  
  
    if (pid < 0) {  
        // fork failed  
        fprintf(stderr, "Fork failed\n");  
        return 1;  
    }  
  
    if (pid == 0) {  
        // child process  
        printf("I am the child process, my PID is %d, my PPID is %d\n", getpid(), getppid());  
    } else {  
        // parent process  
        printf("I am the parent process, my PID is %d, my child's PID is %d\n", getpid(), pid);  
    }  
  
    return 0;  
}

 

注意点:

  • 虽然子进程开始时是父进程的副本,但是它们的后续行为(如执行的代码、修改的数据等)是独立的。
  • 在 fork() 调用之后,父进程和子进程分别有自己的执行路径(通过 if-else 语句)。

进程状态

在Linux中,进程状态描述了进程在操作系统中的当前行为或条件。Linux进程主要有以下几种状态:

  1. R(运行/可运行)状态:进程正在运行或在运行队列中等待运行。这是进程可执行的状态,只有处于这个状态的进程才可能在CPU上运行。
  2. S(可中断的睡眠)状态:进程因为等待某个条件成立而被挂起(如等待I/O操作完成)。这个状态中的进程可以被信号中断。
  3. D(不可中断的睡眠)状态:进程也在等待某个条件成立而被挂起,但与S状态不同,这个状态的进程对信号是不响应的,即不能被中断。这通常用于内核进程等待硬件设备的响应。
  4. T(暂停/跟踪)状态:进程收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信号后停止运行。这个状态的进程不会被调度执行,直到收到SIGCONT信号。此外,进程被调试器跟踪时也会处于此状态。
  5. Z(僵尸)状态:进程已经终止,但父进程尚未通过wait()waitpid()系统调用来获取其终止状态。僵尸进程不占用除进程表项外的任何系统资源,但进程表项是有限的,因此如果系统中存在大量僵尸进程,可能会耗尽这些资源。
  6. X(死亡)状态:这个状态在Linux中实际上并不常见或不被直接标识。当进程终止并且其状态被父进程获取后,该进程会从系统中移除,不再存在。所以通常不会看到一个进程处于“死亡”状态。

僵尸进程

僵尸进程(Zombie Process) 是指在Unix和Unix-like操作系统(如Linux)中,一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占有的资源)获取终止状态的进程。僵尸进程虽然不占用除进程表项外的任何系统资源,但若系统中存在大量的僵尸进程,由于进程表项也是很有限的,也可能会导致系统资源的耗尽。

僵尸进程的特点:

  1. 进程已终止:僵尸进程已经完成了其执行过程,即代码已经执行完毕,但进程控制块(PCB)仍存在于内存中。
  2. 占用进程表项:由于进程控制块仍然保留,僵尸进程会占用进程表中的一个表项。如果系统中存在大量的僵尸进程,可能会耗尽进程表资源。
  3. 不可见:僵尸进程在常规的系统命令或工具中是不可见的,如ps命令默认不会显示僵尸进程。但可以使用特定的选项或工具来查看它们。
  4. 等待父进程处理:僵尸进程会等待其父进程通过wait()waitpid()系统调用来获取其终止状态。只有当父进程执行了这些系统调用后,僵尸进程才会从系统中被移除。

创建一个僵尸进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
  printf("parent begin pid:%d  ppid:%d\n",getpid(),getppid());
  pid_t id=fork();
  if(id<0)
  {
    perror("fork");
    return 1;
  }
  else if (id==0)
  {
    int cnt=3;
    while(cnt--)
    {
      printf("child pid:%d  ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
  }
  else
  {
    printf("parent pid:%d  ppid:%d\n",getpid(),getppid());
    sleep(10);
  }
  return 0;
}

结果以及进程状态

处理僵尸进程的方法:

  1. 父进程处理:父进程可以通过调用wait()waitpid()系统调用来获取子进程的终止状态,从而清理僵尸进程。这是处理僵尸进程的首选方法。
  2. init进程接管:如果父进程没有处理子进程的终止状态,并且父进程已经终止,那么子进程会被init进程(PID为1)接管。init进程会定期调用wait()函数来清理其子进程中的僵尸进程。但这种方法并不是实时的,并且可能会在系统重启之前留下一些僵尸进程。
  3. 重启系统:在极端情况下,如果系统中存在大量的僵尸进程并且无法通过其他方法清理,那么可能需要考虑重启系统来解决问题。但这种方法并不推荐,因为它会导致系统短暂的中断服务。

孤儿进程

孤儿进程在操作系统领域中是一个特定的概念。它指的是在其父进程执行完成或被终止后,仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

在类UNIX操作系统中,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。当一个父进程由于正常完成工作而退出或由于其他情况被终止,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。

为了避免孤儿进程退出时无法释放所占用的资源而僵死,进程号为1的init进程将会接受这些孤儿进程,这一过程也被称为“收养”。init进程会周期性地调用wait()系统调用来回收已经终止的子进程资源,因此孤儿进程就能被及时回收。

创建一个孤儿进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
  printf("parent begin pid:%d  ppid:%d\n",getpid(),getppid());
  sleep(1);
  pid_t id=fork();
  if(id<0)
  {
    perror("fork");
    return 1;
  }
  else if (id==0)
  {
    int cnt=5;
    while(cnt--)
    {
      printf("child pid:%d  ppid:%d\n",getpid(),getppid());
      sleep(2);
    }
  }
  else
  {
    sleep(1);
    printf("parent pid:%d  ppid:%d\n",getpid(),getppid());
  }
  return 0;
}

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值