美文网首页
文件锁的使用浅析

文件锁的使用浅析

作者: guotianqing | 来源:发表于2018-04-22 22:31 被阅读0次

    概述

    在多数unix系统中,当多个进程/线程同时编辑一个文件时,该文件的最后状态取决于最后一个写该文件的进程。

    对于有些应用程序,如数据库,各个进程需要保证它正在单独地写一个文件。这时就要用到文件锁。

    文件锁(也叫记录锁)的作用是,当一个进程读写文件的某部分时,其他进程就无法修改同一文件区域。

    能够实现文件锁的函数主要有2个:flockfcntl

    早期的伯克利版本只支持flock,该函数只能对整个文件加锁,不能对文件的一部分加锁。

    lockf是在fcntl基础上构造的函数,它提供了一个简化的接口。它们允许对文件中任意字节区域加锁,短至一个字节,长至整个文件。


    fcntl函数

    #include <fcntl.h>
    
    int fcntl(int fd, int cmd, .../*struct flock *flockptr*/);
    #返回值:若成功,返回值依赖于cmd,失败返回-1
    

    cmdF_GETLK, F_SETLK, F_SETLKW中的一个。第三个参数是指向flock结构的指针,flock结构如下:

    struct flock {
    short l_type;/* one of F_RDLCK, F_WRLCK, F_UNLCK */
    short l_whence;/* SEEK_SET, SEEK_CUR, SEEK_END */
    off_t l_start;/* offset in bytes, relative to l_whence */
    off_t l_end;/* length, in bytes, 0 means lock to EOF */
    off_t l_pid;/* returned with F_GETLK */
    };
    

    其中,

    • 锁类型:共享读锁F_RDLCK,独占性写锁F_WRLCK,解锁F_UNLCK
    • 加锁或解锁区域的起始字节偏移量(l_start, l_whence
    • 区域字节长度(L_len
    • 进程的id持有的锁能阻塞当前进程,仅由F_GETLK返回
    • 锁可以在文件尾处开始或者越过尾端开始,但是不能在文件起始位置之前开始
    • l_len=0, 表示锁的范围可以扩大到最大可能偏移量,这意味着不管向文件中追加多少数据,它们都可以处于锁的范围内,而且起始位置可以任意
    • 设置l_startl_whence指向文件的起始位置,并且指定l_len=0,以实现对整个文件加锁(一般l_start=0, l_whence=SEEK_SET

    锁的使用

    使用锁的基本规则:

    • 任意多个进程在一个给定的字节上可以有一把共享的读锁(F_RDLCK),但是在一个给定的字节上只能有一个进程有一把独占性写锁(F_WRLCK
    • 如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁,如果在一个字节上已经有一把独占性写锁,则不能再对它加任何读锁
    • 对于单个进程而言,如果进程对某个文件区域已经有了一把锁,然后又试图在相同区域再加一把锁,则新锁会替换旧锁
    • 加读锁时,该描述符必须是读打开,加写锁时,该描述符必须是写打开

    fcntl三种cmd的使用:

    • F_GETLK:判断由flockptr所描述的锁是否会被另一把锁所排斥(阻塞),如果存在一把锁阻止创建由flockptr所描述的锁,由该现有锁的信息将重写flockptr指向的信息。如果不存在这种情况,则除了将l_type设置为F_UNLCK之处,flockptr所指向结构中的其他信息保持不变
    • F_SETLK:设置由flockptr所描述的锁,如果程序试图获得一把锁,而系统阻止程序获得该锁,则fcntl会立即返回错误,errno设置为EACCES或EAGAIN。当l_type=F_UNLCK时,此命令用来清除指定的锁
    • F_SETLKW:F_SETLK的阻塞版本(wait)。如果程序尝试获得的锁无法被授予,调用进程会进入休眠直到进程获得锁或者信号中断

    注意:用F_GETLK 测试能否创建一把锁,然后用F_SETLK尝试建立锁之间并非原子操作,也就是说两次调用之间有可能另一进程插入并创建了相同的锁。如果不希望在等待锁变为可用时产生阻塞,就必须处理由F_SETLK返回的可能出错值

    下面是测试一把锁的例子:

    #include <stdio.h>
    #include <errno.h>
    #include <pthread.h>
    #include <fcntl.h>
    
    pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
    {
        struct flock lock;
        
        lock.l_type = type;
        lock.l_start = offset;
        lock.l_whence = whence;
        l   ock.l_len = len;
        
        if (fcntl(fd, F_GETLK, &lock) < 0) {
            printf("fcntl error: %s\n", strerror(errno));
            return 1;
        }
        
        if (lock.l_type == F_UNLCK) {
            return 0;
        }
        
        return lock.l_pid;
    }
    

    锁的继承与释放

    锁的继承和释放有以下三条原则:

    • 锁与进程和文件两者相关联。即当一个进程终止时,它所建立的所有锁均释放,对于描述符而言,无论它何时关闭,进程通过它引用的文件上的任何一把锁也都会释放
    • fork产生的子进程不继承父进程所设置的锁
    • 执行exec后,新程序可以继承原程序的锁。注意,如果对一个文件描述符设置了执行时关闭标志,那么当作为exec的一部分关闭该文件描述符时,将释放相应文件的所有锁

    避免死锁

    如果两个进程互相等待对方持有并且不释放的资源时,这两个进程就会进入死锁状态。

    如果一个进程已经控制了文件中的一个加锁区域,然后它又试图对另一个进程控制的区域加锁,那么它就会进入睡眠,并有可能发生死锁。

    检测到死锁时,内核必须选择一个进程接收错误返回。


    总结

    在多进程或多线程环境中,当多个应用需要读写同一个文件时,需要考虑对文件加锁,以保证对文件修改的一致性。

    在使用文件锁时,应明确应用模式,防止死锁。

    更多关于文件锁的使用细节,请参考《UNIX环境高级编程》。

    相关文章

      网友评论

          本文标题:文件锁的使用浅析

          本文链接:https://www.haomeiwen.com/subject/fiyhlftx.html