两种经常使用的文件锁:
1、 协同锁
协同锁要求參与操作的进程之间协同合作。
如果进程“A”获得一个WRITE锁,并開始向文件里写入内容;此时,进程“B”并没有试图获取一个锁,它仍然能够打开文件并向文件里写入内容。
在此过程中,进程“B”就是一个非合作进程。如果进程“B”试图获取一个锁,那么整个过程就是一个合作的过程,从而能够保证操作的“序列化”。
仅仅有当參与操作的进程是协同合作的时候,协同锁才干发挥作用。协同锁有时也被称为“非强制”锁。
2、 强制锁
强制锁不须要參与操作的进程之间保持协同合作。它利用内核来查检每一个打开、读取、写入操作,从而保证在调用这些操作时不违反文件上的锁规则。关于强制锁的很多其它信息,能够在kernal.org上找到。
为了使能Linux中的强制锁功能。你须要在文件系统级别上打开它。同一时候在单个文件上打开它。其步骤是:
1、 挂载文件系统时使用“-o mand”參数。
2、 对于要打开强制锁功能的文件lock_file。必须打开set-group-ID位。关闭group-execute位。
在多个进程同时操作同一份文件的过程中,很容易导致文件中的数据混乱,需要锁操作来保证数据的完整性,这里介绍的针对文件的锁,称之为“文件锁”-flock。
flock,建议性锁,不具备强制性。一个进程使用flock将文件锁住,另一个进程可以直接操作正在被锁的文件,修改文件中的数据,原因在于flock只是用于检测文件是否被加锁,针对文件已经被加锁,另一个进程写入数据的情况,内核不会阻止这个进程的写入操作,也就是建议性锁的内核处理策略。
flock主要三种操作类型:
LOCK_SH,共享锁,多个进程可以使用同一把锁,常被用作读共享锁;
LOCK_EX,排他锁,同时只允许一个进程使用,常被用作写锁;
LOCK_UN,释放锁;
进程使用flock尝试锁文件时,如果文件已经被其他进程锁住,进程会被阻塞直到锁被释放掉,或者在调用flock的时候,采用LOCK_NB参数,在尝试锁住该文件的时候,发现已经被其他服务锁住,会返回错误,errno错误码为EWOULDBLOCK。即提供两种工作模式:阻塞与非阻塞类型。
服务会阻塞等待直到锁被释放:
flock(lockfd,LOCK_EX)
服务会返回错误发现文件已经被锁住时:
ret = flock(lockfd,LOCK_EX|LOCK_NB)
同时ret = -1, errno = EWOULDBLOCK
flock锁的释放非常具有特色,即可调用LOCK_UN参数来释放文件锁,也可以通过关闭fd的方式来释放文件锁(flock的第一个参数是fd),意味着flock会随着进程的关闭而被自动释放掉。
flock其中的一个使用场景为:检测进程是否已经存在;
int checkexit(char* pfile)
{
if (pfile == NULL)
{
return -1;
}
int lockfd = open(pfile,O_RDWR);
if (lockfd == -1)
{
return -2;
}
int iret = flock(lockfd,LOCK_EX|LOCK_NB);
if (iret == -1)
{
return -3;
}
return 0; } <!-- more --> fcntl()函数提供了比该函数更为强大的功能,并且所拥有的功能也覆盖了flock()所拥有的功能,但是在某些应用中任然使用着flock()函数,并且在继承和锁释放方面的一些语义 中flock()与fcntl()还是有所不同的。
flock()系统调用是在整个文件中加锁,通过对传入的fd所指向的文件进行操作,然后在通过operation参数所设置的值来确定做什么样的操作。
flock()根据调用时operation参数传入LOCK_UN的值来释放一个文件锁。此外,锁会在相应的文件描述符被关闭之后自动释放。同时,当一个文件描述符被复制时(dup()、dup2()、或一个fcntl() F_DUPFD操作),新的文件描述符会引用同一个文件锁。
flock(fd, LOCK_EX);
new_fd = dup(fd);
flock(new_fd, LOCK_UN);
这段代码先在fd上设置一个互斥锁,然后通过fd创建一个指向相同文件的新文件描述符new_fd,最后通过new_fd来解锁。从而我们可以得知新的文件描述符指向了同一个锁。所以,如果通过一个特定的文件描述符获取了一个锁并且创建了该描述符的一个或多个副本,那么,如果不显示的调用一个解锁操作,只有当文件描述符副本都被关闭了之后锁才会被释放。
如果使用fork()创建一个子进程,子进程会复制父进程中的所有描述符,从而使得它们也会指向同一个文件锁。
,有时候可以利用这些语义来将一个文件锁从父进程传输到子进程:在fork()之后,父进程关闭其文件描述符,然后锁就只在子进程的控制之下了。通过fork()创建的锁在exec()中会得以保留(除非在文件描述符上设置了close-on-exec标记并且该文件描述符是最后一个引用底层的打开文件描述的描述符)。
如果程序中使用open()来获取第二个引用同一个文件的描述符,那么,flock()会将其视为不同的文件描述符。如下代码会在第二个flock()上阻塞。
flock()放置的锁有如下限制
只能对整个文件进行加锁。这种粗粒度的加锁会限制协作进程间的并发。假如存在多个进程,其中各个进程都想同时访问同一个文件的不同部分。
通过flock()只能放置劝告式锁。
很多NFS实现不识别flock()放置的锁。
#include
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
[描述]
fcntl()针对(文件)描述符提供控制。参数fd是被参数cmd操作(如下面的描述)的描述符。针对cmd的值,fcntl能够接受第三个参数int arg。
[返回值]
fcntl()的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD , F_GETFD , F_GETFL以及F_GETOWN。
F_DUPFD 返回新的文件描述符
F_GETFD 返回相应标志
F_GETFL , F_GETOWN 返回一个正的进程ID或负的进程组ID
fd:文件描述符
F_DUPFD:复制文件描述符
F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec函数之后仍保持打开状态
F_SETFD:设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定
F_GETFL:得到open设置的标志
函数传入值
cmd
F_SETFL:改变open设置的标志
F_GETFK:根据lock描述,决定是否上文件锁
F_SETFK:设置lock描述的文件锁
F_SETLKW:这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。
如果存在其他锁,则调用进程睡眠;如果捕捉到信号则睡眠中断
F_GETOWN:检索将收到SIGIO和SIGURG信号的进程号或进程组号
F_SETOWN:设置进程号或进程组号
函数返回值
Lock:结构为flock,设置记录锁的具体状态,后面会详细说明
成功:0
-1:出错
对描述符fd加了文件锁后,如果没有显式使用LOCK_UN 解锁,在关闭 fd 时,会自动解除其持有的文件锁。
但是在为 fd 加锁后如果调用了dup 复制了文件描述符,这时关闭fd 时的表现和调用 LOCK_UN 是不一样的。
如果未显式使用 LOCK_UN解锁,在关闭文件描述符后,如果还有其他的fd 指向同一文件表项,比如之前调用
了dup 的情形,这时加在文件表项上的文件锁并不会解除,其他指向此文件表项的文件描述符依然持有锁,并且
锁的类型也不会发生变化。
使用fork 产生子进程时同样如此。父进程和子进程的描述符指向同一文件表项且已经加了文件锁时,如果用
LOCK_UN将其中一个fd 解锁,那么指向同一表项的所有其他fd 都会自动解锁。但是如果未使用 LOCK_UN
解锁,只是通过 close(fd)关闭了某个文件描述符,那么指向同一文件表项的其他描述符,依然会持有原有的锁。
出于方便考虑,在没有出现多个fd 指向现一文件表项的情况下,可以直接使用close(fd) 的默认解锁功能,
而不用显式的使用LOCK_UN。在有多个 fd 指向同一文件表项的情形下,如果要完全解锁,一定要使用
LOCK_UN 解锁,不能再使用 close(fd) 的默认解锁功能。
对于有些应用程序,如数据库,各个进程需要保证它正在单独地写一个文件。这时就要用到文件锁。
文件锁(也叫记录锁)的作用是,当一个进程读写文件的某部分时,其他进程就无法修改同一文件区域。
能够实现文件锁的函数主要有2个:flock和fcntl。
早期的伯克利版本只支持flock,该函数只能对整个文件加锁,不能对文件的一部分加锁。
lockf是在fcntl基础上构造的函数,它提供了一个简化的接口。它们允许对文件中任意字节区域加锁,短至一个字节,长至整个文件。
fcntl函数
#include
int fcntl(int fd, int cmd, …/struct flock *flockptr/);
#返回值:若成功,返回值依赖于cmd,失败返回-1
cmd是F_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_start和l_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返回的可能出错值