Linux中的僵尸进程和孤儿进程

Linux中的僵尸进程和孤儿进程

孤儿进程

孤儿进程指的一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

子进程死亡需要父进程来处理, 那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时, 子进程死亡时没父进程处理, 这个死亡的子进程就是孤儿进程。

但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID, 损害运行系统。

僵尸进程

僵尸进程即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程.

下面有实例:

#include 
#include 
#include 
#include 
#include 

int main() {
  pid_t pid = fork();
  if (pid == 0) {
    // child
    printf("child pid: %d, parent pid:%d,\n", getpid(), getppid());
  } else {
    //parent
    while (1) {
      sleep(2);
      printf("I am parent!!!\n");
    }
  }
  exit(0);
}

在执行以后,我们可以在终端中使用ps -aux | grep pid打印进程, 发现:

brownfe+  8241  0.0  0.0      0     0 ?        Z    15:35   0:00 [zombie] 

可以看到结果, 子进程后有一个 , 表示僵尸进程. 因为子进程已经结束, 而其父进程并未释放其PCB,所以产生了这个僵尸进程.

一个进程在调用exit命令结束自己的生命的时候, 其实它并没有真正的被销毁, 而是留下一个称为僵尸进程Zombie的数据结构。

在Linux进程的状态中, 僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间,

这个僵尸进程需要它的父进程来为它收尸,如果他的父进程没有处理这个僵尸进程的措施,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。

系统调用exit的作用是进程退出, 也就是将一个正常的进程变成一个僵尸进程, 它并不能完全释放这个进程资源.

僵尸进程的处理方法

从前面我们知道, Linux任何一个进程(init除外)在退出(exit()或者return之后, 不会立刻释放自己的pcb, 而是变成僵尸进程(Zombie), 等待父进程处理.

如果父进程在子进程结束之前就退出了,那么子进程就会由init进程处理, 成为孤儿进程, 孤儿进程不会占用系统资源.

为了避免僵尸进程占用系统资源,常见有方法:

  1. 暴力杀死僵尸进程的父进程. 这样僵尸进程变成孤儿进程init回收.
  2. SIGCHLD信号+wait函数

wait函数和SIGCHLD信号

如果进程调用wait函数, 调用进程会阻塞自己, 由wait函数会分析当前进程的某个子进程已经退出, 如果有,wait函数会收集这个子进程的信息,并将其销毁以后返回.

#include 
#include 

pid_t wait(int *status)
waitpid...

其中status用来保存已经退出子进程的返回状态(exit(1)的返回内容). 该方法如果成功, 就返回被收集的子进程的进程ID, 如果调用进程没有子进程, 调用就会失败, 此时wait返回-1, 同时errno被置为ECHILD

另外一个关键点在于,子进程终止时, 内核就会向它的父进程发送一个SIGCHLD信号, 默认情况下进程会忽略这个信号. 我们可以修改信号处理逻辑, 专门处理这个信号, 在收到该信号时, 主动调用wait或者waitpit回收子进程的资源.

#include 
#include 
#include 
#include 
#include 
#include 
#include 

void deal_child(int num) {
  printf("deal_child into\n");
  wait(NULL);
}

int main() {
  signal(SIGCHLD, deal_child);
  pid_t pid = fork();
  if (pid == 0) {
    printf("I am child, pid=%d, ppid=%d\n", getpid(), getppid());
    printf("child is running...\n");
    sleep(2);
    printf("child will end...\n");
  } else if (pid > 0) {
    sleep(1); //让子进程先执行
    printf("parent is running\n");
    sleep(10); //一旦被打断就不能再进入睡眠
    printf("sleep 10 s over\n");
    sleep(5);
    printf("sleep 5s over\n");
  }
  exit(0);
}

你可能感兴趣的:(Linux中的僵尸进程和孤儿进程)