文件锁也被称为记录所,文件锁如果深讲的话,内容不少(比如文件锁最起码分为了建议锁和强制性锁,暂时挖坑,后面填)。

文件锁作用

顾名思义,就是用来保护文件数据的。当多个进程共享读写同一个文件时,为了不让进程们各自读写数据时相互干扰,我们可以使用进程信号量来互斥实现,除了可以使用进程信号量以外,还可以使用我们本小节要讲的“文件锁”来实现,而且功能更丰富,使用起来相对还更容易些。

多进程读写文件

高级IO——文件锁

多进程共享读写同一个文件时,如果数据很重要的话,为了防止数据相互修改,应该满足如下读写条件:

①写与写应该互斥

当某个进程正在写文件,而且在数据没有写完时,其它进程不能写,否者会相互打乱对方写的数据。

②读与写也应该是互斥的

分两种情况:

1)某个进程正在写数据,而且在数据没有写完时,其它进程不能读数据。因为别人在没有写完之前,读到的数据是不完整的,所以读和写时互斥的。

2)某个进程正在读数据,在数据没有读完之前,其它进程不能写数据。因为可能会扰乱别人读到的数据。

③读与读共享

某个进程在读数时,就算数据没有读完,其它进程也可以共享读数据,并不需要互斥等别人读完后才能读。因为读文件是不会修改文件的内容,所以不用担心数据相互干扰的问题。

总结起来就是,多进程读写文件时,如果你想进行资源保护的话,完美的资源保护应该满足如下这样的。

1)写与写之间互斥

2)读与写之间互斥

3)读与读之间共享

如何实现以上读写要求?

如果使用信号量来实现保护的话,只能是一律互斥,包括读与读都是互斥的,不能够向上面描述的,既能互斥又能共享,但是文件锁可以做到。

文件锁的读锁与写锁

对文件加锁时可以加两种锁,分别是“读文件锁”和“写文件锁”,我们这里简称为读锁和写锁。

读锁、写锁之间关系

①读锁和读锁共享:可以重复加读锁,别人加了读锁在没有解锁之前,我依然可以加读锁,这就是共享。

②读锁与写锁互斥:别人加了读锁没有解锁前,加写锁会失败,反过来也是如此。

加锁失败后两种处理方式,

(1)阻塞,直到别人解锁然后加锁成功为止

(2)出错返回,不阻塞

③写锁与写锁互斥:别人加了写锁在没有解锁前,不能加写锁,加写锁会失败。

加锁失败后两种处理方式,

(1)阻塞,直到别人解锁然后加锁成功为止

(2)出错返回,不阻塞

使用文件锁对文件进行保护

读文件时加读锁,写文件时就加写锁,然后就可以很容易的实现符合如下要求的资源保护。

1)写与写之间互斥

2)读与写之间互斥

3)读与读之间共享

高级IO——文件锁

文件锁的加锁方式

(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
View Code

相关文章:

  • 2021-10-11
  • 2022-12-23
  • 2021-11-26
  • 2021-11-23
  • 2022-01-12
  • 2022-12-23
  • 2021-11-24
  • 2022-12-23
猜你喜欢
  • 2021-10-18
  • 2019-02-27
  • 2021-06-10
  • 2022-01-17
  • 2021-06-26
相关资源
相似解决方案