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

Linux下按需启动守护进程:文件锁传递与原子性检查问题

问题描述

我想要编写一个可按需启动守护进程的程序,核心逻辑如下:

if_there_are_no_daemon_alive_start_daemon();
pass_job_to_daemon();

但我卡在了if_there_are_no_daemon_alive_start_daemon的实现上。最初我尝试对文件/run/lock/my-unique-name.lck加锁,若加锁成功则执行双fork启动守护进程,若失败则说明已有守护进程在运行,直接调用pass_job_to_daemon。但发现父进程获取的文件锁会在父进程退出后消失,示例代码如下:

#include <assert.h>
#include <fcntl.h>
#include <sys/file.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
        assert(argc > 1);
        int fd = open(argv[1], O_WRONLY | O_APPEND | O_CREAT, 0600);
        assert(fd >= 0);
        int ret = flock(fd, LOCK_EX);
        assert(ret == 0); 
        printf("get flock\n");
        pid_t pid = fork();
        assert(pid >= 0);
        if (pid == 0) {
             printf("child process\n");   
             sleep(5); //<-- no flock here according to lslocks
             printf("child done\n");                
        } else {
             printf("parent: time to exit\n");
        }
        return 0; 
}  

请问我可以使用哪种进程间锁,既能实现守护进程运行状态的原子性检查,又能在双fork后无问题地将锁传递给子进程?


解决方案

1. 修正文件锁的继承逻辑

你遇到的问题根源是:flock的锁与文件描述符绑定,父进程退出时会关闭所有打开的fd,导致锁被释放。要让守护进程持续持有锁,需要在双fork流程中保证锁对应的fd被最终的守护进程继承,且父进程要等子进程完成继承后再退出。

正确的双fork流程步骤:

  1. 父进程打开锁文件并获取排他锁
  2. 第一次fork出子进程A,父进程等待子进程A退出后再关闭fd(确保守护进程已经继承fd)
  3. 子进程A执行setsid()脱离终端,再fork出最终的守护进程(子进程B)
  4. 子进程A直接退出,子进程B保留锁文件的fd,持续持有锁

修正后的示例代码:

#include <assert.h>
#include <fcntl.h>
#include <sys/file.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    assert(argc > 1);
    int fd = open(argv[1], O_WRONLY | O_APPEND | O_CREAT, 0600);
    assert(fd >= 0);

    // 获取阻塞式排他锁,确保拿到锁再继续
    int ret = flock(fd, LOCK_EX);
    assert(ret == 0);
    printf("Parent got lock\n");

    pid_t pid1 = fork();
    assert(pid1 >= 0);

    if (pid1 == 0) {
        // 子进程A:脱离终端会话,成为会话首进程
        setsid();
        printf("Child A: created new session\n");

        // 第二次fork,生成最终的守护进程
        pid_t pid2 = fork();
        assert(pid2 >= 0);

        if (pid2 == 0) {
            // 守护进程:保留锁文件fd,持续持有锁
            printf("Daemon started, holding lock\n");
            // 这里替换为守护进程的业务逻辑
            sleep(30);
            printf("Daemon exiting, releasing lock\n");
            // 主动解锁并关闭fd(可选,进程退出时系统会自动处理)
            flock(fd, LOCK_UN);
            close(fd);
            return 0;
        } else {
            // 子进程A退出,不关闭fd,让守护进程继承
            printf("Child A exiting, daemon running\n");
            return 0;
        }
    } else {
        // 父进程等待子进程A退出,确保守护进程已继承fd
        waitpid(pid1, NULL, 0);
        printf("Parent exiting, lock held by daemon\n");
        // 父进程关闭fd,但守护进程的fd副本仍存在,锁不会释放
        close(fd);
        return 0;
    }
}

2. 结合PID文件做双重验证

仅靠文件锁可能存在异常场景(比如守护进程崩溃但锁未正常释放),建议同时维护一个PID文件:

  • 拿到锁后,将守护进程的PID写入/run/my-daemon.pid
  • 其他进程尝试启动时,除了尝试获取锁,还要读取PID文件,用kill(pid, 0)检查进程是否存活:
    • 如果kill返回0,说明进程正常运行,直接跳过启动
    • 如果kill返回错误(比如ESRCH),说明PID文件无效,可以强制获取锁并重新启动守护进程

3. 改用fcntl记录锁

如果需要更灵活的锁控制,可以用fcntl的记录锁替代flock。fcntl锁同样通过文件描述符继承,用法如下:

#include <fcntl.h>

// 设置整个文件的排他锁
struct flock lock = {
    .l_type = F_WRLCK,
    .l_whence = SEEK_SET,
    .l_start = 0,
    .l_len = 0, // 0表示锁定到文件末尾
    .l_pid = getpid()
};

// 获取锁,非阻塞的话用F_SETLK,阻塞用F_SETLKW
int ret = fcntl(fd, F_SETLKW, &lock);

fcntl锁的继承逻辑和flock一致,只要守护进程保留fd,锁就会持续有效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 17:35:19