wait WNOHANG 僵尸进程

本文详细解释了僵尸进程的概念及其产生的原因,并提供了几种有效避免僵尸进程的方法,包括通过信号处理函数循环调用waitpid来确保所有子进程被正确回收。
什么是僵尸进程?
首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。
而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。
任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。
如何避免僵尸进程
通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收
父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
如以下代码会创建100个子进程,但是父进程并未等待它们结束,所以在父进程退出前会有100个僵尸进程。
C
#include <stdio.h>
#include <unistd.h>
 
int main() {
 
  int i;
  pid_t pid;
 
  for(i=0; i<100; i++) {
    pid = fork();
    if(pid == 0)
      break;
  }
 
  if(pid>0) {
    printf("press Enter to exit...");
    getchar();
  }
 
  return 0;
}
其中一个解决方法即是编写一个SIGCHLD信号处理程序来调用wait/waitpid来等待子进程返回。
C
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
 
void wait4children(int signo) {
 
  int status;
  wait(&status);
 
}
 
int main() {
 
  int i;
  pid_t pid;
 
  signal(SIGCHLD, wait4children);
 
  for(i=0; i<100; i++) {
    pid = fork();
    if(pid == 0)
      break;
  }
 
  if(pid>0) {
    printf("press Enter to exit...");
    getchar();
  }
 
  return 0;
}
但是通过运行程序发现还是会有僵尸进程,而且每次僵尸进程的数量都不定。这是为什么呢?其实主要是因为Linux的信号机制是不排队的,假如在某一时间段多个子进程退出后都会发出SIGCHLD信号,但父进程来不及一个一个地响应,所以最后父进程实际上只执行了一次信号处理函数。但执行一次信号处理函数只等待一个子进程退出,所以最后会有一些子进程依然是僵尸进程。
虽然这样但是有一点是明了的,就是收到SIGCHLD必然有子进程退出,而我们可以在信号处理函数里循环调用waitpid函数来等待所有的退出的子进程。至于为什么不用wait,主要原因是在wait在清理完所有僵尸进程后再次等待会阻塞。
所以最佳方案(个人观点)如下:
C
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
 
void wait4children(int signo) {
  int status;
  while(waitpid(-1, &status, WNOHANG) > 0);
}
 
int main() {
 
  int i;
  pid_t pid;
 
  signal(SIGCHLD, wait4children);
 
  for(i=0; i<100; i++) {
    pid = fork();
    if(pid == 0)
      break;
  }
 
  if(pid>0) {
    printf("press Enter to exit...");
    getchar();
  }
 
  return 0;
}
<think> 嗯,用户想了解僵尸进程和孤儿进程的概念、产生原因、影响及处理方法。看了用户提供的四篇站内引用,内容都很相关,需要整合这些信息给出系统性的解释。 用户对僵尸进程和孤儿进程的理解可能存在误区。比如引用[1]中提到僵尸进程的元凶是父进程,而引用[2][3][4]都强调僵尸进程的产生是因为父进程没有正确处理子进程退出信号。需要澄清这些概念的区别。 孤儿进程的产生原因比较明确,就是父进程先于子进程退出(引用[1][2])。这里有个有趣的细节:孤儿进程会被init进程接管,这是Unix系统的设计特性。 关于影响方面,引用[1]提到僵尸进程过多会占用系统进程表资源。但引用[2]特别指出僵尸进程不占用内存和CPU,这点很重要,需要强调。用户可能担心资源耗尽问题,但实际危害是有限的。 处理方法上,四篇引用都指向同一个核心方案:正确处理SIGCHLD信号。引用[3][4]提到的wait/waitpid是关键技术点。突然想到用户可能还关心如何预防,这需要补充编程实践的建议。 用户提供的资料里有个矛盾点:引用[1]说守护进程会清理僵尸进程,但标准定义中守护进程并不负责这个。需要指出这个差异,避免用户误解。 最后考虑设计相关问题。用户既然问概念,可能后续会想了解实际编程中的处理技巧,或者如何在现有系统中排查这类问题。准备的问题要覆盖概念理解和实践操作两个维度。 </think> ### 僵尸进程和孤儿进程详解 #### **一、概念** 1. **僵尸进程(Zombie Process)** 子进程退出后,内核会保留其退出状态信息(PCB)直到父进程读取。若父进程**未调用 `wait()`/`waitpid()`** 回收子进程状态,该子进程成为僵尸进程(状态为 `Z`)[^3][^4]。 **特点**: - 不占用内存和CPU资源 - 占用系统**进程表项**(PID不可重用)[^2] *示例*:`ps aux` 中状态为 `Z` 的进程。 2. **孤儿进程(Orphan Process)** 父进程**先于子进程退出**,子进程成为孤儿进程,会被 **`init` 进程(PID=1)接管**并自动回收资源[^1][^2]。 **特点**: - 无害,系统自动处理 - 不会长期占用资源 --- #### **二、产生原因** | **进程类型** | **触发场景** | **关键机制** | |--------------|---------------------------------------------|----------------------------------| | 僵尸进程 | 父进程未处理子进程的 `SIGCHLD` 信号 | 子进程退出时发送 `SIGCHLD`[^3][^4] | | 孤儿进程 | 父进程意外终止(如被 `kill` 或崩溃) | `init` 进程自动接管[^1][^2] | --- #### **三、影响对比** | **类型** | **资源占用** | **系统危害** | **处理方** | |------------|--------------------|----------------------------------|------------------| | 僵尸进程 | 占用进程表项 | 大量堆积导致新进程无法创建 | 需手动回收 | | 孤儿进程 | 短暂占用后自动释放 | 无害 | `init` 自动回收 | > 📌 **关键区别**:僵尸进程因**父进程不作为**产生;孤儿进程因**父进程消失**产生[^1][^2]。 --- #### **四、处理方法** 1. **僵尸进程的解决** - **终止父进程**:父进程退出后,僵尸进程成为孤儿进程,被 `init` 回收[^1]。 ```bash kill -9 <父进程PID> # 强制终止父进程 ``` - **修改父进程代码**: ```c signal(SIGCHLD, SIG_IGN); // 忽略SIGCHLD信号(仅Linux支持) 或 while (waitpid(-1, NULL, WNOHANG) > 0); // 非阻塞回收所有子进程 ``` 2. **孤儿进程的预防** - 父进程使用 `waitpid()` **主动回收子进程**: ```c pid_t pid = fork(); if (pid == 0) { /* 子进程逻辑 */ exit(0); } else { waitpid(pid, NULL, 0); } // 父进程阻塞等待回收 ``` - 创建**守护进程**时需双重 `fork()`,确保孙子进程由 `init` 接管[^1]。 --- #### **五、编程实践建议** - **避免僵尸进程**:父进程必须通过 `wait()` 系列函数或注册 `SIGCHLD` 处理函数回收子进程[^4]。 - **处理孤儿进程**:无需额外操作,系统自动回收。 - **守护进程设计**:若需长期运行,应剥离与控制终端的关联,避免产生孤儿进程群[^1]。 ```c // 典型防僵尸进程代码(SIGCHLD处理) void sigchld_handler(int sig) { while (waitpid(-1, NULL, WNOHANG) > 0); // 非阻塞回收所有僵尸子进程 } int main() { signal(SIGCHLD, sigchld_handler); // 注册信号处理器 // ... 主逻辑 ... } ``` > 💡 **核心原则**:子进程退出状态必须被父进程或 `init` 读取,否则成为僵尸进程[^2][^4]。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值