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流程步骤:
- 父进程打开锁文件并获取排他锁
- 第一次fork出子进程A,父进程等待子进程A退出后再关闭fd(确保守护进程已经继承fd)
- 子进程A执行
setsid()脱离终端,再fork出最终的守护进程(子进程B) - 子进程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
相关产品推荐
相关产品推荐

