建议锁,强制锁,记录锁的概念
建议锁:
如果某一个进程对一个文件持有一把锁之后,其他进程仍然可以直接对文件进行操作(open, read, write)而不会被系统禁止,即使这个进程没有持有锁。只是一种编程上的约定。建议锁只对遵守建议锁准则的进程生效(程序在操作前应该自觉的检查所的
状态之后才能进行后续操作)
。
强制锁:
试图实现一套内核级的锁操作。当有进程对某个文件上锁之后,其他进程会在open、read 或 write 等文件操作时发生错误。
记录锁:
只对文件中自己所关心的那一部分加锁。记录锁是更细粒度的文件锁。记录锁存在于文件结构体中,并不与文件描述符相关联,会在进程退出时候被释放掉。
fcntl,flock,lockf 之间的区别
这三个函数的作用都是给文件加锁,那它们有什么区别呢?首先 flock 和 fcntl 是系统调用,而 lockf 是库函数。lockf 实际上是对 fcntl 的封装,所以 lockf 和 fcntl 的底层实现是一样的,对文件加锁的效果也是一样的。后面分析不同点时大多数情况是将 fcntl 和 lockf 放在一起的。下面首先看每个函数的使用,从使用的方式和效果来看各个函数的区别。
flock 函数
头文件:
#include <sys/file.h>
函数原型:
int flock(int fd, int operation); // 在 fd 指定的打开文件上应用或删除建议锁
函数描述:
flock()会依参数 operation 所指定的方式对参数 fd 所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。
参数描述:
fd:文件描述符
operation:属性参数选项
LOCK_SH:共享锁
LOCK_EX:排他锁或独占锁
LOCK_UN:解锁
LOCK_NB:使用非阻塞方式上锁,无法建立锁定时,此操作可不被阻断,马上返回进程。通常与 LOCK_SH 或 LOCK_EX 以或(OR)方式组合使用,因为单一文件无法同时建立共享锁定和互斥锁定。
返回值:
返回 0 表示成功,若有错误则返回-1,错误代码存于 errno。
用法描述:
flock 只要在打开文件后,需要对文件读写之前 flock 一下就可以了,用完之后再flock 一下,前面加锁,后面解锁。
进程使用 flock 尝试锁文件时,如果文件已经被其他进程锁住,进程会被阻塞直到锁被释放掉,或者在调用 flock 的时候,采用 LOCK_NB 参数,在尝试锁住该文件的时候,发现已经被其他服务锁住,会返回错误。
flock 锁的释放非常具有特色,即可调用 LOCK_UN 参数来释放文件锁,也可以通过关闭 fd 的方式来释放文件锁(flock 的第一个参数是 fd),意味着 flock 会随着进程的关闭而被自动释放掉
特点:
关于 flock 函数,首先要知道 flock 函数只能对整个文件上锁,而不能对文件的某一部分上锁,这是于 fcntl/lockf 的第一个重要区别,后者可以对文件的某个区域上锁。 其次,flock 只能产生劝告性锁。我们知道,linux 存在强制锁(mandatory lock)和劝告锁(advisory lock)。再次,flock 和 fcntl/lockf 的区别主要在 fork(建立子进程函数) 和 dup(克隆文件描述符)。
1. flock 不能在 NFS 文件系统(网络文件系统,简单理解就是通过
mount
挂载的文件)上使用,如果要在 NFS 使用文件锁,请使用 fcntl。
2. flock 锁可递归,即通过 dup 或者或者 fork 产生的两个 fd,都可以加锁而不会产生死锁。也就是说持有 flock 锁的进程还可以再次上锁而不会引起死锁。flock 创建的锁和文件描述符是关联的,这就意味着复制文件 fd(通过 fork 或者 dup)后,那么通过这两个 fd 都可以操作这把锁(例如通过一个 fd 加锁,通过另一个 fd 可以释放锁),也就是说子进程继承父进程的锁
。但是上锁过程中关闭其中一个 fd,锁并不会释放(因为 file 结构并没有释放),只有关闭所有复制出的 fd,锁才会释放。
示例 1:dup 对 flock 的影响
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
int main(int argc, char ** argv)
{
int ret;
int fd1 = open("./1.txt",O_RDWR);
int fd2 = dup(fd1);
printf("fd1: %d, fd2: %d\n", fd1, fd2);
ret = flock(fd1,LOCK_EX);
printf("get lock1, ret: %d\n", ret);
ret = flock(fd2,LOCK_EX);
printf("get lock2, ret: %d\n", ret);
return 0;
}
运行结果显示对 fd1 上锁并不会影响对 fd2 的上锁
示例 2:fork 对 flock 的影响
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
int main(int argc, char ** argv)
{
int ret;
int fd = open("./tmp.txt",O_RDWR);
int pid = fork();
if (pid == 0)
{
ret = flock(fd,LOCK_EX);
printf("child get lock, fd: %d, ret: %d\n",fd, ret);
sleep(10);
printf("child exit\n");
exit(0);
}
ret = flock(fd,LOCK_EX);
printf("parent get lock, fd: %d, ret: %d\n", fd, ret);
printf("parent exit\n");
return 0;
}
运行结果显示子进程持有锁,并不影响父进程通过相同的 fd 获取锁,反之亦然。
示例 3:多次 open 获取文件描述符 fd,不继承 flock 锁
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
int main (int argc, char ** argv)
{
int ret;
int fd1 = open("./1.txt",O_RDWR);
int fd2 = open("./1.txt",O_RDWR);
printf("fd1: %d, fd2: %d\n", fd1, fd2);
ret = flock(fd1,LOCK_EX);
printf("get lock1, ret: %d\n", ret);
ret = flock(fd2,LOCK_EX | LOCK_NB);
printf("get lock2, ret: %d\n", ret);
return 0;
}
运行结果显示 fd1 获取锁后,fd2 无法获取
lockf 函数和 fcntl 函数
头文件:
#include <unistd.h>
函数原型:
int lockf(int fd, int cmd, off_t len);
函数描述:
lockf()函数允许将文件区域用作信号量(监视锁),或用于控制对锁定进程的访问(强制模式记录锁定)。试图访问已锁定资源的其他进程将返回错误或进入休眠状态,直到资源解除锁定为止。当关闭文件时,将释放进程的所有锁定,即使进程仍然有打开的文件。当进程终止时,将释放进程保留的所有锁定。
参数描述:
fd:文件描述符
cmd:属性设置选项
F_LOCK:给文件互斥加锁,若文件以被加锁,则会一直阻塞到锁被释放
F_TLOCK:同 F_LOCK,不过是以非阻塞方式加锁
F_ULOCK:解锁
F_TEST 测试文件是否被上锁,若文件没被上锁则返回 0,否则返回-1。
len:为从文件当前位置的起始要锁住的长度,如果为0表示整个文件
返回值:
如果成功,则返回 0
。如果出现错误,则返回
-1
,并适当地设置
errno
。
fcntl/lockf
的特性:
1.
上锁可递归,如果一个进程对一个文件区间已经有一把锁,后来进程又企图在同一区间再加一把锁,则新锁将替换老锁。
2.
加读锁(共享锁)文件必须是读打开的,加写锁(排他锁)文件必须是写打开。
3.
进程终止时,他所建立的所有文件锁都会被释放,对于
flock
也是一样的。
4.
任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的任何一把锁都被释放(这些锁都是该进程设置的),这一点与 flock
不同。如:
fd1 = open (pathname, …);lockf ( fd1 , F_LOCK , 0 );fd2 = dup ( fd1 );close ( fd2 );
则在
close(fd2)
后,再
fd1
上设置的锁也会被释放,如果将
dup
换为
open
,以打开另一描述符上的同一文件,则效果也一样。如下
fd1 = open (pathname, …);lockf ( fd1 , F_LOCK , 0 );fd2 = open (pathname, …);close ( fd2 );
5.
由
fork
产生的子进程不继承父进程所设置的锁,这点与
flock
也不同。
6.
在执行
exec
后,新程序可以继承原程序的锁,这点和
flock
是相同的。(如果对
fd
设置了 close-on-exec
,则
exec
前会关闭
fd
,相应文件的锁也会被释放)。
fcntl/lockf
的关系
那么
flock
和
lockf/fcntl
所上的锁有什么关系呢?答案时互不影响。测试程序如下:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
int main(int argc, char **argv)
{
int fd, ret;
int pid;
fd = open("./1.txt", O_RDWR);
ret = flock(fd, LOCK_EX);
printf("flock return ret : %d\n", ret);
ret = lockf(fd, F_LOCK, 0);
printf("lockf return ret: %d\n", ret);
sleep(10);
return 0;
}
可见 flock
的加锁,并不影响
lockf
的加锁。两外我们可以通过
/proc/locks
查看进程获取锁的状态。
我们可以看到
/proc/locks
下面有锁的信息:我现在分别叙述下含义:
POSIX
和
FLOCK
这个比较明确,就是哪个类型的锁。
flock
系统调用产生的是
FLOCK
,
fcntl
调用
F_SETLK
,
F_SETLKW
或者
lockf
产生的是
POSIX
类型,有次可见两种调用产生的
锁的类型是不同的;
ADVISORY
表明是劝告锁
WRITE
顾名思义,是写锁还是读锁;
2650
是持有锁的进程
ID
。当然对于
flock
这种类型的锁,会出现进程已经退出的状况。
00:37:906
表示的对应磁盘文件的所在设备的主设备号,次设备号,还有文件对应的
inode
number
。
0
表示的是所的起始位置
EOF
表示的是结束位置。 这两个字段对
fcntl
类型比较有用,对
flock
来是总是
0
和
EOF
。
备注:
fcntl 不做详细介绍,具体用法可查看文件 IO 资料的 2.23 节