Unix 文件锁(共享锁、排他锁)
文章目录
- 文件锁
- 锁类型
- 共享锁(读锁)
- 排他锁(写锁)
- fcntl函数+flock结构对文件锁的操作
- 代码例子
- 协议锁和强制锁
- 文件锁的内核结构
文件锁
Unix提供了文件锁机制来防止多进程对同一文件的并发操作导致的脏读和数据混乱,同时也为多进程提供了同步机制。
锁类型
共享锁(读锁)
共享锁也称为读锁。
如果一个进程为某个文件的某个区域加了一把共享锁,那么其他进程对该文件的该区域可以加共享锁不能加排他锁。
排他锁(写锁)
排他锁也称为写锁。
如果一个进程为某个文件的某个区域加了一把排他锁,那么其他进程无法对该文件的该区域加任何锁,包括共享锁和排他锁。
fcntl函数+flock结构对文件锁的操作
fcntl()
函数定义的头文件:#include <fcntl.h>
函数定义:
int fcntl(int fd, F_GETLK|F_SETLK|F_SETLKW, struct flock* lock);
第一个参数是需要加锁的文件描述符;第二个参数是fcntl函数的命令,F_GETLK
、F_SETLK
、F_SETLKW
这三个宏定义被用来获取、释放和测试锁记录是否存在;第三个参数为一个指向flock结构的指针。F_GETLK
:获取锁记录F_SETLK
:非阻塞模式加锁F_SETLKW
:阻塞模式加锁
flock
结构如下:
struct flock {
...
short l_type; /* 锁类型: F_RDLCK - 读锁 F_WRLCK - 写锁 F_UNLCK - 释放锁 */
short l_whence; /* 锁偏移方式: SEEK_SET - 从文件头开始 SEEK_CUR - 从当前位置开始 SEEK_END - 文件结尾 */
off_t l_start; /* 锁定区域起始位置 */
off_t l_len; /* 从起始位置开始锁定的字节数,如果被设置为0, 则表示从起始位置(l_start)直接锁定到文件结尾 */
pid_t l_pid; /* 加锁的进程pid号,-1由系统填写设置 */
...
};
单个进程只能持有文件区域上的一种类型的锁;如果将一个新锁设置到一个已经被锁定的区域,那么旧锁将被转换为新的锁类型;如果新锁定的区域与旧锁定的区域范围不一致,那么会涉及到拆分、缩小或合并现有的锁。
代码例子
该程序演示了给文件加锁并写入数据,数据写入速度为每个字符一秒。如果将锁操作的代码去掉,并同时在两个终端运行该程序,会发现文件中的内容是混乱的。如果加上锁就会发现写入的数据是有序的。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
//加写锁
int wlock(int fd, int wait) {
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = -1;
return fcntl(fd, wait ? F_SETLKW : F_SETLK, &lock);
}
//加读锁
int rlock(int fd, int wait) {
struct flock lock;
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = -1;
return fcntl(fd, wait ? F_SETLKW : F_SETLK, &lock);
}
//解锁
int ulock(int fd) {
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = -1;
return fcntl(fd, F_SETLK, &lock);
}
int main(void) {
int fd = open("./a.txt", O_RDWR | O_CREAT | O_APPEND);
if (fd == -1) {
perror("open");
return -1;
}
//加锁
if (wlock(fd, 1) == -1) {
perror("wlock");
return -1;
}
char *buf = "hello world!\n";
for (int i = 0; i < strlen(buf); ++i) {
if (write(fd, &buf[i], sizeof(buf[0])) == -1) {
perror("write");
return -1;
}
sleep(1);
}
//释放锁
if (ulock(fd) == -1) {
perror("ulock");
return -1;
}
close(fd);
return 0;
}
协议锁和强制锁
协议锁:顾名思义,按照协议进行加锁操作。如同交通规则,红灯停绿灯行。但是你不遵守交通规则,硬闯红灯,也能通过马路,但是你可能会出车祸。协议锁就如何交通规则,对文件写的操作,不加写锁可以不?可以!就算别的进程已经给该文件加了锁,正在写入,你一样的可以写,但是你写入的数据无法保障它的完整性,同时也破坏了别人写入的数据完整性,读锁亦是如此。Unix实现的就是协议锁,只是单纯的在文件表项中维护了当前的锁状态。
强制锁:只要加锁了,别的进程不管使用何种方式,都无法写入数据,除非你释放锁。
文件锁的内核结构
Unix内核维护了一个锁表结构,用来实现对文件的加锁解锁操作。在系统内核中一个V节点代表了一个文件系统中的I节点也就代表了一个文件,每个V节点在系统内核中是全局唯一的,V节点中存放了指向该文件的锁表结构的锁表指针。
每个进程在对文件进行加锁解锁的动作时,系统内核会收集所有进程对文件所加的锁,以链表的形式进行存储,锁链表中每个节点都是一个对该文件某区域所加的锁信息。任何进程对文件进行加锁,系统都会遍历该文件对应的锁表,发现锁冲突,则阻塞或报错,否则将锁插入锁表中。当进程进行解锁时,系统则会将该锁从链表中删除。
在一个进程中,如果多个文件描述符引用同一个文件,只要关闭其中任何一个文件描述符,该进程对该文件所加的所有的锁都会被释放掉。
还没有评论,来说两句吧...