Daemon进程为何需要两次fork

本文深入探讨了守护进程的概念及其在后台运行的特性,详细解释了创建守护进程时进行双Fork操作的原因与步骤,确保进程与控制终端完全隔离,实现真正的后台运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Daemon进程

守护进程(daemon)是指在UNIX或其他多任务操作系统中在后台执行的电脑程序,并不会接受电脑用户的直接操控。此类程序会被以进程的形式初始化。守护进程程序的名称通常以字母“d”结尾:例如,syslogd就是指管理系统日志的守护进程。(来自维基百科

常见的Apache服务器(httpd),MySQL服务端(mysqld),作业规划进程(crond)都是以守护进程的方式运行于Linux系统中。

创建Daemon

守护进程最重要的特性是常驻后台运行,因此必须要与当前的运行环境完全隔离,这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。

关于如何创建Daemon进程可以参考这篇daemon进程原理及实现,这里就不再赘述。

为何两次Fork

这是创建Daemon进程的大致步骤及作用:

  • 第一次fork(产生父子进程,父进程退出,子进程与父进程控制终端脱离)
  • 子进程setsid(子进程成为会话组长,与父进程会话脱离)
  • 第二次fork(产生子孙进程,子进程退出,孙进程成为Daemon最终进程)
  • 切换工作目录、设置文件创建掩模、关闭所有打开文件句柄等

原则上来说在第一次fork后,子进程即与父进程的控制终端脱离关系,父进程退出后,子进程被init接管,基本达到了Daemon进程要求。但还差了一点,子进程与父进程还在同一个会话组,因此子进程需要调用setsid以达到与父进程会话脱离。也就是这个setsid,使子进程成为了新的会话组组长,却导致了新的问题产生:在Linux中会话组长可以重新申请打开一个控制终端。为了彻底与控制终端断绝关系,我们需要一个非会话组长的进程,子进程的子进程正是我们要的。(The first process in the process group becomes the process group leader and the first process in the session becomes the session leader. Every session can have one TTY associated with it. Only a session leader can take control of a TTY. For a process to be truly daemonized (ran in the background) we should ensure that the session leader is killed so that there is no possibility of the session ever taking control of the TTY.)

参考:
1. 维基百科 守护进程
2. daemon进程原理及实现
3. What is the reason for performing a double fork when creating a daemon?

<think>我们正在讨论一个使用双重fork的多进程服务器程序。用户提供的代码片段中包含了两次fork两次close(sock)操作。我们需要解释双重fork的原理以及两次close(sock)的作用。首先,回顾代码片段:1.第一次fork创建守护进程daemon)```cvoiddaemon_fork(){intpid=-1;pid=fork();if(pid<0){//错误处理}elseif(pid>0){//父进程退出exit(0);}else{//子进程(守护进程)继续运行return;}}```2.第二次fork:在处理客户端连接时创建进程```cvoidsubprocess(intsock){intpid=-1;pid=fork();if(pid<0){//错误处理}elseif(pid>0){//父进程(这里可能是主进程或者守护进程)关闭客户端socket并返回close(sock);return;}else{//子进程:关闭监听socket,然后处理客户端通信close(listen_fd);//注意:这里代码片段中写的是close(listen_fd),但参数是sock,可能是笔误?实际上应该是关闭监听套接字processMsg(sock);exit(0);}}```另外,在代码中我们还看到,在创建守护进程后,主程序会继续运行(作为守护进程),然后设置监听等。当有客户端连接时,会调用`subprocess`函数来处理。现在,我们解释双重fork两次close(sock)的原因。###双重fork的目的1.**第一次fork创建守护进程)**:目的是将程序变为守护进程daemon)。守护进程是在后台运行的进程,通常不受终端控制。第一次fork后,父进程退出,子进程继续运行(成为守护进程)。这样做的目的是让出终端控制,并且让子进程成为init进程的子进程(这样就不会有僵尸进程的问题,因为init会回收其子进程)。但是,这里只进行了一次fork,按照传统的守护进程创建步骤,通常还会进行setsid等操作,但代码中并没有体现,所以这里可能只是一个简单的后台运行。2.**第二次fork(处理客户端请求)**:当守护进程(主进程)接收到一个客户端连接时,它会调用`subprocess`函数,在该函数中再次fork。这次fork的目的是为每个客户端连接创建一个独立的子进程来处理通信。这样,服务器可以同时处理多个客户端连接。###两次close(sock)的作用在`subprocess`函数中,我们看到了两次关闭套接字:1.**在父进程中关闭`sock`**:```celseif(pid>0){close(sock);return;}```这里的父进程是处理客户端连接请求的进程(即守护进程)。在fork出子进程后,父进程不再需要这个客户端连接套接字(因为通信将由子进程处理),所以关闭它。这样可以释放父进程的资源,避免文件描述符泄漏。2.**在子进程中关闭`listen_fd`**:```celse{close(listen_fd);processMsg(sock);exit(0);}```子进程负责与客户端通信,它不需要监听套接字(`listen_fd`),所以关闭监听套接字。然后,子进程使用客户端套接字(`sock`)与客户端进行通信。通信结束后,子进程退出。###为什么需要双重fork?在传统的守护进程创建中,双重fork(即两次fork)是为了确保守护进程不会成为会话组长,从而避免重新获取终端控制权。但是,在这个代码片段中,第一次fork并没有调用setsid,所以实际上并没有完成完整的守护进程创建。因此,这里的第一次fork可能只是为了将进程放到后台运行(父进程退出,子进程继续)。而第二次fork是为了创建处理客户端请求的子进程。这是多进程服务器的典型做法:主进程负责监听,每当有一个新连接,就创建一个子进程来处理该连接。###双重fork的完整守护进程创建通常,创建守护进程的完整步骤包括:1.第一次fork:父进程退出,子进程继续。2.子进程调用setsid创建一个新会话,并成为该会话的组长。3.第二次fork:确保守护进程不是会话组长,防止它重新获取终端。4.改变工作目录到根目录。5.重设文件权限掩码。6.关闭不必要的文件描述符。但是,在用户提供的代码中,只有一次fork(在`daemon_fork`中),所以它并不是一个完整的守护进程创建。因此,这里的双重fork实际上是指:第一次fork创建守护进程)和第二次fork创建处理客户端请求的子进程)的组合。###总结-**两次fork**:-第一次fork:将主进程变为守护进程(后台运行)。-第二次fork:为每个客户端连接创建一个子进程来处理通信。-**两次close(sock)**:-在父进程(守护进程)中关闭客户端套接字:因为父进程不需要与客户端通信,关闭以释放资源。-在子进程中关闭监听套接字:因为子进程不需要监听新连接,关闭以释放资源。注意:在子进程中关闭监听套接字并不会影响父进程中的监听套接字,因为fork之后文件描述符被复制,但是关闭操作只影响当前进程的描述符。###引用说明根据引用[3]和[4],多进程服务器通过fork进程来处理每个客户端连接,避免主进程被阻塞,从而实现并发。引用[3]还提到了僵尸进程的问题,但在这个代码中,子进程退出后由init进程回收(因为父进程已经退出)或者通过其他方式回收(如信号处理),但代码片段中未体现回收子进程的部分,可能需要完善。引用[2]提到多线程和多进程的设计思路类似,主线程/进程负责监听,子线程/进程负责通信。[^1]:用户提供的代码片段[^2]:使用多进程和多线程实现服务器并发[^3]:TCP/IP网络编程(3)——多进程服务器实现[^4]:高并发服务器-使用多进程(Multi-Process)实现###相关问题根据用户的问题,我们生成了以下相关问题:1.在多进程服务器中,如何避免僵尸进程?2.除了多进程,还有哪些方法可以实现高并发服务器?3.为什么在子进程中要关闭监听套接字,在父进程中要关闭客户端套接字?4.如何设置守护进程的完整步骤?5.在多进程服务器中,如果父进程先于子进程退出,会发生什么?</think>### C语言双重fork实现多进程服务器的原理 在C语言多进程服务器中,双重`fork()`主要用于实现**守护进程化**和**客户端连接处理**两个关键目标。以下是核心原理和作用分析: #### 1. 第一次`fork()`:创建守护进程 ```c void daemon_fork() { pid = fork(); // 第一次fork if (pid > 0) exit(0); // 父进程退出 } ``` - **目的**:将程序转为后台守护进程(脱离终端控制) - **作用**: - 父进程退出后,子进程被init进程接管,成为独立服务 - 解除终端关联,避免程序被信号意外终止 - **关闭`sock`无关**:此阶段尚未涉及套接字操作 #### 2. 第二次`fork()`:处理客户端连接 ```c void subprocess(int sock) { pid = fork(); // 第二次fork if (pid > 0) { close(sock); // 父进程关闭客户端socket } else { close(listen_fd); // 子进程关闭监听socket processMsg(sock); // 处理通信 } } ``` #### 3. 两次`close(sock)`的作用 | 关闭位置 | 关闭对象 | 作用原理 | |------------------|----------------|-------------------------------------------------------------------------| | **父进程** | 客户端`sock` | 释放父进程的文件描述符,避免资源泄漏。子进程已继承副本,不影响通信[^3][^4] | | **子进程** | 监听`listen_fd` | 子进程无需监听新连接,关闭后可释放资源,专注处理当前客户端通信[^2][^4] | #### 4. 双重fork的整体流程 ```mermaid graph TD A[主进程] -->|fork1| B[父进程退出] A -->|fork1| C[守护进程] C -->|accept| D[新客户端连接] D -->|fork2| E[父进程关闭client_sock] D -->|fork2| F[子进程关闭listen_fd] F --> G[处理客户端请求] ``` #### 5. 关键设计思想 1. **资源隔离** 父子进程各自关闭不需要的socket: - 父进程保留`listen_fd`继续监听新连接 - 子进程保留`client_sock`处理专属客户端 2. **并发能力** 每个客户端连接由独立子进程处理,避免阻塞主监听循环[^4] 3. **僵尸进程预防** 通过`fork()`后父进程立即关闭子进程不需要的资源,配合信号处理或`waitpid()`避免僵尸进程[^3] > **注意**:代码片段中`close(listen_fd)`的参数名应为`sock`(客户端socket),实际需要关闭的是监听socket(通常命名为`listen_fd`)[^1] ### 相关问题 1. **在多进程服务器中,如何避免僵尸进程的产生?** 2. **为什么进程需要关闭监听套接字,而父进程要关闭客户端套接字?** 3. **除了多进程,还有哪些方法可以实现高并发服务器?** 4. **双重fork创建的守护进程与普通后台进程有何区别?** 5. **在多进程服务器中,如果父进程意外退出会对子进程造成什么影响?** [^1]: 原始代码片段中的套接字管理 [^2]: 多进程/多线程服务器设计原理 [^3]: 僵尸进程的产生与回收机制 [^4]: 高并发服务器资源隔离策略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值