Systemd中双Fork后子进程未执行及服务重启问题排查
问题描述
用户编写了一段采用双Fork模式的C代码,用于将程序作为systemd服务实现守护进程化。手动运行该程序时,日志正常输出PRINT0、PRINT2、PRINT3,程序可持续运行;但通过systemd作为服务启动时,日志仅输出PRINT0和PRINT2,双Fork后的子进程未执行,服务还会不断重启,且捕获到SIGTERM(信号15)。操作系统为Debian 4.19.0-25-amd64,注释chdir相关代码后问题依旧。
代码实现
#include <string.h> /* strrchr, strerror, strcat, memset */ #include <syslog.h> /* openlog, syslog, closelog, setlogmask */ #include <stdlib.h> /* strtol, exit */ #include <stdio.h> /* fputs, vsnprintf, snprintf, fflush */ #include <sys/types.h> /* umask, open, accept, setsockopt */ #include <sys/stat.h> /* umask, open */ #include <sys/resource.h> /* getrlimit */ #include <sys/socket.h> /* accept, shutdown, setsockopt */ #include <stdarg.h> /* va_start */ #include <unistd.h> /* fork, setsid, chdir. close, dup, sync, sleep */ #include <signal.h> /* sigemptyset, sigaction */ #include <fcntl.h> /* open */ #include <sys/mman.h> /* mlockall */ #include <sys/un.h> /* sockaddr_un */ #include <sys/shm.h> /* shmdt, shmctl */ #define MAX_MSG_SIZE (512) char main_strbuff[MAX_MSG_SIZE]; static void term_handler(int signo) { syslog(LOG_CRIT, "term_handler: rcv %d.", signo); closelog(); exit(0); } /* term_handler */ static void err_handle(int has_errno, int my_errno, const char *my_args, va_list my_list) { char my_string[MAX_MSG_SIZE]; /* Buffer di diagnostica */ vsnprintf(my_string, MAX_MSG_SIZE, my_args, my_list); if(has_errno) { snprintf(my_string + strlen(my_string), MAX_MSG_SIZE - strlen(my_string), ":%s", strerror_r(my_errno, main_strbuff, MAX_MSG_SIZE)); } strcat(my_string, "\n"); fflush(stdout); fputs(my_string, stderr); fflush(NULL); } static void err_exit(const char *my_args, ...) { va_list args_list; va_start(args_list, my_args); err_handle(0, 0, my_args, args_list); va_end(args_list); exit(1); } static void err_print(const char *my_args, ...) { va_list args_list; va_start(args_list, my_args); err_handle(0, 0, my_args, args_list); va_end(args_list); } static void daemon_me(char *my_name) { struct rlimit file_lim; pid_t new_pid; struct sigaction sig_act; int i; int f0, f1, f2; int my_mask; umask(0); sig_act.sa_handler = SIG_IGN; sigemptyset(&sig_act.sa_mask); sig_act.sa_flags = 0; if(sigaction(SIGHUP, &sig_act, NULL) < 0) { syslog(LOG_CRIT, "Impossibile ignorare SIGHUP"); exit(1); } sig_act.sa_handler = term_handler; sigemptyset(&sig_act.sa_mask); sig_act.sa_flags = SA_INTERRUPT; if(sigaction(SIGTERM, &sig_act, NULL) < 0) { /* Errore di sistema */ syslog(LOG_CRIT, "Impossibile configurare SIGTERM"); exit(1); } if((new_pid = fork()) < 0) { err_exit("%s: Errore sulla fork", my_name); } else if(new_pid != 0) { exit(0); } setsid(); if((new_pid = fork()) < 0) { err_exit("%s: Errore sulla fork", my_name); } else if(new_pid != 0) { exit(0); } } int main(int argc, char **argv) { daemon_me("programNAME"); syslog(LOG_CRIT, "Success!"); while(1); }
日志情况
- 手动运行日志:
PRINT0 pid 0 PRINT2 pid 3080 PRINT3 pid 0
- Systemd启动日志:
PRINT0 pid 0 WAN_Application.out: PRINT2 pid 3009 term_handler: rcv 15
用户疑问:为何仅在systemd环境下出现该问题,手动运行则正常?
问题分析与解决
核心原因
systemd作为服务管理器,会严格监控服务主进程的状态:
- 双Fork逻辑中,原始主进程调用
daemon_me后,第一次fork就退出了,systemd检测到主进程终止,会触发服务重启逻辑(取决于服务文件的Restart配置,默认多为on-failure或always)。 - 同时,systemd会向该服务的所有剩余进程发送SIGTERM信号,清理"孤儿"进程,导致双Fork后的最终子进程被杀死,无法执行后续的
syslog(LOG_CRIT, "Success!");和循环逻辑。 - 手动运行时,父进程是shell,shell不会监控子进程的退出状态,双Fork后的子进程会被init/systemd收养,因此能正常持续运行。
解决办法
方法1:适配systemd的forking类型服务
修改systemd服务文件,明确告知systemd服务会通过fork方式启动,让它追踪子进程:
- 在服务文件(如
/etc/systemd/system/your-service.service)中添加以下配置:[Service] Type=forking PIDFile=/var/run/your-program.pid - 在代码的
daemon_me函数末尾,将最终子进程的PID写入上述PID文件:// 添加在daemon_me函数最后一个exit(0)之后 FILE *pid_file = fopen("/var/run/your-program.pid", "w"); if (pid_file) { fprintf(pid_file, "%d\n", getpid()); fclose(pid_file); // 设置文件权限,避免安全问题 chmod("/var/run/your-program.pid", 0644); } - 重新加载systemd配置并重启服务:
sudo systemctl daemon-reload sudo systemctl restart your-service.service
方法2:移除手动双Fork逻辑,依赖systemd管理
systemd本身具备成熟的守护进程管理能力,无需手动实现双Fork:
- 删除代码中的
daemon_me函数调用,让程序直接在前台运行:int main(int argc, char **argv) { // 移除daemon_me("programNAME"); openlog("programNAME", LOG_PID, LOG_DAEMON); syslog(LOG_CRIT, "Success!"); while(1); } - 确保systemd服务文件使用默认的
Type=simple:[Service] Type=simple ExecStart=/path/to/your/program # 可选:配置重启策略 Restart=always - 重新编译程序,加载systemd配置并重启服务即可。
这种方式更简洁,也更符合systemd的设计理念,避免手动守护进程逻辑与systemd的管理冲突。
内容的提问来源于stack exchange,提问作者Davide
相关产品推荐
相关产品推荐

