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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:49:49