1. 文件锁
当多个进程打开同一文件进行读写时可能会出现数据混乱的问题,原因在于进程间共享同一文件读写指针位置,也就是f_pos(关于f_pos参考:4-文件描述符与打开的文件之间的关系的第三小节file结构体)。
解决这个问题的方法有很多,比如可以使用信号量完成进程同步,但通常使用文件锁会更好一些,因为内核会将锁跟文件关联起来,这就需要借助fcntl函数来实现对一个文件进行加锁。只有拿到锁的进程可以对文件进行读写操作,而没有获得锁的进程操作文件可以打开文件,但无法执行read、write操作,以此来防止进程间对同一文件进行读写出现的数据混乱问题。
2. fcntl函数
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
参数fd:指定文件描述符
参数cmd:一般用于设置文件锁,F_SETLK和F_SETLKW 用于加锁解锁, F_GETLK用于获取文件锁。
F_SETLK设置文件锁,如果另一进程已经加锁,那么fcntl将会失败并返回EAGAIN错误,不会阻塞。
F_SETLKW设置文件锁,如果另一进程已经加锁,那么该进程将会阻塞,直到文件锁被释放。
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; //具体起始偏移位置,0表示文件开头
off_t l_len; //长度:其实是表示加锁的范围(加多少个字节),如果为0表示整个文件加锁
pid_t l_pid; //持有该锁的进程ID:(只有调用F_GETLK only,才会用到这个参数)
...
};
加锁范围:
1. 如果 len > 0, [whence+start , whence+start+len) ,包左不包右
2. 如果 len = 0, [whence+start , ∞),包左不包右,表示对整个文件加锁
以whence为参考位置开始,加上start就是具体的起始位置。例如:whence = SEEK_SET,start = 10,那么参考位置就是文件开头(偏移为0的位置),加上start后,具体的起始位置就是偏移为10的位置。
3. 文件锁示例
多个进程对加锁文件进行访问:
1. 首先测试两个进程间以读方式加锁
2. 然后测试两个进程间以写方式加锁
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
void sys_err(char *str){
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
int fd;
struct flock f_lock;
//参数不够
if (argc < 2) {
printf("./a.out filename\n");
exit(1);
}
//打开文件失败
if ((fd = open(argv[1] , O_RDWR)) < 0){
sys_err("open");
}
//选用写琐
f_lock.l_type = F_WRLCK;
//选用读琐
//f_lock.l_type = F_RDLCK;
//设置文件指针为文件开头
f_lock.l_whence = SEEK_SET;
//起始偏移,0表示文件开头
f_lock.l_start = 0;
// l_len表示加锁的长度,0表示整个文件加锁
f_lock.l_len = 0;
//设置属性,让线程阻塞(F_SETLKW),直到加锁成功
fcntl(fd, F_SETLKW, &f_lock);
//如果加锁成功,打印get flock
printf("get flock\n");
//为了测试效果明显,休眠10秒
sleep(10);
//设置锁类型为解锁
f_lock.l_type = F_UNLCK;
//设置属性,解锁不成功阻塞
fcntl(fd, F_SETLKW, &f_lock);
//解锁成功打印unflock
printf("un flock\n");
close(fd);
return 0;
}
测试两个进程间以写方式加锁,程序执行结果:
从图中可以看出,以写方式进行加锁的话,那么左1进程对文件以写方式加锁成功,右1进程对文件以写方式加锁会失败并阻塞,等到左1进程释放写锁后,右1进程立马获取到了写锁。
修改部分代码,测试两个进程间以读方式加锁:
//选用写琐
//f_lock.l_type = F_WRLCK;
//选用读琐
f_lock.l_type = F_RDLCK;
以读方式进行加锁的话,那么左1进程对文件以读方式加锁成功,右1进程对文件以读方式加锁也会成功。
由此我们可以得出结论:进程间对文件进行加文件锁也遵循“读共享、写独占”特性。但!如若进程不加锁直接操作文件,依然可访问成功,但数据势必会出现混乱。