文件锁(二)——文件锁的读锁和写锁
文件锁的读锁和写锁对文件加锁时可以加两种锁,分别是“读文件锁”和“写文件锁”,简称读锁和写锁。读锁、写锁之间关系读锁和读锁共享:可以重复加读锁,别人加了读锁在没有解锁之前,我依然可以加读锁,这就是共享。读锁和写锁互斥:别人加了读锁没解锁前,加写锁会失败,反过来也是如此。加锁失败后两种处理方式:阻塞,直到别人解锁然后加锁成功为止。出错返回,不阻塞写锁与写锁互斥:别人加了写锁在没有解锁前,不能加写锁,
文件锁的读锁和写锁
对文件加锁时可以加两种锁,分别是“读文件锁”和“写文件锁”,简称读锁和写锁。
读锁、写锁之间关系
- 读锁和读锁共享:可以重复加读锁,别人加了读锁在没有解锁之前,我依然可以加读锁,这就是共享。
- 读锁和写锁互斥:别人加了读锁没解锁前,加写锁会失败,反过来也是如此。
- 加锁失败后两种处理方式:
- 阻塞,直到别人解锁然后加锁成功为止。
- 出错返回,不阻塞
- 加锁失败后两种处理方式:
- 写锁与写锁互斥:别人加了写锁在没有解锁前,不能加写锁,加写锁会失败。
-
加锁失败后两种处理方式:
- 阻塞,直到别人解锁然后加锁成功为止。
- 出错返回,不阻塞
-
文件的加锁方式
-
对整个文件的内容加锁
对整个文件加锁是最常用的文件加锁方式。
当你整个文件加锁时,如果文件的长度因为写入新数据或者截短而发生了变化,加锁内容长度会自动变化,保证对内容变化着的整个文件加锁。 -
对文件某部分内容加锁
不过一般来说,对多少内容加锁,就对多少内容解锁。如果你是对整个文件加锁,就将整个文件解锁。
但是实际上加锁和实际解锁的长度不相同,比如我对1000个字节的内容加了锁,但是只对其中的100字节解锁,不过这种情况用的少,知道怎么回事即可。
怎么实现文件的整个加锁和区域解锁呢?
后面再说
-
再看看fcntl的函数原型
int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );
第三个参数是...,fcntl函数是一个变参函数,第三个参数用不到时就不写
- 功能
fcntl函数有多种功能,我们这里主要介绍实现文件锁的功能,当cmd被设置的是与文件锁相关的宏时,fcntl就是用来实现文件锁。 - 参数
- 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_SETLKW一样,只不过设置的是阻塞文件锁,也就是说加锁不成功的话就阻塞,是由F_SETLKW宏来决定的。
- F_GETLK
- int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );
- 第三个参数
- 第三个参数设置为什么视情况而定,如果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 (set by F_GETLK and F_OFD_GETLK) */ ... };
- 第三个参数
成员说明:
-
l_type:锁类型
- F_RDLCK:读锁(或称共享锁)
- F_WRLCK:写锁
- F_UNLCK:解锁
-
l_whence:加锁位置粗定位,设置同lseek的whence
- SEEK_SET:文件开始处
- SEEK_CUR:文件当前位置处
- SEEK_END:文件末尾位置处
- l_whence这个与lseek函数的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,就表示从文件加锁到文件末尾,其实就是对整个文件加锁。
如果只是对文件中间的某段加锁,这只是区域加锁,加区域锁时可以给文件n多个的独立区域加锁。
-
l_pid:当前正加锁进程的PID
代码演示
使用文件锁的互斥操作,解决父子进程向同一文件写"hello",“world\n”时,hello hello world相连的问题。
头文件,学到了封装函数的思想,尤其是使用宏来实现不同类型的文件锁
/* Too many parameters ,Different types of locks
* Write dead!!!!!!!!!
* */
/* Set non blocking write lock */
#define SET_WRFLCK(fd, l_whence, l_start, l_len) \
set_filelock(fd, F_SETLK, F_WRLCK, l_whence, l_start, l_len)
/* Set blocking write lock */
#define SET_WRFLCKW(fd, l_whence, l_start, l_len) \
set_filelock(fd, F_SETLKW, F_WRLCK, l_whence, l_start, l_len)
/* Set blocking read lock */
#define SET_RDFLCKW(fd, l_whence, l_start, l_len) \
set_filelock(fd, F_SETLKW, F_RDLCK, l_whence, l_start, l_len)
/* Set nonblocking read lock */
#define SET_RDFLCK(fd, l_whence, l_start, l_len) \
set_filelock(fd, F_SETLK, F_RDLCK, l_whence, l_start, l_len)
/* Unlock */
#define SET_UNFLCK(fd, l_whence, l_start, l_len) \
set_filelock(fd, F_SETLK, F_UNLCK, l_whence, l_start, l_len)
/*Package and set the locking function
* */
static void set_filelock(int fd, int ifwait, short l_type, short l_whence, off_t l_start, short l_len)
{
int ret = 0;
struct flock flock;
flock.l_type = l_type;
flock.l_whence = l_whence;
flock.l_start = l_start;
flock.l_len = l_len;
ret = fcntl(fd, ifwait, &flock);
if (ret == -1)
{
perror("fcntl");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
int fd = 0;
int ret = 0;
fd = open("./hello", O_RDWR|O_CREAT|O_TRUNC, 0664);
if (fd == -1) print_err("./hello", __LINE__, errno);
ret = fork();
if (ret > 0)
{
while(1)
{
SET_WRFLCKW(fd, SEEK_SET, 0, 0);
write(fd, "hello ", 6);
write(fd, "world\n", 6);
SET_UNFLCK(fd, SEEK_SET, 0, 0);
}
}
else if (ret == 0)
{
while(1)
{
SET_WRFLCKW(fd, SEEK_SET, 0, 0);
write(fd, "hello ", 6);
write(fd, "world\n", 6);
SET_UNFLCK(fd, SEEK_SET, 0, 0);
}
}
return 0;
}
在hello文件里面使用/hello hello没找到文本内容说明成功了。
对于fcntl的认识不仅仅可以追加改变文件标志,还可以实现文件锁的功能
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)