最佳锁文件方法

时间:2009-10-21 08:31:05

标签: c++ unix file locking

Windows可以选择打开具有独占访问权限的文件。 Unix没有。

为了确保对某些文件或设备的独占访问,在Unix中通常的做法是使用通常存储在/ var / lock目录中的锁文件。

如果锁定文件已存在,则C指令open( "/var/lock/myLock.lock", O_RDWR | O_CREAT | O_EXCL, 0666 )返回-1,否则创建它。该函数是原子的,确保没有竞争条件。

释放资源后,将通过以下指令删除锁定文件 remove( "/var/lock/myLock.lock" )

此方法存在两个问题。

  1. 程序可以在不删除锁定的情况下终止。例如,因为它被杀死,崩溃或其他什么。锁定文件保持不变,即使不再使用,也会阻止对资源的任何访问。

  2. 使用组和世界写入权限创建锁定文件,但通常的做法是将帐户配置为使用将清除组和世界写入权限的权限掩码。因此,如果我们有一个可靠的方法来确定锁是孤立的(不使用),则不允许不是文件所有者的用户将其删除。

  3. 为了记录,我使用锁定文件来确保对连接到串行端口的设备(实际上是/ dev / ttyUSBx)的独占访问。咨询方法,需要合作,没关系。但应确保不同用户之间的独占访问。

    是否有比锁定文件更好的同步方法?如何确定创建锁文件的进程是否仍在运行? 如果不使用,如何让其他用户删除锁定文件?

    我想出的一个解决方案是将该文件用作Unix套接字文件。如果文件存在,请尝试使用该文件进行连接。如果失败,我们可能会认为文件的所有者进程已经死亡。这需要在所有者进程中的套接字accept()上循环一个线程。不幸的是,系统不再是原子的。

6 个答案:

答案 0 :(得分:34)

看看具有启发性的演示文稿File Locking Tricks and Traps

  

这篇简短的演讲介绍了文件锁定的几个常见缺陷,以及一些有效使用文件锁定的有用技巧。

修改:更准确地解决您的问题:

  

是否有比锁定文件更好的同步方法?

正如@Hasturkun已经提到的那样,正如上面的演示文稿所述,您需要使用的系统调用是flock(2)。如果您要在多个用户之间共享的资源已基于文件(在您的情况下为/dev/ttyUSBx),那么您可以 flock设备文件本身

  

如何确定创建锁文件的进程是否仍在运行?

您无需确定这一点,因为在关闭与您的文件关联的文件描述符时,flock - ed锁将自动释放,即使该进程已终止。

  

如果不使用,如何让其他用户删除锁定文件?

如果要锁定设备文件本身,则无需删除该文件。即使您决定使用/var/lock锁定flock中的普通文件,也不需要删除该文件以释放锁定。

答案 1 :(得分:21)

你可能应该使用flock(),如

fd = open(filename, O_RDWR | O_CREAT, 0666); // open or create lockfile
//check open success...
rc = flock(fd, LOCK_EX | LOCK_NB); // grab exclusive lock, fail if can't obtain.
if (rc)
{
    // fail
}

答案 2 :(得分:8)

Hasturkun的答案让我走上正轨。

这是我使用的代码

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>

/*! Try to get lock. Return its file descriptor or -1 if failed.
 *
 *  @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
 *  @return File descriptor of lock file, or -1 if failed.
 */
int tryGetLock( char const *lockName )
{
    mode_t m = umask( 0 );
    int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
    umask( m );
    if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
    {
        close( fd );
        fd = -1;
    }
    return fd;
}

/*! Release the lock obtained with tryGetLock( lockName ).
 *
 *  @param fd File descriptor of lock returned by tryGetLock( lockName ).
 *  @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
 */
void releaseLock( int fd, char const *lockName )
{
    if( fd < 0 )
        return;
    remove( lockName );
    close( fd );
}

答案 3 :(得分:4)

请谨慎使用答案中提到的实现的锁定和释放锁定功能,例如:

int tryGetLock( char const *lockName )
{
    mode_t m = umask( 0 );
    int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
    umask( m );
    if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
    {
        close( fd );
        fd = -1;
    }
    return fd;
}

和:

void releaseLock( int fd, char const *lockName )
{
    if( fd < 0 )
        return;
    remove( lockName );
    close( fd );
}

问题是releaseLock的remove调用引入了竞赛情况错误。让我们考虑一下三个过程都试图以讨厌的时机获取排他性羊群:

  • #1进程已打开锁定文件,获取了flock,将要调用解锁函数,但尚未完成。
  • 进程#2已调用open来打开指向名为lockName的文件,并且已经获得了该文件的文件描述符,但尚未调用flock。也就是说,由lockName指向的文件现在已打开两次。
  • 进程#3尚未启动。

在令人讨厌的时间安排下,进程#1可能首先调用remove()和close()(顺序无关紧要),然后进程#2使用已经打开的文件描述符调用flock。不再是名为lockName的文件,而是没有链接到任何目录项的文件描述符。

现在,如果启动进程3,则对它的open()调用将创建lockName文件,并获取对该文件的锁定,因为该文件未锁定。结果,进程#2和#3都认为它们都拥有对fileName的锁定->一个错误。

实现中的问题是remove()(或更多个unlink())仅取消链接目录条目中的名称-引用该文件的文件描述符仍然可用。一个人可以创建另一个具有相同名称的文件,但是已经打开的fd仍指向另一个位置。

这可以证明为锁定功能增加了延迟:

int tryGetLock( char const *lockName)
{
    mode_t m = umask( 0 );
    int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
    umask( m );
    printf("Opened the file. Press enter to continue...");
    fgetc(stdin);
    printf("Continuing by acquiring the lock.\n");
    if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
    {
        close( fd );
        fd = -1;
    }
    return fd;
}

static const char *lockfile = "/tmp/mylock.lock";

int main(int argc, char *argv[0])
{
    int lock = tryGetLock(lockfile);
    if (lock == -1) {
        printf("Getting lock failed\n");
        return 1;
    }

    printf("Acquired the lock. Press enter to release the lock...");
    fgetc(stdin);

    printf("Releasing...");
    releaseLock(lock, lockfile);
    printf("Done!\n");
    return 0;

}

  1. 尝试启动#1进程,然后按Enter键以获取锁。
  2. 然后在另一个终端上启动进程2,
  3. 在运行进程#1的终端上按另一个Enter键以释放锁定。 4.继续执行#2流程,方法是按一次Enter键以获取锁。
  4. 然后打开另一个要在其中运行进程3的终端。在其中,按一次Enter键即可获取锁定。

发生“不可能”事件:进程#2和#3认为它们都具有互斥锁。

至少在通常的应用程序中,这种情况在实践中可能很少发生,但是尽管如此,实现还是不正确的。

此外,以模式0666创建文件可能会带来安全风险。

我没有“发表评论的声誉”,这也是一个很老的问题,但是人们仍在引用此文件并做类似的事情,所以这就是为什么要添加此注释作为答案的原因。

答案 4 :(得分:1)

我正在使用chmike发布的代码,并注意到一个小的不完美。在打开锁定文件期间我遇到了比赛问题。有时,几个线程同时打开锁定文件。

因此,我删除了&#34;删除(lockName);&#34;来自&#34; releaseLock()&#34;功能。我不明白为什么,但不知怎的,这一行动有助于这种情况。

我一直在使用以下代码来测试锁文件。通过其输出,可以看到多个线程何时开始同时使用一个锁。

void testlock(void) {
  # pragma omp parallel num_threads(160)
  {    
    int fd = -1; char ln[] = "testlock.lock";
    while (fd == -1) fd = tryGetLock(ln);

    cout << omp_get_thread_num() << ": got the lock!";
    cout << omp_get_thread_num() << ": removing the lock";

    releaseLock(fd,ln);
  }
}

答案 5 :(得分:0)

扩展Hasturhun的答案。除了使用锁定文件的存在或不存在作为指示符之外,您还需要创建锁定文件(如果它不存在),然后获取文件的独占锁定。

这种方法的优点是,与许多其他同步程序的方法不同,如果您的程序在没有解锁的情况下退出,操作系统应该为您整理。

所以程序结构就像:

1: open the lock file creating it if it doesn't exist
2: ask for an exclusive lock an agreed byte range in the lock file
3: when the lock is granted then
    4: <do my processing here>
    5: release my lock
    6: close the lock file
end

在步骤:您可以阻止等待锁定被授予或立即返回。 您锁定的字节实际上不必存在于文件中。如果你能掌握Marc J. Rochkind的高级Unix编程的副本,他开发了一个完整的C库,它使用这种方法提供一种同步程序的方法,这些程序可以被操作系统整理,程序会退出。