记录锁的功能:当某个进程正在读或者修改某个文件的某个部分的时候,记录锁可以阻止其他进程在同一时间对这部分文件的访问修改。
早期的伯克利版本只支持flock函数,改函数锁只能锁住整个文件,不能锁文件中的某一部分文件;SVR3通过fcntl函数添加了记录锁的功能,在此基础上构造了lockf 函数,这些函数允许调用者锁住文件中的任意部分,长至整个文件,短至文件中的一个字节。
fcntl函数构建记录锁
#include <fcntl.h>
int fcntl(int filedes, int cmd, ../* struct flock *flockptr);
返回值:成功则依赖于cmd的值,失败全都返回-1;
对于记录锁,cmd的值是 F_GETLK 、 F_SETLK 、 F_SETLKW
flock 结构体:
struct flock
{
short l_type; /**锁的类型: F_RDLCK(读锁) 、F_WRLCK(写锁) 、F_UNLCK(解锁) ,读锁与读锁之间不阻塞、读锁与写锁阻塞、写锁与写锁阻塞*/
off_t l_start; /****偏移量:从whence位置开始***********/
short l_whence; /***偏移的首位置:SEEK_SET(文件头)、SEEK_CUR(文件的当前位置)、SEEK_END(文件尾)********/
off_t l_len; /******需要锁住文件中的长度,0表示锁住整个文件******/
pid_t l_pid; /******returned with F_GETLK******/
};
F_GETLK:判断由flockptr所描述的锁是否被另外一把锁阻塞,如果有,则阻止创建由flockptr所描述的锁,并把该现有锁的信息写到flockptr指向的结构体中;如果不存在,则把l_type设置为F_UNLCK,其他信息不变。(所以,F_GETLK常用来判断文件是否加了锁,若l_type == F_UNLCK 则表示没有加锁,反之则加了锁。F_GETLK并不用来加锁,只是作为一种判断)。
struct flock fl;
30 fl.l_type = F_WRLCK;
31 fl.l_whence = whence;
32 fl.l_start = offset;
33 fl.l_len = len;
34
35 fcntl(fd, F_GETLK, &fl);
36 if(fl.l_type == F_UNLCK)
37 {//表示没有加写锁
38 return 0;
39 }
40 return 1;
F_SETLK:试图创建一把由flockptr所描述的锁,当文件之前如果已经加过锁了,则立即出错返回,否则加锁。
F_SETLKW:这是F_SETLK的阻塞版本(命令中的W表示等待),如果已加锁则等待直到解锁在加锁,如果没有加锁则直接加锁。
F_GETLK先判断是否加锁,F_SETLKW再来加锁,这不是一个原子操作,有时间窗口,因此不能保证在F_GETLK和F_SETLKW之间不会被另一个进程插入并建立一把相关的锁。所以如果不希望这种情况发生,应使用F_SETLK,通过对其返回结果测试来判断是否建立了所需要求的锁。
//读锁
7 #define read_lock(fd, offset, whence, len) lock_reg(fd, F_RDLCK, offset, whence, len) /*****宏定义*******/
9
10 //写锁
11 #define write_lock(fd, offset, whence, len) lock_reg(fd, F_WRLCK, offset, whence, len)
13
//解锁
14 #define unlock(fd, offset, whence, len) lock_reg(fd, F_UNLCK, offset, whence, len)
16
17 //设置锁
18 static int inline lock_reg(int fd, short type, off_t offset, short whence, off_t len)
19 {
20 struct flock fl;
21 fl.l_type = type;
22 fl.l_whence = whence;
23 fl.l_start = offset;
24 fl.l_len = len;
25 return fcntl(fd, F_SETLKW, &fl);
26 }
加锁应遵循两个原则以防止死锁的情况出现:
1、不能加两次锁。
2、两个进程加锁的顺序一致,以防两个进程相互等待对方持有并锁定的资源。
锁的隐含继承和释放:
1、当一个进程结束时,其建立的所有锁全都释放。
2、任何时刻关闭一个描述符时,则该进程通过这个描述符可以引用的文件上的任何一把锁都释放。
3、由fork产生的子进程不继承父进程所设置的锁。
4、在执行exec后,新程序可以继承原执行程序的锁。
在文件尾端加锁或者解锁时要特别小心,采用SEEK_END和SEEK_CUR是可能不断变化的,所以们最好采用SEEK_SET,使用绝对偏移,否则则应时刻记住当前偏移量和文件尾端的位置。