Linux 中关于文件锁的系统调用:flock和fcntl

http://blog.sina.com.cn/s/blog_5e0fa98601012ejz.html


部分转载,并且加上个人试验程序与总结。

这里介绍在 Linux 中与文件锁关系密切的两个系统调用:flock() 和 fcntl()。劝告锁既可以通过系统调用 flock() 来实现,也可以通过系统调用 fcntl() 来实现。flock() 系统调用是从 BSD 中衍生出来的,在传统的类 UNIX 操作系统中,系统调用flock() 只适用于劝告锁。但是,Linux 2.6内核利用系统调用 flock() 实现了我们前面提到的特殊的强制锁:共享模式强制锁。另外,flock() 只能实现对整个文件进行加锁,而不能实现记录级的加锁。系统调用fcntl() 符合 POSIX 标准的文件锁实现,它也是非常强大的文件锁,fcntl() 可以实现对纪录进行加锁。

flock()

flock() 的函数原型如下所示:

int flock(int fd, int operation);

其中,参数 fd 表示文件描述符;参数 operation 指定要进行的锁操作,该参数的取值有如下几种:LOCK_SH, LOCK_EX, LOCK_UN 和 LOCK_MANDphost2008-07-03T00:00:00

man page 里面没有提到,其各自的意思如下所示:

  • LOCK_SH:表示要创建一个共享锁,在任意时间内,一个文件的共享锁可以被多个进程拥有
  • LOCK_EX:表示创建一个排他锁,在任意时间内,一个文件的排他锁只能被一个进程拥有
  • LOCK_UN:表示删除该进程创建的锁
  • LOCK_MAND: 它主要是用于共享模式强制锁,它可以与 LOCK_READ 或者 LOCK_WRITE 联合起来使用,从而表示是否允许并发的读操作或者并发的写操作(尽管在 flock() 的手册页中没有介绍 LOCK_MAND,但是阅读内核源代码就会发现,这在内核中已经实现了)

通常情况下,如果加锁请求不能被立即满足,那 么系统调用 flock() 会阻塞当前进程。比如,进程想要请求一个排他锁,但此时,已经由其他进程获取了这个锁,那么该进程将会被阻塞。如果想要在没有获得这个排他锁的情况下不阻 塞该进程,可以将 LOCK_NB 和 LOCK_SH 或者 LOCK_EX 联合使用,那么系统就不会阻塞该进程。flock() 所加的锁会对整个文件起作用。


个人测试程序:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/file.h>



FILE * g_lockfile = NULL;

int main(int argc, char ** argv)
{
    char *lockfile = "/root/text.txt";
    int ret = -1;
    char rcvbuf[4096];
    char *ptr = NULL;

   
    if (g_lockfile != NULL)
    {
        return 0;
    }

    g_lockfile = fopen(lockfile, "a+");
    if (g_lockfile == NULL)
    {
        fprintf(stderr, "fopen() failed:%s!\n", strerror(errno));
        return -1;
    }

    ret = flock(fileno(g_lockfile), LOCK_EX);
    if (ret != 0)
    {
        fprintf(stderr, "flock() failed:%s!\n", strerror(errno));
        return -1;
    }

    printf("Open the file fd = %d\n", fileno(g_lockfile));

    while(1)
    {
        ptr = fgets(rcvbuf, sizeof(rcvbuf), stdin);
        if (ptr == NULL)
        {
            break;
        }

        ret = fputs(rcvbuf, g_lockfile);
        if (ret < 0)
        {
            fprintf(stderr, "fputs() failed:%s!\n", strerror(errno));
            return -1;
        }

        fflush(g_lockfile);
    }

    ret = flock(fileno(g_lockfile), LOCK_UN);
    if (ret != 0)
    {
        fprintf(stderr, "flock() failed:%s!\n", strerror(errno));
        return -1;
    }

    fclose(g_lockfile);
   
    return 0;
}

这里要说明下,因为是我个人进入的误区,有可能你也会遇到,觉得程序没有问题,为什么会不是理想的呢?

flock是对同一个文件进行加锁,才会对不同进程进行排斥,所以,如果一个进程加锁后,是可以独享这个文件的操作权,但是,如果另一个进程不去尝试加锁,就直接往该文件写东西,那还是会写进去的。这里的防止多进程排斥,其实是要求每个进程必须先去尝试加锁,这就是flock的真正含义加锁。

fcntl()

fcntl() 函数的功能很多,可以改变已打开的文件的性质,本文中只是介绍其与获取/设置文件锁有关的功能。fcntl() 的函数原型如下所示:

int fcntl (int fd, int cmd, struct flock *lock);
其中,参数 fd 表示文件描述符;参数 cmd 指定要进行的锁操作,由于 fcntl() 函数功能比较多,这里先介绍与文件锁相关的三个取值 F_GETLK、F_SETLK 以及 F_SETLKW。这三个值均与 flock 结构有关。

在 flock 结构中,l_type 用来指明创建的是共享锁还是排他锁,其取值有三种:F_RDLCK(共享锁)、F_WRLCK(排他锁)和F_UNLCK(删除之前建立的 锁);l_pid 指明了该锁的拥有者;l_whence、l_start 和l_end 这些字段指明了进程需要对文件的哪个区域进行加锁,这个区域是一个连续的字节集合。因此,进程可以对同一个文件的不同部分加不同的锁。l_whence 必须是 SEEK_SET、SEEK_CUR 或 SEEK_END 这几个值中的一个,它们分别对应着文件头、当前位置和文件尾。l_whence 定义了相对于 l_start 的偏移量,l_start 是从文件开始计算的。

可以执行的操作包括:

  • F_GETLK: 进程可以通过它来获取通过 fd 打开的那个文件的加锁信息。执行该操作时,lock 指向的结构中就保存了希望对文件加的锁(或者说要查询的锁)。如果确实存在这样一把锁,它阻止 lock 指向的 flock 结构所给出的锁描述符,则把现存的锁的信息写到 lock 指向的 flock 结构中,并将该锁拥有者的 PID 写入 l_pid 字段中,然后返回;否则,就将 lock 指向的 flock 结构中的 l_type 设置为 F_UNLCK,并保持 flock 结构中其他信息不变返回,而不会对该文件真正加锁。
  • F_SETLK:进程用它来对文件的某个区域进行加锁(l_type的值为 F_RDLCK 或 F_WRLCK)或者删除锁(l_type 的值为F_UNLCK),如果有其他锁阻止该锁被建立,那么 fcntl() 就出错返回
  • F_SETLKW:与 F_SETLK 类似,唯一不同的是,如果有其他锁阻止该锁被建立,则调用进程进入睡眠状态,等待该锁释放。一旦这个调用开始了等待,就只有在能够进行加锁或者收到信号时才会返回 。

个人测试代码,本人也参考过APUE,写了下面的程序。此程序的功能是,先判断是否有锁,有锁就解锁,没有就加锁。其他的进程尝试解锁,是不可能真的解除的,只有该进程释放了锁,其他进程才能使用。我用过vi,>,>>做了测试,在我的系统环境下,是可以写入的,加锁了没有作用。

我系统环境是:Linux lvs 2.6.18-128.el5 #1 SMP Wed Jan 21 10:41:14 EST 2009 x86_64 x86_64 x86_64 GNU/Linux。


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>


int main(int argc, char ** argv)
{
   
    int ret;
    struct flock f1;
    int fd;

    fd = open("/home/test1.txt", O_CREAT | O_RDWR, 0777);
    if (fd < 0)
    {
        printf("open failed! msg: %s\n", strerror(errno));
        return -1;
    }

    f1.l_type = F_WRLCK;
    f1.l_start = 0;
    f1.l_whence = SEEK_SET;
    f1.l_len = 0;
    f1.l_pid = -1;

    ret = fcntl(fd, F_GETLK, &f1);
    if (ret < 0)
    {
        printf("fcntl failed msg:%s\n", strerror(errno));
        return -1;

    }

    if (f1.l_type == F_UNLCK)
    {
        printf("file fd = %d, pid = [%d] is unlock!\n", fd, f1.l_pid);
        goto lock;
    }
    if (f1.l_type == F_RDLCK)
    {
        printf("file fd = %d, pid = [%d] had a read locked!\n", fd, f1.l_pid);
        goto unlock;

    }
    if (f1.l_type == F_WRLCK)
    {
        printf("file fd = %d, pid = [%d] had a write locked!\n", fd, f1.l_pid);
        goto unlock;

    }

unlock:   
    f1.l_type = F_UNLCK;
    f1.l_start = 0;
    f1.l_whence = SEEK_SET;
    f1.l_len = 0;
   
    ret = fcntl(fd, F_SETLK, &f1);
    if (ret < 0)
    {
        printf("fcntl failed msg:%s\n", strerror(errno));
        return -1;
    }

    printf("delete file fd = %d lock is  success !\n", fd);
    return 0;

lock:
   

    f1.l_type = F_WRLCK;
    f1.l_start = 0;
    f1.l_whence = SEEK_SET;
    f1.l_len = 0;
   
    ret = fcntl(fd, F_SETLK, &f1);
    if (ret < 0)
    {
        printf("fcntl failed msg:%s\n", strerror(errno));
        return -1;
    }

    printf("lock file fd = %d success!\n", fd);

    sleep(100);

    printf("time out!\n");
   
    return 0;
}


如果大家有什么见解,欢迎提供!谢谢!


你可能感兴趣的:(Linux 中关于文件锁的系统调用:flock和fcntl)