守护进程必须脱离终端控制,否则终端关闭会发送SIGHUP导致其终止;正确做法是调用setsid()创建新会话,或使用nohup/setsid命令,而systemd服务中禁止自行fork和setsid。

守护进程为什么必须脱离终端控制
Linux 守护进程(daemon)一旦启动就不再依赖终端,否则 ssh 断开或终端关闭会向其发送 SIGHUP,导致进程意外终止。根本原因在于:进程组组长退出时,内核默认向同组进程发送该信号;而普通前台进程天然属于终端控制的会话(session),受其生命周期绑定。
实操中常见错误是直接用 & 启动脚本(如 ./server.sh &),这仅让进程在后台运行,仍属于当前 shell 的会话和进程组,SIGHUP 依然会到达。
- 正确做法是调用
setsid()创建新会话,使进程脱离原控制终端 - 若用 shell 启动,应使用
nohup ./server.sh &或setsid ./server.sh(需安装util-linux) - Go/Python/C 等语言写 daemon 时,必须显式调用
setsid()(C)、os.setsid()(Python)或确保使用syscall.Syscall(syscall.SYS_SETSID, 0, 0, 0)(Go)
标准 daemon 化的三次 fork 是干什么用的
经典 Unix daemon 实现要求“三次 fork”,目的不是为了多开进程,而是精确解除三重绑定:
- 第一次 fork:子进程调用
setsid()成为会话首进程,脱离控制终端 - 第二次 fork:避免意外获得控制终端(POSIX 规定:会话首进程若打开未关联终端的设备文件,可能被赋予控制终端)
- 第三次 fork:确保进程不再是会话首进程,彻底失去获取控制终端的能力(某些旧内核行为)
现代 Linux(2.6+)已大幅收紧控制终端分配逻辑,实际中两次 fork + setsid() 已足够。但 systemd 服务、supervisord 等管理器不依赖此机制——它们本身接管了进程生命周期,无需 daemon 自行 fork。
systemd 下写 daemon 要不要自己做 setsid 和重定向
不用。systemd 明确禁止传统 daemon 自行 fork()、setsid()、关闭文件描述符或重定向 stdin/stdout/stderr。它会在启动时自动完成这些操作,并通过 Type= 配置项决定行为:
-
Type=simple(默认):systemd 认为可执行文件即主进程,立即监控其生命周期 -
Type=forking:适用于遗留 daemon,要求程序输出 PID 文件,systemd 读取后等待指定进程退出再认为启动完成 - 无论哪种类型,都应禁用日志重定向(如
nohup、> /dev/null 2>&1),否则日志无法被journalctl捕获
典型错误是:在 Type=simple 下仍调用 daemon(3) 或自行 fork(),导致 systemd 监控的是父进程(瞬间退出),服务状态显示为 “inactive (dead)”。
守护进程的标准文件描述符处理方式
即使不走 systemd,传统 daemon 也必须显式重定向 stdin、stdout、stderr 到安全位置,否则可能因继承父进程的终端 fd 导致阻塞或泄露敏感信息。
-
stdin应指向/dev/null(避免读操作阻塞) -
stdout和stderr通常指向日志文件,或统一由 syslog 接管(如调用openlog()) - 必须关闭所有非必要 fd(尤其是数据库连接、socket、配置文件句柄),防止资源泄漏或子进程继承
容易忽略的一点:重定向前要先 dup2() 保留 fd 0/1/2 的数字,否则后续 printf()、write(1, ...) 可能写到意外位置——比如某个被关闭又复用的 socket 上。










