linux文件锁笔记

来源:互联网 发布:基于mac地址的acl 编辑:程序博客网 时间:2024/05/14 09:23

一、概述

应用程序的一个常见的需求是从一个文件中读取数据,修改这些数据,然后将这些数据写回文件,当同一时刻只有一个进程使用这个文件,这么做不会出现问题,但是当多个进程同时更新一个文件时,就会出现资源竞争的问题。

文件锁是一种文件读写机制,在任何特定的时间只允许一个进程访问一个文件。利用这种机制能够使读写单个文件的过程变得更安全。

主要内容

  1. 文件锁的分类与原理
  2. 文件锁的使用
  3. 结束语

二、文件锁的介绍

文件锁主要分为两种:一种是劝告式的,一种是强制式的锁;但是在默认的情况下,文件锁是劝告式的。

劝告式文件锁

这种锁不会介入write和read的操作,是一种协议式的锁,在对一个文件进行读写的时候,采用如下的过程

graph LR加锁-->读写操作读写操作-->解锁

但是劝告式文件锁不会从系统层上强制应用程序遵守这个协议,其他的程序可以不检测文件的锁的状态而直接操作文件.

强制式文件锁

在程序中可以使用chmod()和fchmod()函数对文件强制加锁,强制式的锁会使read/write函数阻塞或失败.

三、文件锁的使用

文件锁有多种实现,这里将的均是劝告锁,强制锁在应用中的价值不高,并且存在很大的弊端(容易锁死).
在使用文件锁时,读写文件的接口必须使用read和write,不能使用库函数的IO操作函数,因为IO操作函数会带有缓冲,影响加锁的结果.
(好像标准库函数的IO操作函数可以选择没有缓冲,但是依旧慎重)

flock

  • 功能:为一个已经打开的文件 添加或者移除一把劝告锁(advisory lock)
  • 原型:int flock(int fd, int operation);
  • 参数:

    • fd 文件id
    • operation 操作数

    operation可以使下列的三个值

  • LOCK_SH 放置一个共享锁
  • LOCK_EX 放置一个排他锁
  • LOCK_UN 解锁

    此外,LOCK_NB可以与前两个共用,通过’|’,意思是非阻塞的锁.当使用flock(fd,LOCK_SH|LOCK_NB)时,如果其他进程持有锁,则不会阻塞,而是返回错误,并记录一个错误码到error.

进程A 进程B LOCK_SH 进程B LOCK_EX LOCK_SH 是 否 LOCK_EX 否 否
一个进程在一个文件上只能拥有一种类型的锁,每次调用flock会改变锁的模式.锁会在文件描述符关闭的时候自动释放.
  • 不足 :
  • 粒度大,只能锁整个文件
  • 劝告锁,无法真正的锁定文件
  • 有些NFS实现不支持flock

fcntl

这种锁通常称为”记录加锁”,fcntl可以对文件的任意部分加锁,而不影响其他部分的使用,记录加锁是针对应用程序说的,记录边界是应用程序定义的一个字节范围

image

  • 原型:int fcntl(int fd, int cmd, … /* arg */ );
  • 参数:
    • fd 文件描述符
    • cmd :
      • F_SET_LK 设置锁状态,失败返回错误
      • F_SET_LKW 设置锁状态,失败阻塞
      • F_GET_LK 获取锁状态
    • arg :struct flock 结构指针

fcntl的锁操作,通过下面这个结构体进行设置

struct flock  {    short int l_type;   /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK.  */    short int l_whence; /* Where `l_start' is relative to (like `lseek').  */#ifndef __USE_FILE_OFFSET64    __off_t l_start;    /* Offset where the lock begins.  */    __off_t l_len;  /* Size of the locked area; zero means until EOF.  */#else    __off64_t l_start;  /* Offset where the lock begins.  */    __off64_t l_len;    /* Size of the locked area; zero means until EOF.  */#endif    __pid_t l_pid;  /* Process holding the lock.  */  };

锁有三种状态,分别是读锁/写锁和无锁.

一个用于设置锁状态的函数

int pidf_set_lock(int fd,int lock_type){        struct flock flockstr;        //default        if(lock_type!=F_RDLCK&&lock_type!=F_WRLCK&&lock_type!=F_UNLCK)                return -1;           flockstr.l_type         =lock_type;        flockstr.l_whence        =SEEK_SET;        flockstr.l_start        =0;        flockstr.l_len                =0;        if(fcntl(fd,F_SETLK,&flockstr)==0)        {                if(flockstr.l_type==F_RDLCK)                        pr_pidf_debug("set a F_RDLCK,thd pid = %d; \n",getpid());                if(flockstr.l_type==F_WRLCK)                        pr_pidf_debug("set a F_WRLCK,thd pid = %d; \n",getpid());                if(flockstr.l_type==F_UNLCK)                        pr_pidf_debug("set a F_UNLCK,thd pid = %d; \n",getpid());                return flockstr.l_type;        }                return -1;        }

获取锁状态的函数

int pidf_get_lock(int fd,pid_t *pid){        struct flock flockstr;                flockstr.l_type             =F_WRLCK;        flockstr.l_whence           =SEEK_SET;        flockstr.l_start            =0;        flockstr.l_len              =0;        if(fcntl(fd,F_GETLK,&flockstr)==0)        {                if(pid!=NULL)                     *pid =flockstr.l_pid;                return flockstr.l_type;         }        perror("fcntl F_GETLK");        return -1;}

继承和释放

  • fork产生的子进程不会继承记录锁(fcntl),但是会继承flock
  • 记录锁在exec()中会得到保留
  • 记录锁同时和一个进程,一个inode关联,所以进程结束或者进程对文件的链接关闭,都会导致其所持有的记录锁被释放

四、结束语

参考

《Linux/UNIX系统编程手册(下)》