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)

相关推荐
数字新视界1 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落1 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨1 小时前
WSL2的安装与使用
linux·wsl
微小的鱼2 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng2 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮3 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
上火的金鱼妹3 小时前
k8s生产实际问题排查(网关,数据库读写等)
运维·服务器
孙克旭_3 小时前
k8s基础组件与创建资源
运维·docker·容器·kubernetes
孙克旭_3 小时前
云原生基础之Containerd容器运行时
运维·云原生
分布式存储与RustFS3 小时前
RustFS 自建对象存储实战目录:110 篇按场景分类(持续更新)
运维·云原生·开源·对象存储·分布式存储·s3·性能基准