Linux C Flock 使用

2018-08-17 Friday     linux , program

在某些场景下,例如需要保证单个进程运行,通常的做法是生成一个 PID 文件,并将当前的进程 PID 写入,每次进程启动时检查文件以及进程是否存在。

如果进程异常崩溃没有删除文件,而 Linux 中 PID 可以复用,那么就可能会导致误认为进程存在,虽然概率很低。

其实在 Linux 中可以通过 flock 实现。

Flock

在 Linux 中有个简单的实现,也就是 flock() ,这是一个建议性锁,不具备强制性。

也就是说,一个进程使用 flock 将文件锁住,另一个进程仍然可以操作正在被锁的文件,修改文件中的数据,这也就是所谓的建议性锁的内核处理策略。

#include <sys/file.h>

int flock (intfd, int operation);

其中 flock() 主要有三种操作类型:

  • LOCK_SH,共享锁,多个进程可以使用同一把锁,常被用作读共享锁;
  • LOCK_EX,排他锁,同时只允许一个进程使用,常被用作写锁;
  • LOCK_UN,释放锁;

默认的操作是阻塞,可以通过 LOCK_NB 设置为非阻塞。

脚本

另外,在命令行中,也可以通过类似如下的方式进行测试。

$ flock -xn /tmp/foobar.lock -c "echo 'Hi world'"

常见的如 crontab 。

PIDFile

默认进程在使用 flock 尝试锁文件时,如果文件已经被其它进程锁住,进程会被阻塞直到锁被释放掉。也可以使用 LOCK_NB 参数,此时如果被锁,那么会直接返回错误,对应的 errnoEWOULDBLOCK

简单来说,也就是阻塞、非阻塞两种工作模式。

flock 可以通过 LOCK_UN 显示的释放锁,也可以直接通过关闭 fd 的来释放文件锁,这意味着 flock 会随着进程的关闭而被自动释放掉。

注意事项

在使用如下测试时,需要保证 /tmp/foobar.txt 存在。

同一进程

在同一个进程中可以多次进行加锁而不会阻塞,可以通过如下方式进行测试。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>

int main(void)
{
        int rc, fd;

        fd = open("/tmp/foobar.txt", O_RDWR);
        printf("current fd: %d\n", fd);
        rc = flock(fd, LOCK_EX);
        printf("get lock rc: %d\n", rc);
        rc = flock(fd, LOCK_EX);
        printf("get lock again, rc: %d\n", rc);

	sleep(1000);

	return 0;
}

当启动第二个程序时就会被阻塞掉。

文件描述符

flock 创建的锁是和文件打开表项 struct file 相关联的,而不是文件描述符 fd

也就是通过 fork() 或者 dup() 复制 fd 后,可以通过这两个 fd 同时操作锁,但是关闭其中一个 fd 锁并不会释放,因为 struct file 并没有释放,只有关闭所有复制出的 fd,锁才会释放。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>

int main(void)
{
        int rc, fd1, fd2;

        fd1 = open("/tmp/foobar.txt", O_RDWR);
        fd2 = dup(fd1);
        close(fd1);

        printf("current fd: %d\n", fd2);
        rc = flock(fd2, LOCK_EX);
        printf("get lock2, ret: %d\n", rc);
        sleep(10);
        close(fd2);
        printf("release\n");

        sleep(10000);

        return 0;
}

如上,在关闭掉所有的文件描述符之后才会释放掉文件锁。

子进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>

int main(void)
{
        int rc, pid, fd;

        fd = open("/tmp/foobar.txt", O_RDWR);

        pid = fork();
        if (pid == 0) { /* child */
                rc = flock(fd, LOCK_EX);
                printf("chile get lock, fd: %d, ret: %d\n", fd, rc);
                sleep(10);
                printf("child exit\n");
                exit(0);
        }

        rc = flock(fd, LOCK_EX);
        printf("parent get lock, fd: %d, ret: %d\n", fd, rc);
        sleep(12);
        printf("parent exit\n");

        return 0;
}

子进程持有锁,并不影响父进程通过相同的 fd 获取锁,反之亦然。

多次打开

当使用 open() 两次打开同一个文件,得到的两个 fd 是独立的,内核会使用两个 struct file 对象,通过其中一个加锁,通过另一个无法解锁,并且在前一个解锁前也无法上锁。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>

int main(void)
{
        int rc, fd1, fd2;

        fd1 = open("/tmp/foobar.txt", O_RDWR);
        fd2 = open("/tmp/foobar.txt", O_RDWR);
        printf("fd1: %d, fd2: %d\n", fd1, fd2);

        rc = flock(fd1, LOCK_EX);
        printf("get lock1, ret: %d\n", rc);
        close(fd1);

        rc = flock(fd2, LOCK_EX);
        printf("get lock2, ret: %d\n", rc);

        return 0;
}

上述代码中,如果注释掉 close(fd1) 会被阻塞。



如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^


About This Blog

Recent Posts

Categories

Related Links

  • RTEMS
    RTEMS
  • GNU
  • Linux Kernel
  • Arduino

Search


This Site was built by Jin Yang, generated with Jekyll, and hosted on GitHub Pages
©2013-2018 – Jin Yang