当前位置: 首页 > news >正文

Linux 文件锁 - fcntl

什么是文件锁?

即锁住文件,不让其他程序对文件做修改!

为什么要锁住文件?

案例,有两个程序,都对一个文件做写入操作。

#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) {// 获取当前进程idprintf("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 filefflock.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_WRLCKint ret = flock_set(fd, F_WRLCK);   // 写锁//int ret = flock_set(fd, F_WRLCK); // 读锁if (-1 != ret) {getchar();// 2.F_UNLCKflock_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依然可以对文件做写入操作!

结论:

        由以上两个测试可知,给文件上锁,仅仅对相应也给文件上锁的程序可以限制,对于没有对文件上锁的程序无任何限制!

        所以,在开发中,就得做出规定,大家对文件操作时都得对文件上锁后再进行操作!


总结

        文件上锁的几种情况已经列举出来了,使用时注意一下就行!

http://www.lryc.cn/news/3349.html

相关文章:

  • CellularAutomata元胞向量机-2-初等元胞自动机MATLAB代码分享
  • OpenStack云平台搭建(6) | 部署Neutron
  • Lesson 05.Configuring the Oracle Network Environment
  • 理论五:接口vs抽象类的区别,如何用普通的类模拟抽象类和接口
  • 【Hello Linux】 Linux的权限以及Shell原理
  • 【STM32】【HAL库】遥控关灯2 分机
  • 代码随想录算法训练营第27天|● 93.复原IP地址 ● 78.子集 ● 90.子集II
  • Unity UI合批的问题
  • MWORKS--系统建模与仿真
  • PC端开发GUI
  • 解读手机拍照的各个参数(拍照时,上面会有6个符号)
  • 数字钥匙最新进展文章
  • 如何在VMware虚拟机上安装运行Mac OS系统(详细图文教程)
  • C++中的强制类型转换
  • 任何人都可以学习Rasa之优秀Rasa学习资源推荐
  • 数据中心的 TCP-Delay ACK 与 RTO, RACK
  • MySQL与常见面试题
  • FFmpeg进阶: 采用音频滤镜对音频进行转码
  • C++:AVL树
  • Docker中安装Oracle-12c
  • 教你如何用Python分析出选注双色球号码
  • elasticsearch映射及字段类型
  • 1493围圈报数(队列)
  • 【ArcGIS Pro二次开发】(2):创建一个Add-in项目
  • 浏览器缓存是如何提升网站访问速度的
  • Linux中几个在终端中有趣的命令
  • 快来来试试SpringBoot3 中的新玩意~
  • 【寻人启事】达坦科技持续招人ing
  • 【C/C++基础练习题】简单函数练习题
  • 【代码随想录训练营】【Day11】第五章|栈与队列|20. 有效的括号|1047. 删除字符串中的所有相邻重复项|150. 逆波兰表达式求值