04年时维护的第一个商业服务就用了两次fork产生守护进程的做法,前两天在网上看到许多帖子以及一些unix书籍,认为一次fork后产生守护进程足够了,各有道理吧,不过多了一次fork到底是出于什么目的呢?
### 谈谈守护进程和僵尸进程
#### 守护进程与僵尸进程的概念及应用场景
在深入探讨守护进程和僵尸进程之前,我们先简要回顾一下这两种进程的基本概念及其应用场景。
- **守护进程**(Daemon Process):是在后台运行的服务程序,通常用于执行一些长期性的任务或提供某种网络服务。例如,邮件服务、Web 服务等都需要通过守护进程来实现。守护进程的特点是没有控制终端,不会因用户退出或断开连接而停止工作,是独立于用户会话之外的。
- **僵尸进程**(Zombie Process):是已经结束但其父进程尚未调用 `wait()` 或 `waitpid()` 函数进行清理的进程。僵尸进程的存在仅仅是因为它们的父进程没有正常地清理它们的资源,它们的状态被标记为 “ZOMBIE”,占用系统的资源但不再执行任何操作。
#### 为什么需要两次 fork?
根据题目中的描述,04 年时维护的第一个商业服务采用了两次 fork 的方式来创建守护进程。那么,为什么要采用两次 fork 呢?这主要基于以下几个原因:
1. **避免 Session 领导者限制**:第一次 fork 之后,子进程不再是 session 领导者(session leader)。这很重要,因为 session 领导者不能捕获 SIGHUP 信号,这使得守护进程可以更好地处理终端挂起的情况。
2. **防止子进程继承父进程资源**:第二次 fork 主要是为了防止子进程继承父进程可能遗留下来的资源或状态,确保守护进程是一个干净的进程,不受父进程的影响。
3. **确保孤儿进程被 init 接管**:第二次 fork 后的子进程即使其父进程意外终止也会变成孤儿进程,并被 init 进程接管,从而避免进程状态的混乱。
#### 进程状态详解
在 Linux 中,进程状态包括但不限于:
- **TASK_RUNNING**:进程正在运行或者准备好运行。
- **TASK_INTERRUPTIBLE**:可中断的睡眠状态。
- **TASK_UNINTERRUPTIBLE**:不可中断的睡眠状态。
- **TASK_STOPPED** 和 **TASK_TRACED**:分别表示进程被暂停和正在被跟踪的状态。
- **EXIT_ZOMBIE** 和 **EXIT_DEAD**:进程已退出但未被清理的状态,其中 EXIT_ZOMBIE 表示僵尸状态。
#### 僵尸进程的形成与处理
僵尸进程是由父进程未能正确清理已终止的子进程而形成的。具体来说,当一个子进程终止时,它会被设置为僵尸状态。此时,子进程的资源仍然被保留,等待父进程通过调用 `wait()` 或 `waitpid()` 函数来回收这些资源。如果父进程没有这样做,则该进程将一直处于僵尸状态,占用系统资源直到父进程终止。
为了避免僵尸进程的产生,开发人员需要确保每个创建的子进程都能被正确地清理。一种常见的做法是在父进程中使用信号处理函数来响应子进程的终止信号,自动调用 `wait()` 或 `waitpid()` 来回收子进程的资源。
#### 守护进程的创建
守护进程的创建通常遵循以下步骤:
1. **第一次 fork**:创建一个子进程,让子进程脱离当前会话组,避免后续操作影响到终端。
2. **改变工作目录**:将当前工作目录更改为根目录 `/`,以避免占用文件系统上的任何特定目录。
3. **设置文件权限掩码**:通常设置为 `umask(0)`,这样守护进程创建的所有文件都有最宽松的权限。
4. **第二次 fork**:再次创建一个子进程,避免成为 session 领导者,从而能够捕捉 SIGHUP 信号。
5. **重定向标准输入、输出和错误**:将标准输入、输出和错误重定向到 `/dev/null`,以避免占用任何文件描述符。
6. **设置进程ID**:使用 `setsid()` 函数让进程成为一个新的会话的领导者,从而脱离原来的会话。
7. **关闭不必要的文件描述符**:确保所有不需要的文件描述符都被关闭,避免资源泄露。
通过上述步骤,我们可以创建出一个安全、可靠的守护进程,这样的守护进程能够有效地避免僵尸进程的产生,并且能够在后台持续运行,提供所需的服务功能。