有同学会很好奇,为什么这里又来一次守护进程,之前进程间关系不是已经说过一次了吗?
没错,之前的确是讲过,不过那时候我们只是讲了守护进程的原理,实现方法,然而这一次并不是讲守护进程怎么实现。
1. 问题提出
实际上之前我们写的守护进程,如果多次启动,就会产生多个相同的进程。比如下面这样:
图1 启动几次就有几个实例
进程 processd 就是一个守护进程,每次启动都会产生一个实例,实际上很多时候守护进程都是服务进程,我们并不希望系统中存在多份实例。如果同时运行多个实例,每个实例都会进行相同的操作,造成错误。比如系统中的 cron 守护进程,这是一个定时任务程序,如果存在多个 cron 进程,那就糟糕了。
2. 单例守护进程
如果一个程序被启动多次,系统中仍然只存在一份实例,这种进程就是单例进程。放到守护进程上来说,就是单例守护进程。
一般来说,就算你多次启动它也没关系,它内部提供了检测机制,对进程进行了保护。接下来要讲的就是这种机制是如何实现的。
3. 实现单例守护进程
它采用的技术就是之前我们学习的记录锁。只要点破这层纸,我相信很多同学已经知道怎么写程序了。
其原理很简单,启动进程的时候,对约定的文件进行加锁,如果加锁失败,说明已经有进程对其加过锁了,直接退出。
4. 程序清单
singleprocessd 演示了如何实现一个单例守护进程。
4.1 代码
// singleprocessd.c
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
// 对文件加锁
int lockfile(int fd) {
struct flock flk;
flk.l_type = F_WRLCK;
flk.l_start = 0;
flk.l_whence = SEEK_SET;
flk.l_len = 0;
// 非阻塞方式加锁
return fcntl(fd, F_SETLK, &flk);
}
int already_running(void) {
int fd;
char buf[16];
// 输出日志文件
FILE* fp = fopen("/tmp/singleprocess.log", "a");
// 锁文件
fd = open("/var/run/singleprocess.pid", O_RDWR | O_CREAT, 0644);
if (fd < 0) {
fprintf(fp, "can't open /var/run/singleprocess.pid: %s\n", strerror(errno));
fclose(fp);
exit(1);
}
if (lockfile(fd) < 0) {
if (errno == EACCES || errno == EAGAIN) {
fprintf(fp, "singleprocess already running: %s\n", strerror(errno));
close(fd);
fclose(fp);
return 1;// 表示已被加锁
}
fprintf(fp, "can't lock /var/run/singleprocess.pid: %s\n", strerror(errno));
fclose(fp);
exit(1);
}
ftruncate(fd, 0);// 截断成 0
sprintf(buf, "%d", getpid());
write(fd, buf, strlen(buf) + 1);
return 0; // 加锁成功
}
int main() {
if (daemon(0, 0) < 0) {
perror("daemon");
return 1;
}
if (already_running()) {
return 1;
}
while(1) {
sleep(10);
// do something
}
return 0;
}
4.2 编译和运行
- 编译
gcc singleprocessd.c -o singleprocessd
- 运行
运行的时候用 root 权限,因为它要向 /var/run 下面写文件
图2 单例守护进程
如图 2 所示,singleprocessd 程序启动了 4 次,但是最终只有一个实例在运行。日志 /tmp/singprocess.log 中记录了后三次运行历史。
锁文件 /var/run/singprocess.pid 中保存的是实例的 pid,和 ps 命令中查看的结果是一致的。
5. 总结
- 理解什么是单例守护进程
- 为什么要采用单例守护进程
- 利用记录锁实现单例守护进程