linux中fcntl()、lockf、flock的区别

fcntl() 、l ockf

、flock的区别

——lvyilong316

三个函数的作用都是给文件加锁,那它们有什么区别呢?首先 flock fcntl 是系统调用,而 lockf 是库函数 lockf 实际上是 fcntl 的封装 ,所以 lockf fcntl 的底层实现是一样的,对文件加锁的效果也是一样的。后面分析不同点时大多数情况是将 fcntl lockf 放在一起的。下面首先看每个函数的使用,从使用的方式和效果来看各个函数的区别。

1. flock

函数原型

#include <sys/file.h>

int flock(int fd, int operation);  // Apply or remove an  advisory lock  on the open file specified by fd ,只是建议性锁

其中 fd 是系统调用 open 返回的文件描述符, operation 的选项有:

LOCK_SH  :共享锁

LOCK_EX  :排他锁或者独占锁

LOCK_UN :  解锁。

LOCK_NB :非阻塞(与以上三种操作一起使用)

关于 flock 函数,首先要知道 flock 函数只能对整个文件上锁,而不能对文件的某一部分上锁 ,这是于 fcntl /lock f 的第一个重要区别,后者可以对文件的某个区域上锁。其次, flock 只能产生劝告性锁 我们知道, linux 存在强制锁( mandatory lock )和劝告锁( advisory lock )。所谓强制锁,比较好理解,就是你家大门上的那把锁,最要命的是只有一把钥匙,只有一个进程可以操作。所谓劝告锁,本质是一种协议,你访问文件前,先检查锁,这时候锁才其作用,如果你不那么 kind ,不管三七二十一,就要读写,那么劝告锁没有任何的作用。而遵守协议,读写前先检查锁的那些进程,叫做合作进程。再次, flock fcntl /lockf 的区别主要在 fork dup

(1)  flock 创建的锁是和文件打开表项( struct   file )相关联的,而不是 fd 。这就意味着复制文件 fd (通过 fork 或者 dup )后,那么通过这两个 fd 都可以操作这把锁(例如通过一个 fd 加锁,通过另一个 fd 可以释放锁),也就是说 子进程继承父进程的锁 。但是上锁过程中关闭其中一个 fd ,锁并不会释放(因为 file 结构并没有释放),只有关闭所有复制出的 fd ,锁才会释放。测试程序入程序一。

程序一

点击( 此处 )折叠或打开

  1. #include < stdio . h >
  2. #include < unistd . h >
  3. #include < stdlib . h >
  4. #include < sys / file . h >
  5. int main ( int argc , char * * argv )
  6. {
  7.      int ret ;
  8.      int fd1 = open ( "./tmp.txt" , O_RDWR ) ;
  9.      int fd2 = dup ( fd1 ) ;
  10.     printf ( "fd1: %d, fd2: %d\n" , fd1 , fd2 ) ;
  11.     ret = flock ( fd1 , LOCK_EX ) ;
  12.     printf ( "get lock1, ret: %d\n" , ret ) ;
  13.     ret = flock ( fd2 , LOCK_EX ) ;
  14.     printf ( "get lock2, ret: %d\n" , ret ) ;
  15.     return 0 ;
  16. }

运行结果如图,对 fd1 上锁,并不影响程序通过 fd2 上锁。对于父子进程,参考程序二。

程序二

点击( 此处 )折叠或打开

  1. #include < stdio . h >
  2. #include < unistd . h >
  3. #include < stdlib . h >
  4. #include < sys / file . h >
  5. int main ( int argc , char * * argv )
  6. {
  7.      int ret ;
  8.      int pid ;
  9.      int fd = open ( "./tmp.txt" , O_RDWR ) ;
  10.      if ( ( pid = fork ( ) ) = = 0 ) {
  11.         ret = flock ( fd , LOCK_EX ) ;
  12.         printf ( "chile get lock, fd: %d, ret: %d\n" , fd , ret ) ;
  13.         sleep ( 10 ) ;
  14.         printf ( "chile exit\n" ) ;
  15.          exit ( 0 ) ;
  16.      }
  17.     ret = flock ( fd , LOCK_EX ) ;
  18.     printf ( "parent get lock, fd: %d, ret: %d\n" , fd , ret ) ;
  19.     printf ( "parent exit\n" ) ;
  20.     return 0 ;
  21. }

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

(2) 使用 open 两次打开同一个文件,得到的两个 fd 是独立的(因为底层对应两个 file 对象),通过其中一个加锁,通过另一个无法解锁,并且在前一个解锁前也无法上锁。测试程序如程序三:

程序三

点击( 此处 )折叠或打开

  1. #include < stdio . h >
  2. #include < unistd . h >
  3. #include < stdlib . h >
  4. #include < sys / file . h >
  5. int main ( int argc , char * * argv )
  6. {
  7.      int ret ;
  8.      int fd1 = open ( "./tmp.txt" , O_RDWR ) ;
  9.      int fd2 = open ( "./tmp.txt" , O_RDWR ) ;
  10.     printf ( "fd1: %d, fd2: %d\n" , fd1 , fd2 ) ;
  11.     ret = flock ( fd1 , LOCK_EX ) ;
  12.     printf ( "get lock1, ret: %d\n" , ret ) ;
  13.     ret = flock ( fd2 , LOCK_EX ) ;
  14.     printf ( "get lock2, ret: %d\n" , ret ) ;
  15.     return 0 ;
  16. }

结果如图,通过 fd1 获取锁后,无法再通过 fd2 获取锁。

(3)  使用 exec 后,文件锁的状态不变。

(4)  flock 不能再 NFS 文件系统上使用,如果要在 NFS 使用文件锁,请使用 fcntl

(5) flock 锁可递归,即通过 dup 或者或者 fork 产生的两个 fd ,都可以加锁而不会产生死锁。

2.  lockf fcntl

函数原型

#include <unistd.h>

int lockf(int fd, int cmd, off_t len);

fd 为通过 open 返回的打开文件描述符。

cmd 的取值为:

F_LOCK :给文件互斥加锁,若文件以被加锁,则会一直阻塞到锁被释放。

F_TLOCK :同 F_LOCK ,但若文件已被加锁,不会阻塞,而回返回错误。

F_ULOCK :解锁。

F_TEST :测试文件是否被上锁,若文件没被上锁则返回 0 ,否则返回 -1

len :为从文件当前位置的起始要锁住的长度。

通过函数参数的功能,可以看出 lockf 只支持排他锁,不支持共享锁。

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

struct flock {

... 

short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */

short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */  

off_t l_start;    /* Starting offset for lock */ 

off_t l_len;      /* Number of bytes to lock */ 

pid_t l_pid;  /* PID of process blocking our lock (F_GETLK only) */ 

...        

}; 

文件记录加锁相关的 cmd  分三种:

F_SETLK :申请锁(读锁 F_RDLCK ,写锁 F_WRLCK )或者释放所( F_UNLCK ),但是如果 kernel 无法将锁授予本进程(被其他进程抢了先,占了锁),不傻等,返回 error

F_SETLKW :和 F_SETLK 几乎一样,唯一的区别,这厮是个死心眼的主儿,申请不到,就傻等。

F_GETLK :这个接口是获取锁的相关信息: 这个接口会修改我们传入的 struct flock

通过函数参数功能可以看出 fcntl 是功能最强大的,它既支持共享锁又支持排他锁,即可以锁住整个文件,又能只锁文件的某一部分。

下面看 fcntl /lockf 的特性:

(1)  上锁可递归,如果一个进程对一个文件区间已经有一把锁,后来进程又企图在同一区间再加一把锁,则新锁将替换老锁。

(2)  加读锁(共享锁)文件必须是读打开的,加写锁(排他锁)文件必须是写打开。

(3)  进程不能使用 F_GETLK 命令来测试它自己是否再文件的某一部分持有一把锁。 F_GETLK 命令定义说明,返回信息指示是否现存的锁阻止调用进程设置它自己的锁。因为, F_SETLK F_SETLKW 命令总是替换进程的现有锁,所以调用进程绝不会阻塞再自己持有的锁上,于是 F_GETLK 命令绝不会报告调用进程自己持有的锁。

(4)  进程终止时,他所建立的所有文件锁都会被释放,队医 flock 也是一样的。

(5)  任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的任何一把锁都被释放(这些锁都是该进程设置的),这一点与 flock 不同 。如:

fd 1 = open(pathname, …);

lockf(fd1, F_LOCK, 0);

fd2 = dup(fd1);

close(fd2);

则在 close (fd2) 后,再 fd1 上设置的锁会被释放,如果将 dup 换为 open ,以打开另一描述符上的同一文件,则效果也一样。

fd 1 = open(pathname, …);

lockf(fd1, F_LOCK, 0);

fd2 = open(pathname, …);

close(fd2);

(6)  fork 产生的子进程不继承父进程所设置的锁,这点与 flock 也不同。

(7)  在执行 exec 后,新程序可以继承原程序的锁,这点和 flock 是相同的。(如果对 fd 设置了 close-on-exec ,则 exec 前会关闭 fd ,相应文件的锁也会被释放)。

(8)  支持强制性锁:对一个特定文件打开其设置组 ID (S _ISGID ) ,并关闭其组执行位 (S_IXGRP) ,则对该文件开启了强制性锁机制。再 L i nux 中如果要使用强制性锁,则要在文件系统 mount 时,使用 _omand 打开该机制。

3.  两种锁的关系

那么 flock lockf /fcntl 所上的锁有什么关系呢?答案时互不影响。测试程序如下:

点击( 此处 )折叠或打开

  1. #include < unistd . h >
  2. #include < stdio . h >
  3. #include < stdlib . h >
  4. #include < sys / file . h >
  5. int main ( int argc , char * * argv )
  6. {
  7.      int fd , ret ;
  8.      int pid ;
  9.     fd = open ( "./tmp.txt" , O_RDWR ) ;
  10.     ret = flock ( fd , LOCK_EX ) ;
  11.     printf ( "flock return ret : %d\n" , ret ) ;
  12.     ret = lockf ( fd , F_LOCK , 0 ) ;
  13.     printf ( "lockf return ret: %d\n" , ret ) ;
  14.     sleep ( 100 ) ;
  15.     return 0 ;
  16. }

测试结果如下:

$./a.out

flock return ret : 0

lockf return ret: 0

可见 flock 的加锁,并不影响 lockf 的加锁。两外我们可以通过 /proc/lock s 查看进程获取锁的状态。

$ps aux | grep a.out | grep -v grep

123751   18849  0.0  0.0  11904   440 pts/5    S+   01:09   0:00 ./a.out

$sudo cat /proc/locks | grep 18849

1: POSIX  ADVISORY  WRITE 18849 08:02:852674 0 EOF

2: FLOCK  ADVISORY  WRITE 18849 08:02:852674 0 EOF

我们可以看到 /proc/locks 下面有锁的信息:我现在分别叙述下含义:

1)  POSIX FLOCK  这个比较明确,就是哪个类型的锁。 flock 系统调用产生的是 FLOCK fcntl 调用 F_SETLK F_SETLKW 或者 lockf 产生的是 POSIX 类型,有次可见两种调用产生的锁的类型是不同的;

2)  ADVISORY 表明是劝告锁;

3)  WRITE 顾名思义,是写锁,还有读锁;

4)  18849 是持有锁的进程 ID 。当然对于 flock 这种类型的锁,会出现进程已经退出的状况。

5)  08:02:852674 表示的对应磁盘文件的所在设备的主设备好,次设备号,还有文件对应的 inode number

6)  0 表示的是所的其实位置

7)  EOF 表示的是结束位置。 这两个字段对 fcntl 类型比较有用,对 flock 来是总是 EOF

你可能感兴趣的:(linux中fcntl()、lockf、flock的区别)