Linux 守护进程与服务完整篇:从daemonize、systemd 到pidfile 与排障

手写 while(1) 后台程序一重启就没了、用 & 挂起却仍占着终端、或 systemd 单元 active (running) 却立刻退出------问题通常在 会话/控制终端、文件描述符、工作目录与监督模型,而不是业务循环本身。

传统 Unix 用「双重 fork + setsid」把进程从终端剥离;现代发行版则把长期服务交给 systemd(或同等 init) 监督:崩溃拉起、依赖排序、日志进 journal。本文覆盖经典 daemonize、为何优先用 systemd、pidfile/权限、信号与 Checklist。源 chapter(109--114)为提纲;正文按 POSIX/daemon(3) 与 systemd 实践重写。


源码锚点

路径/接口 作用
man 3 daemon / glibc daemon() 库封装的后台化(内部仍是 fork/setsid 一类步骤)
man 2 fork / setsid / chdir / umask / open 经典 daemonize 原语
man 7 signal / sigaction SIGTERM/SIGHUP 优雅退出与热加载
man 5 systemd.service unit 类型、Type=Restart=User=
man 1 systemctl / journalctl 启停与日志
/run / pidfile 惯例 运行时 pid、锁文件

经典步骤(理解用;新项目优先 systemd):

c 复制代码
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

static int daemonize(void)
{
	pid_t pid = fork();
	if (pid < 0) return -1;
	if (pid > 0) _exit(0);          /* 父进程退出 */

	if (setsid() < 0) return -1;    /* 新会话,脱离控制终端 */

	pid = fork();                   /* 再 fork,防止重新获得终端 */
	if (pid < 0) return -1;
	if (pid > 0) _exit(0);

	umask(0);
	if (chdir("/") < 0) return -1;

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);
	open("/dev/null", O_RDONLY);    /* fd 0 */
	open("/dev/null", O_RDWR);      /* 1 */
	open("/dev/null", O_RDWR);      /* 2 */
	return 0;
}

glibc 捷径(注意移植与行为差异):

c 复制代码
/* man 3 daemon: nochdir, noclose */
if (daemon(0, 0) < 0) {
	perror("daemon");
	exit(1);
}

最小 systemd unit 示例:

ini 复制代码
# /etc/systemd/system/myagent.service
[Unit]
Description=My Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/myagent --config /etc/myagent.conf
Restart=on-failure
RestartSec=2
User=myagent
Group=myagent
WorkingDirectory=/
# 不要在程序里再双重 fork;交给 systemd

[Install]
WantedBy=multi-user.target

调用链

经典双重 fork 脱离终端

#mermaid-svg-5HT8pwFHFOJBr15l{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5HT8pwFHFOJBr15l .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5HT8pwFHFOJBr15l .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5HT8pwFHFOJBr15l .error-icon{fill:#552222;}#mermaid-svg-5HT8pwFHFOJBr15l .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5HT8pwFHFOJBr15l .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5HT8pwFHFOJBr15l .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5HT8pwFHFOJBr15l .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5HT8pwFHFOJBr15l .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5HT8pwFHFOJBr15l .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5HT8pwFHFOJBr15l .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5HT8pwFHFOJBr15l .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5HT8pwFHFOJBr15l .marker.cross{stroke:#333333;}#mermaid-svg-5HT8pwFHFOJBr15l svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5HT8pwFHFOJBr15l p{margin:0;}#mermaid-svg-5HT8pwFHFOJBr15l .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5HT8pwFHFOJBr15l .cluster-label text{fill:#333;}#mermaid-svg-5HT8pwFHFOJBr15l .cluster-label span{color:#333;}#mermaid-svg-5HT8pwFHFOJBr15l .cluster-label span p{background-color:transparent;}#mermaid-svg-5HT8pwFHFOJBr15l .label text,#mermaid-svg-5HT8pwFHFOJBr15l span{fill:#333;color:#333;}#mermaid-svg-5HT8pwFHFOJBr15l .node rect,#mermaid-svg-5HT8pwFHFOJBr15l .node circle,#mermaid-svg-5HT8pwFHFOJBr15l .node ellipse,#mermaid-svg-5HT8pwFHFOJBr15l .node polygon,#mermaid-svg-5HT8pwFHFOJBr15l .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5HT8pwFHFOJBr15l .rough-node .label text,#mermaid-svg-5HT8pwFHFOJBr15l .node .label text,#mermaid-svg-5HT8pwFHFOJBr15l .image-shape .label,#mermaid-svg-5HT8pwFHFOJBr15l .icon-shape .label{text-anchor:middle;}#mermaid-svg-5HT8pwFHFOJBr15l .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5HT8pwFHFOJBr15l .rough-node .label,#mermaid-svg-5HT8pwFHFOJBr15l .node .label,#mermaid-svg-5HT8pwFHFOJBr15l .image-shape .label,#mermaid-svg-5HT8pwFHFOJBr15l .icon-shape .label{text-align:center;}#mermaid-svg-5HT8pwFHFOJBr15l .node.clickable{cursor:pointer;}#mermaid-svg-5HT8pwFHFOJBr15l .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5HT8pwFHFOJBr15l .arrowheadPath{fill:#333333;}#mermaid-svg-5HT8pwFHFOJBr15l .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5HT8pwFHFOJBr15l .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5HT8pwFHFOJBr15l .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5HT8pwFHFOJBr15l .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5HT8pwFHFOJBr15l .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5HT8pwFHFOJBr15l .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5HT8pwFHFOJBr15l .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5HT8pwFHFOJBr15l .cluster text{fill:#333;}#mermaid-svg-5HT8pwFHFOJBr15l .cluster span{color:#333;}#mermaid-svg-5HT8pwFHFOJBr15l div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5HT8pwFHFOJBr15l .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5HT8pwFHFOJBr15l rect.text{fill:none;stroke-width:0;}#mermaid-svg-5HT8pwFHFOJBr15l .icon-shape,#mermaid-svg-5HT8pwFHFOJBr15l .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5HT8pwFHFOJBr15l .icon-shape p,#mermaid-svg-5HT8pwFHFOJBr15l .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5HT8pwFHFOJBr15l .icon-shape .label rect,#mermaid-svg-5HT8pwFHFOJBr15l .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5HT8pwFHFOJBr15l .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5HT8pwFHFOJBr15l .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5HT8pwFHFOJBr15l :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} shell 启动进程
fork #1
父进程 _exit
子进程 setsid
fork #2
中间进程 _exit
孙进程继续
chdir / ; umask ; 重定向 0/1/2
进入服务主循环
处理 SIGTERM 优雅退出

现代监督:systemd 与进程的关系

#mermaid-svg-oU5ArdluQdROnX4v{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oU5ArdluQdROnX4v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oU5ArdluQdROnX4v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oU5ArdluQdROnX4v .error-icon{fill:#552222;}#mermaid-svg-oU5ArdluQdROnX4v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oU5ArdluQdROnX4v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oU5ArdluQdROnX4v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oU5ArdluQdROnX4v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oU5ArdluQdROnX4v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oU5ArdluQdROnX4v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oU5ArdluQdROnX4v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oU5ArdluQdROnX4v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oU5ArdluQdROnX4v .marker.cross{stroke:#333333;}#mermaid-svg-oU5ArdluQdROnX4v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oU5ArdluQdROnX4v p{margin:0;}#mermaid-svg-oU5ArdluQdROnX4v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oU5ArdluQdROnX4v .cluster-label text{fill:#333;}#mermaid-svg-oU5ArdluQdROnX4v .cluster-label span{color:#333;}#mermaid-svg-oU5ArdluQdROnX4v .cluster-label span p{background-color:transparent;}#mermaid-svg-oU5ArdluQdROnX4v .label text,#mermaid-svg-oU5ArdluQdROnX4v span{fill:#333;color:#333;}#mermaid-svg-oU5ArdluQdROnX4v .node rect,#mermaid-svg-oU5ArdluQdROnX4v .node circle,#mermaid-svg-oU5ArdluQdROnX4v .node ellipse,#mermaid-svg-oU5ArdluQdROnX4v .node polygon,#mermaid-svg-oU5ArdluQdROnX4v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oU5ArdluQdROnX4v .rough-node .label text,#mermaid-svg-oU5ArdluQdROnX4v .node .label text,#mermaid-svg-oU5ArdluQdROnX4v .image-shape .label,#mermaid-svg-oU5ArdluQdROnX4v .icon-shape .label{text-anchor:middle;}#mermaid-svg-oU5ArdluQdROnX4v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oU5ArdluQdROnX4v .rough-node .label,#mermaid-svg-oU5ArdluQdROnX4v .node .label,#mermaid-svg-oU5ArdluQdROnX4v .image-shape .label,#mermaid-svg-oU5ArdluQdROnX4v .icon-shape .label{text-align:center;}#mermaid-svg-oU5ArdluQdROnX4v .node.clickable{cursor:pointer;}#mermaid-svg-oU5ArdluQdROnX4v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oU5ArdluQdROnX4v .arrowheadPath{fill:#333333;}#mermaid-svg-oU5ArdluQdROnX4v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oU5ArdluQdROnX4v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oU5ArdluQdROnX4v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oU5ArdluQdROnX4v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oU5ArdluQdROnX4v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oU5ArdluQdROnX4v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oU5ArdluQdROnX4v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oU5ArdluQdROnX4v .cluster text{fill:#333;}#mermaid-svg-oU5ArdluQdROnX4v .cluster span{color:#333;}#mermaid-svg-oU5ArdluQdROnX4v div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oU5ArdluQdROnX4v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oU5ArdluQdROnX4v rect.text{fill:none;stroke-width:0;}#mermaid-svg-oU5ArdluQdROnX4v .icon-shape,#mermaid-svg-oU5ArdluQdROnX4v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oU5ArdluQdROnX4v .icon-shape p,#mermaid-svg-oU5ArdluQdROnX4v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oU5ArdluQdROnX4v .icon-shape .label rect,#mermaid-svg-oU5ArdluQdROnX4v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oU5ArdluQdROnX4v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oU5ArdluQdROnX4v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oU5ArdluQdROnX4v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进程面
管理面
stdout/stderr
Restart=
systemctl start/stop
myagent.service
journald
ExecStart 主进程 Type=simple
可选子进程


重点知识

1. 「后台」≠「守护进程」

做法 结果 问题
cmd & 后台但可能仍在同一会话 终端关闭可能收到 SIGHUP
nohup cmd & 忽略 HUP,输出进 nohup.out 无崩溃拉起、无依赖管理
双重 fork daemonize 脱离终端 无监督;日志与权限需自管
systemd/service 监督、依赖、日志一体 需正确写 unit,程序勿再抢 daemonize

生产环境:让 init 监督 ,程序保持前台(Type=simple)是默认最佳实践。

2. Type= 选错会导致「假死」或「误杀」

Type 含义 适用
simple ExecStart 即主进程 大多数长驻服务(推荐)
forking 进程自己 fork 后父退出,需 PIDFile= 老式 daemonize 程序
notify 就绪后 sd_notify 需要明确 ready 的服务
oneshot 跑完即结束 初始化脚本

把已 daemonize 的程序配成 simple,systemd 可能跟踪错 pid;把前台程序配成 forking 又会一直等 pidfile------二选一对齐

3. pidfile 与单实例

传统锁:

c 复制代码
/* 伪代码:打开 /run/myagent.pid,flock,写入 getpid() */

systemd 下多数场景 不需要 自管 pidfile;若必须兼容老工具,用 Type=forking + PIDFile= 并保证 fork 后写文件再通知就绪。

/run 为 tmpfs,重启清空------适合运行时文件,不要放配置。

4. 信号约定

信号 常见语义
SIGTERM 停止:刷盘、关 socket、退出 0
SIGINT 交互中断;服务里可同 TERM
SIGHUP 重载配置(若支持);systemd 可用 ExecReload=
SIGKILL 不可捕获;仅作最后手段
c 复制代码
static volatile sig_atomic_t stop;

static void on_term(int sig) { (void)sig; stop = 1; }

/* main 循环 while (!stop) ... 退出前清理 */

5. 日志:别再只写自己的文件

  • systemd 服务:打 stdout/stderr (或 StandardOutput=journal),用 journalctl -u myagent -f
  • 若必须文件日志:处理轮转、权限、磁盘满;仍建议同步一份到 journal
  • 千万别在关闭 fd 后继续 printf 到已重定向到 /dev/null 的描述符还以为有日志

6. 权限与安全

  • User=/Group= 降权;能力用 CapabilityBoundingSet=/AmbientCapabilities= 精细放权
  • 配置目录权限、密钥勿 world-readable
  • ProtectSystem=/ProtectHome=/NoNewPrivileges= 等沙箱指令按需打开

7. 常见坑

现象 对策
程序内双重 fork + Type=simple 服务立刻 inactive 去掉 daemonize 或改 forking
忽略 SIGTERM systemctl stop 卡住直至超时强杀 处理 TERM 并快速退出
工作目录在卸载点 异常或占住挂载 chdir("/") 或 unit 设 WorkingDirectory=
依赖网络未声明 启动过早 bind 失败 After=/Wants=network-online.target
& 部署 重启丢失 安装 unit 并 enable
日志全丢 关 fd 后无 journal 前台运行交给 systemd

8. 验证命令

bash 复制代码
sudo systemctl daemon-reload
sudo systemctl enable --now myagent
systemctl status myagent -l
journalctl -u myagent -n 100 --no-pager
# 崩溃拉起
sudo kill -9 $(systemctl show -p MainPID --value myagent)
sleep 2; systemctl is-active myagent

Checklist

  • 新服务默认 前台运行 + systemd Type=simple,不重复 daemonize
  • 若必须自 daemonize:与 Type=forking/PIDFile= 对齐
  • 处理 SIGTERM(及可选 SIGHUP),stop 可在超时前退出
  • 日志进入 journal 或明确的文件轮转策略
  • User= 降权;运行时文件落在 /run,配置在 /etc
  • 依赖目标(网络、本地挂载)在 unit 中声明
  • enable 后重启主机服务仍在;stop/restart 行为符合预期
  • 单实例有保障(systemd 或锁文件),避免双进程抢资源

合并自:Linux系统编程/chapters/109--114-守护进程与服务(2026-09-06)

相关推荐
毕竟是shy哥10 小时前
vs code连接远程服务器docker环境及配置免密登录
服务器·docker·vs code
徐小黑ACG11 小时前
nginx配置文件
linux·服务器·nginx
新时代牛马11 小时前
Linux VFS 完整篇:从path_openat、dentry/inode 到page cache 与挂载排障
linux·运维·服务器
java_logo11 小时前
Docker 部署 Rocky Linux:轻松搭建 RHEL 兼容企业级基础镜像平台
linux·docker·容器·rocky linux·基础镜像·轩辕镜像·rhel 兼容
拂拉氏11 小时前
【知识讲解】 Linux虚拟地址空间认识
linux·虚拟地址空间
cloudfantasy13 小时前
docker直接二进制安装,非编译。KylinV10SP3 aarch64有效
运维·docker
新时代牛马13 小时前
Linux 驱动中断与定时完整篇:从 request_threaded_irq 到hrtimer 选型与排障
linux·运维·服务器
考虑考虑14 小时前
nginx打印请求日志
运维·后端·nginx
数字补给站15 小时前
Windows 10 + Hyper-V + Terraform + cloud-init:批量产出 3 台 Ubuntu 的实践笔记
运维·ai编程