systemd 中双 fork 后子进程不执行

问题描述 投票:0回答:1

我有这个 C 代码来守护 systemd 服务:

static void daemon_me(char *my_name) {
    pid_t new_pid;
    struct sigaction sig_act;
    int i;
    int f0, f1, f2;
    int my_mask;
    umask(0);

    if((new_pid = fork()) < 0) {
        err_exit("%s: Errore sulla fork", my_name);
    }
    else if(new_pid != 0) {
        exit(0);
    }
    syslog(LOG_CRIT, "PRINT0 pid %d", new_pid);
    setsid(); 

    if((new_pid = fork()) < 0) {
        syslog(LOG_CRIT, "PRINT1 pid %d", new_pid);
        err_exit("%s: Errore sulla fork", my_name);
    }
    else if(new_pid != 0) {
        syslog(LOG_CRIT, "PRINT2 pid %d", new_pid);
        exit(0);
    }
    syslog(LOG_CRIT, "PRINT3 pid %d", new_pid);

并且在第二次fork()之后它不会执行子进程。日志是:

PRINT0 pid 0
WAN_Application.out: PRINT2 pid 3009

如果我手动运行它,它工作正常:

PRINT0 pid 0
PRINT2 pid 3080
PRINT3 pid 0

为什么只使用 systemd 就会发生这种情况?

它应该具有相同的行为

编辑:完整示例

#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);
}

systemd 服务不断自行重启。手动启动它就会起作用。

EDIT2:操作系统信息 /etc/问题: Linux COM-Blade-0 4.19.0-25-amd64 #1 SMP Debian 4.19.289-1 (2023-07-24) x86_64 GNU/Linux

EDIT3:我尝试从

if(chdir("/") < 0)
到函数结束进行评论,但没有任何改变。

编辑 4:SIGTERM 处理的新示例。在使用 systemd 吃午餐时,它会打印:

term_handler: rcv 15
,即 SIGTERM

c linux fork daemon systemd
1个回答
1
投票

对于

Type=forking
服务,systemd 将初始进程退出视为启动已完成且服务现已准备就绪的指示 - 剩下的将成为主服务进程。

此时,服务处于“活动”状态,因此如果 that 进程随后退出(即使它是双分叉的一部分),systemd 会将其视为整个服务停止的指示。

解决方案1:不要在systemd中使用双叉。你实际上不需要它。当您的进程从服务管理器运行时,它从一开始就是一个守护进程 – 它已经处于“后台”;没有 tty 可供分离;没有任何会话可以离开setsid();没有要关闭的 FD – 双分叉或守护进程在这里没有任何作用。

因此,您只需

#if 0
所有代码并将 .service 单元切换为
Type=simple

(理想情况下,当守护进程实际上准备就绪时,您应该使用

Type=notify
并从 libsystemd 调用
sd_notify(0, "READY=1")
。这仍然与 Type=simple 相同,因为它不需要分叉,但具有更多优点准确的监控,只需要很少的改变。以后甚至可以添加一些
sd_notify(0, "STATUS=Brewing coffee (%d done)", progress)
之类的...)

解决方案 2:在分叉之前创建一个管道,然后让父进程退出之前等待,直到孙进程通过管道向其发送“就绪”指示。这是传统方法,将改善所有服务管理器(包括 sysv init)的服务行为,而不仅仅是 systemd。

© www.soinside.com 2019 - 2024. All rights reserved.