最佳锁文件方法

chm*_*ike 34 c++ unix locking file

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

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

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

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

这种方法存在两个问题.

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

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

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

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

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

And*_*ikh 36

看看具有启发性的演示文件锁定技巧和陷阱:

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

编辑:更准确地解决您的问题:

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

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

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

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

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

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


Has*_*kun 22

你可能应该使用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
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意(在Linux上)任何不调用`flock`来检查现有锁的程序都能够任意读/写文件,即使存在另一个进程的独占锁. (13认同)

chm*_*ike 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 );
}
Run Code Online (Sandbox Code Playgroud)


Mii*_*nki 5

请小心使用答案之一中提到的实现的锁和释放锁功能,例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

和:

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

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

  • 进程#1已打开锁定文件,获取了该群集,并即将调用解锁函数,但尚未完成。
  • 进程#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;
Run Code Online (Sandbox Code Playgroud)

}

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

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

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

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

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