目录
[1. 头文件与常量定义](#1. 头文件与常量定义)
[2. Daemon 函数核心逻辑](#2. Daemon 函数核心逻辑)
[步骤 1:忽略可能导致异常退出的信号](#步骤 1:忽略可能导致异常退出的信号)
[步骤 2:让父进程退出,子进程成为 "孤儿进程"](#步骤 2:让父进程退出,子进程成为 "孤儿进程")
[步骤 3:创建新会话,脱离控制终端](#步骤 3:创建新会话,脱离控制终端)
[步骤 4:切换工作目录(可选)](#步骤 4:切换工作目录(可选))
[步骤 5:处理标准 I/O 描述符(关闭或重定向)](#步骤 5:处理标准 I/O 描述符(关闭或重定向))
1.守护进程
Daemon.hpp
cppC++ #pragma once #include <iostream> #include <cstdlib> #include <signal.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> const char *root = "/"; const char *dev_null = "/dev/null"; void Daemon(bool ischdir, bool isclose) { // 1. 忽略可能引起程序异常退出的信号 signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); // 2. 让自己不要成为组长 if (fork() > 0) exit(0); // 3. 设置让自己成为一个新的会话, 后面的代码其实是子进程在走 setsid(); // 4. 每一个进程都有自己的 CWD,是否将当前进程的 CWD 更改成为 /根目录 if (ischdir) chdir(root); // 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了 if (isclose) { close(0); close(1); close(2); } else { // 这里一般建议就用这种 int fd = open(dev_null, O_RDWR); if (fd > 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } } }这段代码是一个典型的守护进程(Daemon)初始化函数,用于将一个普通进程转换为守护进程。守护进程是在后台长期运行、脱离控制终端、独立于用户交互的进程(如系统服务、后台任务等)。下面逐部分解析其实现逻辑和作用:
一、核心目标
将调用该函数的进程转换为守护进程,使其具备以下特性:
- 脱离控制终端(不接收终端输入 / 输出,不受终端关闭影响);
- 在后台独立运行,不阻塞用户交互;
- 避免成为 "僵尸进程",长期稳定运行。
二、代码逐段解析
1. 头文件与常量定义
cpp
运行
#pragma once // 防止头文件重复包含(类似#ifndef的简化写法) #include <iostream> #include <cstdlib> // 包含exit()等函数 #include <signal.h> // 信号处理(signal()) #include <unistd.h> // 包含fork()、chdir()、setsid()等系统调用 #include <fcntl.h> // 文件操作(open()、dup2()等) #include <sys/types.h> #include <sys/stat.h> const char *root = "/"; // 根目录路径(用于切换工作目录) const char *dev_null = "/dev/null"; // 空设备路径(用于重定向I/O)这些头文件提供了进程控制、信号处理、文件操作等核心系统调用的声明,常量用于后续路径操作。
2. Daemon 函数核心逻辑
函数参数
ischdir控制是否切换工作目录,isclose控制是否直接关闭标准 I/O 描述符(或重定向到空设备)。步骤 1:忽略可能导致异常退出的信号
cpp
运行
signal(SIGCHLD, SIG_IGN); // 忽略子进程结束信号(防止僵尸进程) signal(SIGPIPE, SIG_IGN); // 忽略管道破裂信号(避免向已关闭管道写数据时崩溃)
SIGCHLD:子进程退出时会向父进程发送此信号,若忽略,内核会自动回收子进程资源,避免产生僵尸进程。SIGPIPE:当向一个已关闭的管道(或 socket)写入数据时产生,忽略可防止程序因该信号异常退出。步骤 2:让父进程退出,子进程成为 "孤儿进程"
cpp
运行
if (fork() > 0) // 父进程分支 exit(0); // 父进程退出,仅保留子进程
fork()创建子进程后,父进程立即退出,子进程会被操作系统的 init 进程(或 systemd 等进程管理器)收养,避免受原父进程退出的影响。- 更重要的是:此时子进程不是进程组组长 (进程组组长是原父进程,已退出),为下一步
setsid()做准备(setsid()要求调用进程不能是进程组组长,否则失败)。步骤 3:创建新会话,脱离控制终端
cpp
运行
setsid(); // 子进程创建新会话,成为新会话的首进程
setsid()的作用是:
- 子进程脱离原有的进程组和会话;
- 成为新会话的首进程和新进程组的组长;
- 最重要:脱离控制终端 (不再与原终端关联,不会收到终端的信号如
Ctrl+C产生的SIGINT)。步骤 4:切换工作目录(可选)
cpp
运行
if (ischdir) chdir(root); // 若需要,将工作目录切换到根目录 "/"
- 守护进程通常长期运行,若原工作目录被卸载(如挂载的外部设备),会导致进程异常。切换到根目录(
/,几乎不会被卸载)可避免此问题。步骤 5:处理标准 I/O 描述符(关闭或重定向)
cpp
运行
if (isclose) { // 直接关闭标准输入(0)、输出(1)、错误(2) close(0); close(1); close(2); } else { // 更常用:重定向到/dev/null(空设备,写入的数据会被丢弃) int fd = open(dev_null, O_RDWR); // 打开空设备(可读可写) if (fd > 0) { dup2(fd, 0); // 标准输入重定向到/dev/null(读不到数据) dup2(fd, 1); // 标准输出重定向到/dev/null(输出被丢弃) dup2(fd, 2); // 标准错误重定向到/dev/null close(fd); // 关闭原始fd,避免资源泄漏 } }
- 守护进程无需与用户交互,保留标准 I/O(关联原终端)没有意义,甚至可能因终端关闭导致 I/O 错误。
- 直接关闭描述符(
isclose=true)或重定向到/dev/null(isclose=false)是标准做法。/dev/null被称为 "黑洞",所有写入的数据会被丢弃,读取时直接返回 EOF,避免 I/O 阻塞。三、使用场景
调用
Daemon(true, false)后,当前进程就会变成一个标准的守护进程,例如:cpp
运行
int main() { Daemon(true, false); // 转换为守护进程 // 后续逻辑:如循环处理任务、监听端口等(长期运行) while (true) { sleep(1); // 模拟工作 } return 0; }四、总结
这段代码通过信号忽略、进程分叉、创建新会话、切换工作目录、重定向 I/O五个核心步骤,将普通进程转换为守护进程,使其具备后台运行、脱离终端、稳定持久的特性,是 Linux 后台服务开发的基础工具函数。
守护进程(linux)
板鸭〈小号〉2025-11-22 9:03
相关推荐
Leinwin11 小时前
微软与Anthropic深化战略合作,在Azure Foundry平台部署Claude系列AI模型JasonSJX2 天前
海海软件成为微软 PlayReady DRM 官方合作伙伴西焱4402 天前
微软官方直链下载(winxp,win8,win10,win11镜像下载)季春二九2 天前
微软 .Net 运行库丨多语言丨离线全集丨静默安装丨多架构支持Elastic 中国社区官方博客2 天前
Elasticsearch:Microsoft Azure AI Foundry Agent Service 中用于提供可靠信息和编排的上下文引擎初九之潜龙勿用2 天前
C# 使用豆包 AI 模型实现首尾帧模式的视频生成WangMing_X3 天前
C# XML操作演示示例项目(附源码完整)努力的光头强3 天前
《智能体设计模式》从零基础入门到精通,看这一篇就够了!Splashtop高性能远程控制软件3 天前
行业观察 | 微软修复63个漏洞,包含零日与CVSS 9.8关键漏洞