守护进程必须脱离终端控制,否则终端关闭会发送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= 配置项决定行为: