Linux 文件锁 - fcntl

news2024/11/18 4:50:12

什么是文件锁?

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

为什么要锁住文件?

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

#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依然可以对文件做写入操作!

结论:

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

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


总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338717.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【集群】Slurm作业调度系统的使用

最近使用集群进行实验&#xff0c;记录并学习集群系统进行深度学习的实验过程。集群所使用的作业调度系统为Slurm&#xff0c;这里记录下使用的常用命令和一些注意事项。 Slurm简介 Slurm是一个开源&#xff0c;容错&#xff0c;高度可扩展的集群管理和作业调度系统&#xff0…

excel数据处理: 如何用99个空格提取单元格数据

脑洞大开&#xff0c;提取单元格数据用99个空格就成&#xff01;真想扒开那些大神的脑袋看看&#xff0c;是怎么想出这样匪夷所思的方法的。需要从规格型号中提取容值、封装、耐压三组数据&#xff0c;如下&#xff1a;数据源在A列&#xff0c;数据量很大&#xff0c;需要提取的…

微信小程序Springboot短视频分享系统

3.1小程序端 用户注册页面&#xff0c;输入用户的个人信息点击注册即可。 注册完成后会返回到登录页面&#xff0c;用户输入自己注册的账号密码即可登录成功 登录成功后我们可以看到有相关的视频还有视频信息&#xff0c;我的信息等。 视频信息推荐是按照点击次数进行推荐的&am…

Zabbix 构建监控告警平台(四)

Zabbix ActionZabbix Macros1.Zabbix Action 1.1动作Action简介 当某个触发器状态发生改变(如Problem、OK)&#xff0c;可以采取相应的动作&#xff0c;如&#xff1a; 执行远程命令 邮件&#xff0c;短信&#xff0c;微信告警,电话 1.2告警实验简介 1. 创建告警media type&…

9.语义HTMLVScode扩展推荐

语义HTML 定义&#xff1a; 一个元素使用我们并不是只关心他是什么样子的&#xff0c;而是要去关心这个元素名称的实际意义或者代表什么 我们使用标签并不是他仅代表导航栏&#xff0c;只是将导航栏部分归为一个块。现实生活中&#xff0c;多使用之前都是使用div这个元素去构…

删除有序数组中的重复项-力扣26-java

一、题目描述给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。由于在某些语言中不能改变数组的长度&#xff0c;所以必须将结果放在数组nums…

软件设计(九)

软件设计&#xff08;八&#xff09;https://blog.csdn.net/ke1ying/article/details/128954569?spm1001.2014.3001.5501 81、模块A将学生信息&#xff0c;即学生姓名、学号、手机等放到一个结构体系中&#xff0c;传递给模块B&#xff0c;模块A和B之间的耦合类型为 什么耦合…

【C++设计模式】学习笔记(1):面向对象设计原则

目录 简介面向对象设计原则(1)依赖倒置原则(DIP)(2)开放封闭原则(OCP)(3)单一职责原则(SRP)(4)Liskov替换原则(LSP)(5)接口隔离原则(ISP)(6)优先使用对象组合,而不是类继承(7)封装变化点(8)针对接口编程,而不是针对实现编程结语简介 Hello! 非常感谢您阅读海…

变分自编码器背后的直觉【VAE】

在阅读有关机器学习的内容时&#xff0c;你遇到的大部分材料可能都与分类问题有关。 你有一个特定的输入&#xff0c;ML 模型试图找出该输入的特征。 例如&#xff0c;分类模型可以决定图像中是否包含猫。 当你想创建具有预定义特征的数据时&#xff0c;反过来又如何呢&#x…

再不跳槽,就晚了

从时间节点上来看&#xff0c;3月、4月是每年跳槽的黄金季&#xff01; 以 BAT 为代表的互联网大厂&#xff0c;无论是薪资待遇、还是平台和福利&#xff0c;都一直是求职者眼中的香饽饽&#xff0c;“大厂经历” 在国内就业环境中无异于一块金子招牌。在这金三银四的时间里&a…

预处理指令详解

预处理指令详解**1.预定义符号****2.#define****2.1 #define 定义标识符****2.2 #define 定义宏****2.3 #define 替换规则****2.4 #和##****#的作用****##的作用****2.5 带副作用的宏参数****2.6 宏和函数的对比****宏和函数对比图****2.7 命名约定****3.#undef**4.条件编译4.1…

Leg转Goh引擎和架设单机+配置登陆器教程

教程准备1、Leg版本一个2、Goh引擎一套3、电脑一台(最好联网)前言&#xff1a;BLUE/LEGS/Gob/Goh/九龍、4K、AspM2第一步&#xff1a;更换引擎1、把版本自带的LEG引擎换成Goh引擎2、删除服务端里面的exe、dll文件(也可以直接更新)3、清理登录和游戏网关里面的配置文件4、更新引…

Sandman:一款基于NTP协议的红队后门研究工具

关于Sandman Sandman是一款基于NTP的强大后门工具&#xff0c;该工具可以帮助广大研究人员在一个安全增强型网络系统中执行红队任务。 Sandman可以充当Stager使用&#xff0c;该工具利用了NTP&#xff08;一个用于计算机时间/日期同步协议&#xff09;从预定义的服务器获取并…

菌子导航系统(持续开发中)

文章目录菌子导航前言项目架构spring-cloud 和 spring-boot 版本选择使用到的组件&#xff08;依赖&#xff09;架构分层项目基本功能1 使用Nacos做配置中心2 logback日志3 mybatis-plus操作数据库4 Caffeine 缓存整合5 LocalDateTime 序列化&反序列化6 参数校验快速失败配…

ubuntu20.04 系统下 .7z 文件解压缩到指定的目录下

问题描述 环境&#xff1a; ubuntu 20.04 ubuntu 下有个 7z 的压缩文件需要解压&#xff0c;需要解压到指定的目录下&#xff0c;而不是压缩包当前目录下 安装 p7zip-full ubuntu 下的 7z 解压软件&#xff1a; p7zip-full 安装命令&#xff1a; sudo apt install p7zip-fu…

04-PS人像磨皮方法

1.高斯模糊磨皮 这种方法的原理就是建立一个将原图高斯模糊后图层, 然后用蒙版加画笔或者历史画笔工具将需要磨皮的地方涂抹出来, 通过图层透明度, 画笔流量等参数来控制磨皮程度 1.新建图层(命名为了高斯模糊磨皮), 混合模式设置为正常, 然后选择高斯模糊, 模糊数值设置到看…

前端也能悄悄对视频截图?js实现对视频按帧缓存

前言 虽然最后没有采用这种方案来实现滚动控制视频进度&#xff0c;但是仍然想自己试试这种方案的实现&#xff0c;毕竟应用范围也挺广的。 核心代码并不多&#xff0c;算是一篇小短文&#xff5e;。 掘金好像不允许放站外演示链接&#xff0c;所以这里就用动图大概展示下最终…

STL——list

一、list介绍及使用 1. list文档介绍 &#xff08;1&#xff09;list是可以在常数范围内&#xff0c;在任意位置进行插入、删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 &#xff08;2&#xff09;list的底层是带头结点的双向循环链表&#xff0c;其中每个元素…

【Java|golang】2335. 装满杯子需要的最短总时长

现有一台饮水机&#xff0c;可以制备冷水、温水和热水。每秒钟&#xff0c;可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。 给你一个下标从 0 开始、长度为 3 的整数数组 amount &#xff0c;其中 amount[0]、amount[1] 和 amount[2] 分别表示需要装满冷水、温水和热水的…

web期末复习 2023.02.11

文章目录Web 的概念Web 组成用户通过浏览器请求资源的过程:HTML 超文本标记语言CSS插入样式表的方法有三种:对象&#xff0c;类&#xff0c;实例一个完整的 JavaScript 实现是由以下 3 个不同部分组成的&#xff1a;JavaScript 用法什么是 Java Server Pages?JSP 注释JSP 的 J…