daemon 需要fork两次原因

本文详细解析了在创建后台进程时使用双fork与setsid的目的及作用。第一次fork和setsid确保子进程与父进程彻底分离;第二次fork则防止子进程成为会话组长,避免其打开新的控制终端。

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

参考文章:

http://blog.youkuaiyun.com/hepeng597/article/details/9816751
http://blog.youkuaiyun.com/ixidof/article/details/6715792


第一次fork后子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的首进程

也就是说第一次fork的的子进程的SID PGID都是首进程继续来的也就是主进程的   然后setsid是为了跟主进程的SID PGID脱离 都设置成子进程的SID PGID,虽然此时子进程已经被1接管了  但是只有setsid之后才算是跟那个主进程完全脱离,不受他的影响


第2次fork不是必须的。也看到很多开源服务没有fork第二次。fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前提条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。所以也无法打开新的控制终端。
此时这个子进程是首进程了 ,然后此时为了避免他是首进程,所以又fork了一次


然后总结就是:第一次fork和setsid是为了完全脱离主进程  第二次的fork纯粹就是为了让他不是首进程而已 

<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、付费专栏及课程。

余额充值