什么是文件锁?
即锁住文件,不让其他程序对文件做修改!
为什么要锁住文件?
案例,有两个程序,都对一个文件做写入操作。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define FILE_NAME "flock_demo.txt"
int main(void) {
int fd = -1;
char buf[64] = "hello, data one!\n"; // 程序1写入文件内容
//char buf[64] = "hi, data two!\n"; // 程序2写入文件内容
fd = open(FILE_NAME, O_RDWR|O_CREAT, 00666);
if (fd < 0) {
fprintf(stderr, "open file %s failed! reason: %s\n", FILE_NAME, strerror(errno));
exit(-1);
}
int ret = write(fd, buf, sizeof(buf));
if (-1 == ret) {
fprintf(stderr, "write failed. reason: %s\n", strerror(errno));
exit(-2);
}
printf("write successful! start sleep 10s...\n");
sleep(10);
close(fd);
return 0;
}
程序1对文件flock_demo.txt写入hello, data one!
程序2对文件flock_demo.txt写入hi, data two!
然后都睡眠10秒后才调用close函数关闭文件,然后程序结束;
首先运行程序1,然后运行程序2,来看看运行后的文件结果!
根据图片测试结果可知,程序1先将hello, data one!写入文件,然后睡眠10秒钟;然后程序2将hi, data two!写入文件,将程序1写入的内容给覆盖掉了,然后睡眠10秒;10秒后程序1执行close函数关闭文件退出程序,10秒后程序2执行close函数关闭文件退出程序。
但此时文件内容保存的是程序2写入的内容,程序1的调用者肯定就会很纳闷了,为什么我调用程序应该要写入hello, data one!才对,它为什么写入了hi, data two!呢?
如果此时程序1执行一次,而程序二不执行,那么程序1就可以正常将hello, data one!写入到文件中去,那么程序1的调用就很懵逼了,百思不得其解。。。
基于以上情况,我们操作文件前需要使用文件锁!
#include <unistd.h>
#include <fcntl.h>
int fcntl (int fd, int cmd, ... /* arg */ );
描述:文件上锁、解锁等。
fd:
文件描述符;
cmd:
取值 F_GETLK, F_SETLK 和 F_SETLKW,分别表示获取锁、设置锁和同步设置锁.
返回值:
成功: 返回 0;或者返回:F_DUPFD、F_GETFD、F_GETFL、F_GETLEASE、F_GETOWN、F_GETSIG、F_GETPIPE_SZ;对于一个成功的调用,返回值取决于操作;
失败: 返回 -1,并且设置错误编号 errno ,可用( strerror(errno) ) 查看.
文件锁的表示
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 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。
// struct flock 结构体说明 struct flock { short l_type; /*F_RDLCK, F_WRLCK, or F_UNLCK */ off_t l_start; /*offset in bytes, relative to l_whence */ short l_whence; /*SEEK_SET, SEEK_CUR, or SEEK_END */ off_t l_len; /*length, in bytes; 0 means lock to EOF */ pid_t l_pid; /*returned with F_GETLK */ }; l_type: 第一个成员是加锁的类型:只读锁,读写锁,或是解锁。 l_start和l_whence: 用来指明加锁部分的开始位置。 l_len: 是加锁的长度。设0表示对整个文件加锁。 l_pid: 是加锁进程的进程id。 举例: 我们现在需要把一个文件的前三个字节加读锁,则该结构体的l_type=F_RDLCK, l_start=0, l_whence=SEEK_SET, l_len=3, l_pid=-1,然后调用fcntl函数时, cmd参数设F_SETLK. |
1. 文件上锁
struct flock fflock;
memset(&fflock, 0, sizeof(struct flock));
// 上锁
fflock.l_type = F_WRLCK; /* 写锁 */
//fflock.l_type = F_RDLCK; /* 读锁 */
fflock.l_whence = SEEK_SET;
fflock.l_start = 0;
fflock.l_len = 0; // 为0,锁整个文件
fflock.l_pid = -1;
// 上锁或解锁,判断是否成功
//if (fcntl(fd, F_SETLKW, &fflock) < 0) {
if (fcntl(fd, F_SETLK, &fflock) < 0) {
fprintf(stderr, "set file lock failed! reason: %s\n", strerror(errno));
return -1;
}
2. 文件解锁
struct flock fflock;
memset(&fflock, 0, sizeof(struct flock));
fflock.l_type = F_UNLCK; // 解锁
fflock.l_whence = SEEK_SET;
fflock.l_start = 0;
fflock.l_len = 0; // 为0,解锁整个文件
fflock.l_pid = -1;
// 上锁或解锁,判断是否成功
//if (fcntl(fd, F_SETLKW, &fflock) < 0) {
if (fcntl(fd, F_SETLK, &fflock) < 0) {
fprintf(stderr, "set file lock failed! reason: %s\n", strerror(errno));
return -1;
}
3. 获取文件是否上锁
struct flock fflock;
memset(&fflock, 0, sizeof(struct flock));
// 获取当前文件是否已经上锁
int ret = fcntl(fd, F_GETLK, &fflock); // F_GETLK
if (-1 == ret) {
fprintf(stderr, "fcntl get lock failed! reason: %s\n", strerror(errno));
return -1;
}
// 文件已上锁
if (fflock.l_type != F_UNLCK) {
if (fflock.l_type == F_RDLCK) { // 文件已上读锁
printf("flock has been set to read lock by %d\n", fflock.l_pid);
// return;
} else if (fflock.l_type == F_WRLCK) { // 文件已上写锁
printf("flock has been set to write lock by %d\n", fflock.l_pid);
// return;
}
return -1;
}
例:
#include <unistd.h>
#include <fcntl.h> // file lock
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define FILE_NAME "flock_demo.txt"
int flock_set(int fd, int type) {
// 获取当前进程id
printf("pid = %d come in.\n", getpid());
struct flock fflock;
memset(&fflock, 0, sizeof(struct flock));
// 获取当前文件是否已经上锁
int ret = fcntl(fd, F_GETLK, &fflock);
if (-1 == ret) {
fprintf(stderr, "fcntl get lock failed! reason: %s\n", strerror(errno));
return -1;
}
// 文件已上锁
if (fflock.l_type != F_UNLCK) {
if (fflock.l_type == F_RDLCK) {
printf("flock has been set to read lock by %d\n", fflock.l_pid);
} else if (fflock.l_type == F_WRLCK) {
printf("flock has been set to write lock by %d\n", fflock.l_pid);
}
return -1;
}
// lock file
fflock.l_type = type;
fflock.l_whence = SEEK_SET;
fflock.l_start = 0;
fflock.l_len = 0;
fflock.l_pid = -1;
// 上锁或解锁,判断是否成功
//if (fcntl(fd, F_SETLKW, &fflock) < 0) {
if (fcntl(fd, F_SETLK, &fflock) < 0) {
fprintf(stderr, "set file lock failed! reason: %s\n", strerror(errno));
return -1;
}
switch(fflock.l_type) {
case F_RDLCK: {
printf("read lock is set by %d\n", getpid());
}
break;
case F_WRLCK: {
printf("write lock is set by %d\n", getpid());
}
break;
case F_UNLCK: {
printf("lock is released by %d\n", getpid());
}
break;
default:
break;
}
printf("Process pid = %d out.\n", getpid());
return 0;
}
int main(void) {
int fd = -1;
fd = open(FILE_NAME, O_RDWR|O_CREAT, 00666);
if (fd < 0) {
fprintf(stderr, "open file %s failed! reason: %s\n", FILE_NAME, strerror(errno));
exit(-1);
}
// 1.F_WRLCK
int ret = flock_set(fd, F_WRLCK); // 写锁
//int ret = flock_set(fd, F_WRLCK); // 读锁
if (-1 != ret) {
getchar();
// 2.F_UNLCK
flock_set(fd, F_UNLCK);
getchar();
}
close(fd);
return 0;
}
用以上的代码编译一个读锁程序,编译一个写锁程序:
main函数中替换以下代码进行编译
int ret = flock_set(fd, F_WRLCK); // 写锁
int ret = flock_set(fd, F_WRLCK); // 读锁
gcc file_lock.c -o file_w_lock
gcc file_lock.c -o file_r_lock
1. 设置锁使用:F_SETLK
情况一:读锁 和 读锁
结论:可以看出,两个程序都可以对文件进行加读锁!
情况二:读锁 和 写锁
结论:当文件上读锁后,就无法再给文件上写锁,且也无法检测处文件上锁状态;
即(./file_w_lock)执行代码 int ret = fcntl(fd, F_GETLK, &fflock); 后,其 fflock.l_type != F_UNLCK ,所以没法做中断操作;下面再对文件进行上锁操作,就函数返回-1了!
情况三:写锁 和 读锁
结论:文件上写锁后,再给文件上读锁,可以被检测出来文件已经上锁了,程序就返回结束了
情况四:写锁 和 写锁
结论:文件上写锁后,再给文件上写锁,可以被检测出来文件已经上锁了,程序就返回结束了
2. 设置锁使用:F_SETLKW
使用F_SETLKW会有等待阻塞的情况!
情况一:读锁 和 读锁
结论:可以看出,两个程序都可以对文件进行加读锁!
情况二:读锁 和 写锁
先给文件上读锁,再个文件上写锁,此时上写锁程序在获取文件的上锁状态时,被阻塞在此等待;
当我们在程序1那里按下回车键后,对文件解锁;右边程序就可以正常对文件上写锁了;否则会一直阻塞。。。
情况三:写锁 和 读锁
文件上写锁后,再给文件上读锁,可以被检测出来文件已经上写锁了,程序就返回结束了
情况四:写锁 和 写锁
结论:文件上写锁后,再给文件上写锁,可以被检测出来文件已经上写锁了,程序就返回结束了
3. 其他情况
这里还有另一种情况,就是程序1对文件上锁,程序2没有对文件上锁,然后对文件做写入操作
程序1还是上面的程序代码;
程序2代码如下:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#define FILE_NAME "flock_demo.txt"
int main(void) {
int fd = -1;
char buf[64] = "write file data...\n";
fd = open(FILE_NAME, O_RDWR|O_CREAT, 00666);
if (fd < 0) {
fprintf(stderr, "open file %s failed! reason: %s\n", FILE_NAME, strerror(errno));
exit(-1);
}
int ret = write(fd, buf, sizeof(buf));
if (-1 == ret) {
fprintf(stderr, "write failed. reason: %s\n", strerror(errno));
exit(-2);
}
printf("write successful!\n");
close(fd);
return 0;
}
gcc file_write.c -o file_write
文件上读锁:
程序1给文件上读锁后,程序2依然可以 对文件做写入操作!
文件上写锁:
程序1给文件上写锁后,程序2依然可以对文件做写入操作!
结论:
由以上两个测试可知,给文件上锁,仅仅对相应也给文件上锁的程序可以限制,对于没有对文件上锁的程序无任何限制!
所以,在开发中,就得做出规定,大家对文件操作时都得对文件上锁后再进行操作!
总结
文件上锁的几种情况已经列举出来了,使用时注意一下就行!