You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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作为服务管理器,会严格监控服务主进程的状态:

  1. 双Fork逻辑中,原始主进程调用daemon_me后,第一次fork就退出了,systemd检测到主进程终止,会触发服务重启逻辑(取决于服务文件的Restart配置,默认多为on-failure或always)。
  2. 同时,systemd会向该服务的所有剩余进程发送SIGTERM信号,清理"孤儿"进程,导致双Fork后的最终子进程被杀死,无法执行后续的syslog(LOG_CRIT, "Success!");和循环逻辑。
  3. 手动运行时,父进程是shell,shell不会监控子进程的退出状态,双Fork后的子进程会被init/systemd收养,因此能正常持续运行。

解决办法

方法1:适配systemd的forking类型服务

修改systemd服务文件,明确告知systemd服务会通过fork方式启动,让它追踪子进程:

  1. 在服务文件(如/etc/systemd/system/your-service.service)中添加以下配置:
    [Service]
    Type=forking
    PIDFile=/var/run/your-program.pid
    
  2. 在代码的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);
    }
    
  3. 重新加载systemd配置并重启服务:
    sudo systemctl daemon-reload
    sudo systemctl restart your-service.service
    

方法2:移除手动双Fork逻辑,依赖systemd管理

systemd本身具备成熟的守护进程管理能力,无需手动实现双Fork:

  1. 删除代码中的daemon_me函数调用,让程序直接在前台运行:
    int main(int argc, char **argv) {
        // 移除daemon_me("programNAME");
        openlog("programNAME", LOG_PID, LOG_DAEMON);
        syslog(LOG_CRIT, "Success!");
        while(1);
    }
    
  2. 确保systemd服务文件使用默认的Type=simple:
    [Service]
    Type=simple
    ExecStart=/path/to/your/program
    # 可选:配置重启策略
    Restart=always
    
  3. 重新编译程序,加载systemd配置并重启服务即可。

这种方式更简洁,也更符合systemd的设计理念,避免手动守护进程逻辑与systemd的管理冲突。


内容的提问来源于stack exchange,提问作者Davide

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 17:34:58