守护进程/僵尸进程/孤儿进程

本文深入解析Linux系统中孤儿进程与僵尸进程的概念,探讨其产生原因及危害,同时提供有效的解决方案,包括使用wait函数和守护进程技巧。

孤儿进程与僵尸进程

  在Linux当中,子进程是由父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步的过程,即父进程永远无法预测子进程到底什么时候结束。unix系统提供了一种机制可以让父进程获得子进程结束时的状态信息,也就是说在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存,但是仍然为其保留一定的信息,如进程号、退出状态、运行时间等。那么,当一个子进程完成它的工作终止之后,它的父进程就要调用wait()或者waitpid()系统调用来取得子进程的终止状态。

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

僵尸进程:一个进程使用 fork创建子进程,如果子进程退出,而父进程没有调用wait或waitpid函数获取子进程的状态信息,那么子进程的进程描述符仍然会保存在系统中。这种进程称为僵尸进程。

僵尸进程的危害:
  在子进程退出时,如果父进程不调用wait或waitpid函数的话,那么系统保留的子进程的信息就不会被释放,其子进程号会一直被占用,但是系统所能使用的进程号是有限的,如果产生大量的僵尸进程,最终可能导致系统没有可用的进程号,从而不能产生新的进程。寻求如何消灭系统中大量的僵死进程时,就是把产生大量僵死进程的那个父进程kill掉,即通过kill发送SIGTERM或者SIGKILL信号。父进程被kill掉之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源。

僵尸进程的解决方法:

  1. 使用signal函数显式忽略SIGCHLD信号
  2. 调用wait()函数或者waitpid()函数处理僵尸进程
  3. fork()两次,父进程fork子进程后继续执行,子进程fork一个孙进程后退出,此时孙进程会被init进程接管,避免僵尸进程

守护进程

  守护进程(daemon),是一种运行在后台的特殊进程,它独立于控制终端 ,并周期性地执行某项任务或等待处理某些发生的事件。守护进程可以由一个普通的进程按照守护进程的特性改造而来。基本的流程如下:
在这里插入图片描述
1)首先应该屏蔽一些有关控制终端操作的信息,防止在守护进程还没有正常运行起来前受到控制终端的干扰退出或挂起。

2) 前台转后台:首先在普通进程中调用fork函数之后,使父进程终止,让子进程继续执行,此时子进程称为孤儿进程,由于子进程是父进程的完全copy,而子进程又在后台运行,完成“脱壳”。

3)脱离控制终端,登录会话和进程组。如果想要普通进程脱离控制终端,登录会话和进程组,不受他们影响,可以使用setsid()设置新会话的首进程,使其与原来的登录会话和进程组自动分离。代码如下:

if (-1 == setsid()) exit(0);

setsid()函数说明如下:
pid_t  setsid(void);
返回值:成功返回进程组ID,出错返回-1函数说明:如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。结果为
(1)此进程变成新会话的会话首进程,此时,此进程是该新会话组中的唯一进程
(2)此进程变成一个新进程组的组长进程。新进程组ID是该调用进程的进程ID
(3)此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除

4)经过前面的3个步骤,该子进程已经脱离了控制终端,原来的登录会话和进程,似乎已经完成从普通进程到守护进程的转换,但是,对于某些系统,如SVR4,当会话首进程打开一个尚未与任何会话相关联的终端设备,此设备会自动作为控制终端分配给该会话。所以,我们需要采用不再让该子进程称为会话首进程的方式来禁止进程重新打开关联的控制终端。具体方法为再次调用fork函数,该fork函数执行后,子进程结束,那么孙子进程就不再是会话首进程,避免会话再次被关联到控制终端。当需要注意的是,当会话首进程退出的时候可能会向其所在的会话的所有进程发送SIGHUP信号,而SIGHUP信号的默认处理函数是结束进程,为了防止孙子进程意外结束,所以要忽略SIGHUP信号。

5)最后一步就是改变工作目录到根目录。因为进程活动时,其工作目录所在的文件系统不能被卸下,一般需要将工作目录改变到根目录。

6)重设文件创建掩模 (一些进程不用)
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);

补充:
操作系统的fork进程返回值?
  fork()是创建进程函数,子进程创建成功后,fork是返回两个值,一个代表父进程,一个代表子进程:代表父进程的值是一串数字,这串数字是子进程的ID(地址);一个代表子进程,值为0。

<think>我们正在讨论Linux中的三种特殊进程孤儿进程僵尸进程守护进程。用户要求解释它们的概念和区别。根据引用内容,我们可以这样组织: 1. **孤儿进程**:父进程先于子进程终止,子进程被init进程(PID=1)收养。引用[1]和[4]提到孤儿进程,并说明它们不会造成危害,因为init进程会负责回收。 2. **僵尸进程**:子进程终止,但父进程没有调用wait/waitpid来回收,导致子进程进程描述符仍保留在系统进程表中。引用[1][2][3]都提到了僵尸进程的危害:占用进程ID,可能导致无法创建新进程。 3. **守护进程**:一种在后台运行的特殊进程,通常脱离终端控制。引用[4]详细描述了创建守护进程的步骤(两次fork、setsid、关闭文件描述符等),并提到要忽略SIGCHLD信号以避免僵尸进程。 接下来,我们按照用户要求的结构进行回答: - 概念:分别解释三个术语 - 区别:对比它们的状态、产生原因、处理方式及影响 - 代码示例:引用[3]提供了僵尸进程的代码,引用[4]有守护进程的步骤(但无代码),我们可以补充一个简单的守护进程代码框架 - 相关问题:生成3-4个相关问题 注意:用户要求使用中文,数学公式用$...$,代码块单独标注,引用标识自然添加。 计划结构: ### 孤儿进程(Orphan Process) [定义,产生原因,init收养,无害] ### 僵尸进程(Zombie Process) [定义,产生原因(父进程未回收),危害,如何避免] ### 守护进程(Daemon Process) [定义,特点(后台、脱离终端),创建步骤,与孤儿进程的关系(守护进程通常由孤儿进程演变而来)] ### 对比总结 表格形式:名称 | 状态 | 产生条件 | 危害 | 处理方式 ### 代码示例 1. 僵尸进程示例(直接使用引用[3]的代码) 2. 守护进程示例(根据引用[4]的步骤编写伪代码或C代码片段) ### 相关问题 生成3-4个相关问题 注意:引用标识在相关概念后自然添加,如:僵尸进程会占用系统资源[^2]。</think>### 孤儿进程僵尸进程守护进程详解 #### **一、孤儿进程(Orphan Process)** - **概念**: 父进程先于子进程终止,子进程被`init`进程(PID=1)收养。 - **产生条件**: $$ \text{父进程退出时间} < \text{子进程退出时间} $$ - **特点**: - 无害:由`init`进程自动回收资源[^1] - 常见场景:后台服务启动子进程后异常退出 - **系统行为**: 内核将孤儿进程父进程ID重置为1,确保资源回收[^4] #### **二、僵尸进程(Zombie Process)** - **概念**: 子进程已终止,但父进程未调用`wait()`/`waitpid()`回收其退出状态。 - **产生条件**: $$ \text{子进程终止} \land \neg \text{父进程调用wait()} $$ - **特点**: - **资源占用**:保留进程控制块(PCB)占用进程ID[^1] - **危害性**: - 进程ID耗尽导致无法创建新进程 - 系统资源泄漏(如文件描述符未释放)[^2] - **示例代码**(创建僵尸进程): ```c #include <unistd.h> int main() { pid_t pid = fork(); if (pid == 0) exit(0); // 子进程立即退出 else sleep(100); // 父进程不调用wait() return 0; } ``` > 运行后通过`ps aux | grep Z`可查看僵尸进程[^3] #### **三、守护进程(Daemon Process)** - **概念**: 脱离终端控制的后台服务进程(如`sshd`, `nginx`)。 - **创建步骤**: 1. `fork()`后父进程退出 → 子进程成为孤儿[^4] 2. `setsid()`创建新会话 → 脱离终端控制 3. 再次`fork()` → 确保非会话首进程 4. 关闭文件描述符、切换工作目录到`/` 5. 重设文件掩码:`umask(0)` 6. 处理`SIGCHLD`:`signal(SIGCHLD, SIG_IGN)` - **特点**: - 无控制终端(TTY字段为`?`) - 生命周期与系统一致 - 忽略终端信号(如`SIGHUP`) --- ### **关键区别对比** | **特性** | 孤儿进程 | 僵尸进程 | 守护进程 | |----------------|----------------------------|----------------------------------|-----------------------| | **进程状态** | 运行中 | 已终止(Zombie) | 运行中 | | **父进程** | init进程(PID=1) | 原始父进程(未回收) | init进程 | | **资源占用** | 正常占用资源 | 仅占用PCB(不占内存/CPU)[^2] | 正常占用资源 | | **危害性** | 无害 | 可能导致进程ID耗尽[^1] | 无害 | | **回收方式** | 由init进程自动回收 | 需父进程调用`wait()`或杀死父进程 | 由init进程回收 | | **常见场景** | 父进程崩溃 | 父进程未正确处理子进程退出 | 系统服务(如nginx) | --- ### **典型问题处理方案** 1. **僵尸进程清除**: - 方案1:终止父进程僵尸进程由init自动回收) - 方案2:向父进程发送`SIGCHLD`触发`wait()` ```bash kill -s SIGCHLD <parent_pid> ``` 2. **守护进程创建要点**: - 必须二次`fork()`避免重新获取终端[^4] - 关闭所有文件描述符避免资源泄漏 ```c for (int fd = sysconf(_SC_OPEN_MAX); fd >= 0; fd--) close(fd); ``` 3. **孤儿进程预防**: 父进程应监控子进程状态,避免异常退出。 --- ### **相关问题** 1. 如何编程实现一个安全的守护进程? 2. 僵尸进程的PCB具体包含哪些关键信息? 3. 为什么守护进程需要调用`setsid()`? 4. `SIGCHLD`信号在进程回收中起什么作用? > 引用: > [^1]: Linux进程状态与资源管理机制 > [^2]: 僵尸进程对系统资源的影响分析 > [^3]: 进程控制实验:僵尸进程孤儿进程 > [^4]: 守护进程创建规范与安全实践
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值