我有这个 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
对于
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。