Ubuntu 16.04下systemd多子进程场景如何设置PIDFile?
解决systemd Type=forking多子进程场景下的PIDFile问题
首先咱们得把问题根源说清楚:Type=forking类型的服务要求主进程fork后退出,且PIDFile里必须是最终留在后台运行的守护进程的有效PID。但你现在的逻辑是主进程直接fork多个子进程就退出了——如果PIDFile写主进程PID,主进程一退出这个PID就失效了;如果写子进程PID,又有多个子进程,且子进程退出后PID会变化,完全没法满足systemd的要求。
下面给你两个可行的解决方案,按需选择:
方案一:调整程序结构,增加管理进程(推荐)
这个方案完全符合Type=forking的设计规范,让systemd能正确追踪守护进程状态:
1. 修改tastic.c的进程逻辑
- 主进程先fork出一个管理进程,然后主进程直接退出;
- 管理进程负责fork所有工作子进程,并且进入循环监控子进程状态(比如用
waitpid()),如果某个工作进程意外退出,管理进程负责重启它; - 管理进程启动后立刻把自己的PID写入指定的PID文件(比如
/var/run/fantastic.pid),退出时记得删除这个文件。
核心逻辑示例代码:
#include <unistd.h> #include <fcntl.h> #include <sys/wait.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #define PID_FILE "/var/run/fantastic.pid" void cleanup(int sig) { unlink(PID_FILE); exit(0); } int main() { pid_t manager_pid = fork(); if (manager_pid < 0) { perror("fork manager failed"); exit(1); } // 主进程直接退出 if (manager_pid > 0) { exit(0); } // 管理进程:注册信号清理函数 signal(SIGTERM, cleanup); signal(SIGINT, cleanup); // 写入自身PID到文件 int fd = open(PID_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("open pid file failed"); exit(1); } char pid_str[20]; snprintf(pid_str, sizeof(pid_str), "%d\n", getpid()); write(fd, pid_str, strlen(pid_str)); close(fd); // 批量fork工作进程(示例为3个) for (int i=0; i<3; i++) { pid_t worker_pid = fork(); if (worker_pid == 0) { // 工作进程业务逻辑:处理请求、使用SO_REUSEPORT while(1) sleep(3600); // 模拟长期运行 exit(0); } else if (worker_pid < 0) { perror("fork worker failed"); } } // 管理进程持续监控子进程,自动重启退出的进程 while(1) { pid_t exited_pid = waitpid(-1, NULL, 0); if (exited_pid > 0) { // 重启一个工作进程 pid_t new_worker = fork(); if (new_worker == 0) { while(1) sleep(3600); exit(0); } } } return 0; }
2. 修改systemd unit文件
保持Type=forking,指定PIDFile路径,还可以加上RuntimeDirectory确保PID文件目录的权限安全:
[Unit] Description=Fantastic Service After=network.target [Service] Restart=always Type=forking ExecStart=/opt/fan/tastic PIDFile=/var/run/fantastic.pid # 可选:让systemd自动创建/run/fantastic目录,权限更安全 # RuntimeDirectory=fantastic # 若启用RuntimeDirectory,PIDFile路径改为/run/fantastic/fantastic.pid [Install] WantedBy=multi-user.target
这样systemd会等待主进程退出,然后读取PIDFile里的管理进程PID并监控它;管理进程负责维护所有工作子进程,完美契合systemd的管理逻辑。
方案二:改用Type=simple,无需PIDFile(无需大改程序,但有局限性)
如果不想调整程序结构,可以尝试把服务类型改成Type=simple,同时让主进程启动子进程后不退出(比如进入空循环等待信号):
1. 修改tastic.c的主进程逻辑
主进程fork完所有子进程后,不要退出,而是进入循环等待信号:
// ... fork所有工作子进程后 ... void cleanup(int sig) { // 可选:终止所有子进程 kill(0, SIGTERM); exit(0); } int main() { // ... fork子进程的代码 ... // 主进程不退出,等待终止信号 signal(SIGTERM, cleanup); while(1) { pause(); } }
2. 修改systemd unit文件
[Unit] Description=Fantastic Service After=network.target [Service] Restart=always Type=simple ExecStart=/opt/fan/tastic # 无需配置PIDFile [Install] WantedBy=multi-user.target
这个方案的局限性是:systemd只监控主进程状态,若主进程意外退出会重启整个服务,但主进程只是空转占位;如果工作子进程意外退出,主进程不会自动重启它们,需要额外实现监控逻辑(比如用waitpid),否则子进程退出后就无人维护了。
关键注意事项
- 确保PID文件的权限正确:程序要有写入路径的权限,或用
RuntimeDirectory让systemd创建安全的目录; - Type=forking的核心是主进程fork后退出,留下一个长期运行的守护进程,这个守护进程的PID必须写入PIDFile,systemd才能正确监控;
- 使用SO_REUSEPORT时,要确保所有工作进程都正确设置了该选项,避免端口占用冲突。
内容的提问来源于stack exchange,提问作者mur
相关产品推荐
相关产品推荐

