仅运行一个程序的单个实例

一些程序,特别是很多的daemon,需要确保同一时刻只有一个程序实例在系统中运行。完成这项任务的一个常见方法是:使用记录锁。

      让daemon创建一个文件并在该文件上放置一把写锁。daemon在其执行期间一直持有这个文件锁并在即将终止之前删除这个文件。如果启动了daemon的另一个实例,那么它在获取该文件上的写锁时就会失败,其结果是它会意识到daemon的另一个实例肯定正在运行,然后终止。(很多网络服务器采用了另一种常规做法,即将服务器绑定的众所周知的socket端口号已经被使用时,就认为该服务器实例已经处于运行状态了)

      unix的/var/run目录常常是存放此类锁文件的位置。或者可以在daemon的配置文件中加一行来指定文件的位置。

      通常,daemon会将其进程ID写入锁文件,因此这个文件在命名时候,通常将.pid作为扩展名(如rsyslog会创建文件/var/run/rsyslog.pid)。这对于那些需要找出daemon的进程ID的应用程序讲是比较有用的。它可以允许执行额外的检查,像可以使用kill(pid, 0)来检查进程ID是否存在(在较早的不提供文件加锁的UNIX实现上,这是一种不完美但很实用的方法,用于检查一个daemon实例是否在运行或前一个实例在终止之前是否没有成功删除这个文件)

      我们这里使用劝告式记录锁测试。

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

 

void errExit()

{

      fprintf(stderr, "%s\n", strerror(errno));

      exit(1);

}

 

int

lockfile(int fd)

{

      struct flock fl;

 

      fl.l_type = F_WRLCK;

      fl.l_start = 0;

      fl.l_whence = SEEK_SET;

      fl.l_len = 0;

      return (fcntl(fd, F_SETLK, &fl));

}

 

int main()

{

      int fd;

      fd=open("./onlyone", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);      //create temp file

 

      if(fd==-1)

           errExit();

 

      if(lockfile(fd)==-1)//lock temp file

           errExit();

           //daemon(0,1);

 

      for(;;);

}

启动一个该实例后,再启动一个该实例时候,fcntl(fd, F_SETLK,&fl)加锁的时候将返回-1,置errno为EACCES or EAGAIN,strerror(errno)为Resource temporarily unavailable,资源暂时不可用。

      接下来我们将main中的daemon()去掉注释,以将该程序转为后台运行。结果试着启动3次程序,并没有报错出来。ps –ef| grep 程序名。发现有3个实例在后台运行。

      想到daemon化,是通过两次fork实现的,而fork后,子进程并不会继承fcntl()记录锁,所以daemon()后锁没继承下来。这个约束是有道理的,因为记录锁的作用是阻止多个进程同时写同一个文件。如果子进程通过fork继承父进程的锁,则父进程和子进程就可以同时写同一文件。这里我们可以将加锁动作放到daemon之后,解决这个问题。另外fcntl()加锁是在SUSv3中进行了标准化的。而flock()并没有标准化,各个类UNIX实现语义可能不一样,像Linux的flock(),在使用其创建锁时,子进程会继承一个引用同一把锁的引用并且能够释放这把锁,从而导致父进程也会失去这把锁。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章