文件锁也被称为记录所,文件锁如果深讲的话,内容不少(比如文件锁最起码分为了建议锁和强制性锁,暂时挖坑,后面填)。
文件锁作用
顾名思义,就是用来保护文件数据的。当多个进程共享读写同一个文件时,为了不让进程们各自读写数据时相互干扰,我们可以使用进程信号量来互斥实现,除了可以使用进程信号量以外,还可以使用我们本小节要讲的“文件锁”来实现,而且功能更丰富,使用起来相对还更容易些。
多进程读写文件
多进程共享读写同一个文件时,如果数据很重要的话,为了防止数据相互修改,应该满足如下读写条件:
①写与写应该互斥
当某个进程正在写文件,而且在数据没有写完时,其它进程不能写,否者会相互打乱对方写的数据。
②读与写也应该是互斥的
分两种情况:
1)某个进程正在写数据,而且在数据没有写完时,其它进程不能读数据。因为别人在没有写完之前,读到的数据是不完整的,所以读和写时互斥的。
2)某个进程正在读数据,在数据没有读完之前,其它进程不能写数据。因为可能会扰乱别人读到的数据。
③读与读共享
某个进程在读数时,就算数据没有读完,其它进程也可以共享读数据,并不需要互斥等别人读完后才能读。因为读文件是不会修改文件的内容,所以不用担心数据相互干扰的问题。
总结起来就是,多进程读写文件时,如果你想进行资源保护的话,完美的资源保护应该满足如下这样的。
1)写与写之间互斥
2)读与写之间互斥
3)读与读之间共享
如何实现以上读写要求?
如果使用信号量来实现保护的话,只能是一律互斥,包括读与读都是互斥的,不能够向上面描述的,既能互斥又能共享,但是文件锁可以做到。
文件锁的读锁与写锁
对文件加锁时可以加两种锁,分别是“读文件锁”和“写文件锁”,我们这里简称为读锁和写锁。
读锁、写锁之间关系
①读锁和读锁共享:可以重复加读锁,别人加了读锁在没有解锁之前,我依然可以加读锁,这就是共享。
②读锁与写锁互斥:别人加了读锁没有解锁前,加写锁会失败,反过来也是如此。
加锁失败后两种处理方式,
(1)阻塞,直到别人解锁然后加锁成功为止
(2)出错返回,不阻塞
③写锁与写锁互斥:别人加了写锁在没有解锁前,不能加写锁,加写锁会失败。
加锁失败后两种处理方式,
(1)阻塞,直到别人解锁然后加锁成功为止
(2)出错返回,不阻塞
使用文件锁对文件进行保护
读文件时加读锁,写文件时就加写锁,然后就可以很容易的实现符合如下要求的资源保护。
1)写与写之间互斥
2)读与写之间互斥
3)读与读之间共享
文件锁的加锁方式
(1)对整个文件内容加锁
对整个文件加锁是最常用的文件锁的加锁方式。当你对整个文件加锁时,如果文件的长度因为写入新数据或者截短而发生了变化,加锁内容的长度会自动变化,保证对内容变化着的整个文件加锁。
(2)对文件某部分内容加锁
不过一般来说是,对多少内容加锁,就对多少内容解锁,如果你是对整个文件加锁,就将整个文件解锁。但是实际上加锁和实际解锁的长度可以不相同,比如我对1000个字节的内容加了锁,但是可以只对其中的100字节解锁,不过这种情况用的少,知道有这么回事即可。
文件锁的实现——fcntl
实现文件锁时,我们还是需要使用fcntl函数。
再看看fcntl的函数
原型
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, .../*struct flock *flockptr */ );
功能
fcntl函数有多种功能,我们这里主要介绍实现文件锁的功能,当cmd被设置的是与文件锁相关的宏时,fcntl就是用于实现文件锁。
返回值
成功返回0,失败则返回-1,并且errno被设置。
参数
fd:文件描述符,指向需要被加锁的文件。
cmd:实现文件锁时,cmd有三种设置,F_GETLK、F_SETLK和F_SETLKW含义如下:
F_GETLK:
从内核获取文件锁的信息,将其保存到第三个参数,此时第三个参数为struct flock *flockptr。我们这里是要设置文件锁,而不是获取已有文件锁的信息,我们这里用不到这个宏。
F_SETLK:
设置第三个参数所代表的文件锁,而且设置的是非阻塞文件锁,也就是如果加锁失败不会阻塞。也就是说加锁失败后如果不想阻塞的话,就是由F_SETLK宏来决定的。
此时需要用到第三个参数,struct flock *flockptr。
使用举例:
第一步:定义一个struct flock flockptr结构体变量(这个结构体变量就是文件锁)。
第二步:设置flockptr的成员,表示你想设置什么样的文件锁。
第三步:通过第三个参数,将设置好的flockptr的地址传递给fcntl,设置你要的文件锁
F_SETLKW:
与F_SETLK一样,只不过设置的是阻塞文件锁,也就说加锁不成功的话就阻塞,是由F_SETLKW宏来决定的。
第三个参数:
第三个参数设置为什么视情况而定,如果fcntl用于实现文件锁的话,第三个参数为struct flock *flockptr,flockptr代表的就是文件锁。对flockptr的成员设置为特定的值,就可以将文件锁设置为你想要的锁。
struct flock结构体如下:
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) }
成员说明
l_type:锁类型
F_RDLCK:读锁(或称共享锁)
F_WRLCK:写锁
F_UNLCK:解锁
l_whence:加锁位置粗定位,设置同lseek的whence
SEEK_SET:文件开始处
SEEK_CUR:文件当前位置处
SEEK_END:文件末尾位置处
l_whence这个与lseek函数的whence是一个含义,off_t lseek(int fd, off_t offset, int whence);
l_start:精定位,相对l_whence的偏移,与lseek的offset的含义完全一致
通过l_whence和l_start的值,就可以用来指定从文件的什么位置开始加锁,不过一般来说,我们会将l_whence指定为SEEK_SET,l_start指定为0,表示从整个文件头上开始加锁。
l_len:从l_whence和l_start所指定的起始地点算起,对文件多长的内容加锁。
如果l_len被设置0,表示一直加锁到文件的末尾,如果文件长度是变化的,将自动调整加锁的末尾位置。
将l_whence和l_start设置为SEEK_SET和0,然后再将l_len设置为0,就表示从文件头加锁到文件末尾,其实就是对整个文件加锁。
flockptr.l_whence=SEEK_SET; flockptr.l_start=0; flockptr.l_len=0;
就就表示对整个文件加锁。
如果只是对文件中间的某段加锁,这只是区域加锁,加区域锁时可以给文件n多个的独立区域加锁。
l_pid:当前正加着锁的那个进程的PID
只有当我们获取一个已存在锁的信息时,才会使用这个成员,这个成员的值不是我们设置的,是由文件锁自己设置的,我们只是获取以查看当前那个进程正加着锁。对于我们目前设置文件锁来说,这个成员用不到。
代码演示
使用文件锁的互斥操作,解决父子进程向同一文件写“hello ”,“world\n”时,hello hello world相连的问题。
file_lock.h
1 #ifndef H_FILELOCK_H 2 #define H_FILELOCK_H 3 4 #include <unistd.h> 5 #include <fcntl.h> 6 7 //非阻塞设置写锁 8 #define SET_WRFLCK(fd, l_whence, l_offset, l_len)\ 9 set_filelock(fd, F_SETLK, F_WRLCK, l_whence, l_offset, l_len) 10 //阻塞设置写锁 11 #define SET_WRFLCK_W(fd, l_whence, l_offset, l_len)\ 12 set_filelock(fd, F_SETLKW, F_WRLCK, l_whence, l_offset, l_len) 13 14 //非阻塞设置读锁 15 #define SET_RDFLCK(fd, l_whence, l_offset, l_len)\ 16 set_filelock(fd, F_SETLK, F_RDLCK, l_whence, l_offset, l_len) 17 //阻塞设置读锁 18 #define SET_RDFLCK_W(fd, l_whence, l_offset, l_len)\ 19 set_filelock(fd, F_SETLKW, F_RDLCK, l_whence, l_offset, l_len) 20 21 //解锁 22 #define UNLCK(fd, l_whence, l_offset, l_len)\ 23 set_filelock(fd, F_SETLK, F_UNLCK, l_whence, l_offset, l_len) 24 25 /* 调用这个函数,即可实现阻塞加读锁/阻塞加写锁, 非阻塞加读锁/非阻塞加写锁/解锁 */ 26 static void set_filelock(int fd, int ifwait, int l_type, int l_whence, int l_offset, int l_len) 27 { 28 int ret = 0; 29 struct flock flck; 30 31 flck.l_type = l_type; 32 flck.l_whence = l_whence; 33 flck.l_start = l_offset; 34 flck.l_len = l_len; 35 36 ret = fcntl(fd, ifwait, &flck); 37 if(ret == -1) 38 { 39 perror("fcntl fail"); 40 exit(-1); 41 } 42 } 43 44 45 46 #endif