【Linux高级 I/O(6)】初识文件锁—— flock()方法(附代码示例)

news2025/4/8 2:29:13

        想象一下,当两个人同时编辑磁盘中同一份文件时,其后果将会如何呢?在 Linux 系统中,该文件的最后状态通常取决于写该文件的最后一个进程。多个进程同时操作同一文件,很容易导致文件中的数据发生混乱,因为多个进程对文件进行 I/O 操作时,容易产生竞争状态、导致文件中的内容与预想的不一致!

        对于有些应用程序,进程有时需要确保只有自己能够对某一文件进行 I/O 操作,在这段时间不允许其它进程对该文件进行 I/O 操作。为了向进程提供这种功能,Linux 系统提供了文件锁机制。  

        前面学习过互斥锁、自旋锁以及读写锁,文件锁与这些锁一样,都是内核提供的锁机制,锁机制实现用于对共享资源的访问进行保护;只不过互斥锁、自旋锁、读写锁与文件锁的应用场景不一样,互斥锁、自旋锁、读写锁主要用在多线程环境下,对共享资源的访问保护,做到线程同步。

        而文件锁,顾名思义是一种应用于文件的锁机制,当多个进程同时操作同一文件时,我们怎么保证文件数据的正确性,linux 通常采用的方法是对文件上锁,来避免多个进程同时操作同一文件时产生竞争状态。 譬如进程对文件进行 I/O 操作时,首先对文件进行上锁,将其锁住,然后再进行读写操作;只要进程没有对文件进行解锁,那么其它的进程将无法对其进行操作;这样就可以保证,文件被锁住期间,只有它(该进程) 可以对其进行读写操作。

        一个文件既然可以被多个进程同时操作,那说明文件必然是一种共享资源,所以由此可知,归根结底, 文件锁也是一种用于对共享资源的访问进行保护的机制,通过对文件上锁,来避免访问共享资源产生竞争状态。

        文件锁的分类

        文件锁可以分为建议性锁和强制性锁两种:

        ⚫ 建议性锁

        建议性锁本质上是一种协议,程序访问文件之前,先对文件上锁,上锁成功之后再访问文件,这是建议性锁的一种用法;但是如果你的程序不管三七二十一,在没有对文件上锁的情况下直接访问文件,也是可以访问的;如果是这样,那么建议性锁就没有起到任何作用,如果要使得建议性锁起作用, 那么大家就要遵守协议,访问文件之前先对文件上锁。这就好比交通信号灯,规定红灯不能通行,绿灯才可以通行,但如果你非要在红灯的时候通行,谁也拦不住你,那么后果将会导致发生交通事故;所以必须要大家共同遵守交通规则,交通信号灯才能起到作用。

        ⚫ 强制性锁:

        强制性锁比较好理解,它是一种强制性的要求,如果进程对文件上了强制性锁,其它的进程在没有获取到文件锁的情况下是无法对文件进行访问的。其本质原因在于,强制性锁会让内核检查每一个 I/O 操作(譬如 read()、write()),验证调用进程是否是该文件锁的拥有者,如果不是将无法访问文件。当一个文件被上锁进行写入操作的时候,内核将阻止其它进程对其进行读写操作。采取强制性锁对性能的影响很大,每次进行读写操作都必须检查文件锁。

        在 Linux 系统中,可以调用 flock()、fcntl()以及 lockf()这三个函数对文件上锁,接下来将向大家介绍每个函数的使用方法。

flock()函数加锁

       先来学习系统调用 flock(),使用该函数可以对文件加锁或者解锁,但是 flock()函数只能产生建议性锁, 其函数原型如下所示:

#include <sys/file.h>

int flock(int fd, int operation);

        使用该函数需要包含头文件<sys/file.h>。

        函数参数和返回值含义如下:

        fd:参数 fd 为文件描述符,指定需要加锁的文件。

        operation:参数 operation 指定了操作方式,可以设置为以下值的其中一个:

  •  LOCK_SH:在 fd 引用的文件上放置一把共享锁。所谓共享,指的便是多个进程可以拥有对同一 个文件的共享锁,该共享锁可被多个进程同时拥有。
  • LOCK_EX:在 fd 引用的文件上放置一把排它锁(或叫互斥锁)。所谓互斥,指的便是互斥锁只能同时被一个进程所拥有。
  • LOCK_UN:解除文件锁定状态,解锁、释放锁。 除了以上三个标志外,还有一个标志:
  • LOCK_NB:表示以非阻塞方式获取锁。默认情况下,调用 flock()无法获取到文件锁时会阻塞、直到其它进程释放锁为止,如果不想让程序被阻塞,可以指定 LOCK_NB 标志,如果无法获取到锁应立刻返回(错误返回,并将 errno 设置为 EWOULDBLOCK),通常与 LOCK_SH 或 LOCK_EX 一起使用,通过位或运算符组合在一起。

        返回值:成功将返回 0;失败返回-1、并会设置 errno, 对于 flock(),需要注意的是,同一个文件不会同时具有共享锁和互斥锁。

        使用示例

        下面代码演示了使用 flock()函数对一个文件加锁和解锁(建议性锁)。程序首先调用 open()函数将文件打开,文件路径通过传参的方式传递进来;文件打开成功之后,调用 flock()函数对文件加锁(非阻塞方式、排它锁),并打印出“文件加锁成功”信息,如果加锁失败便会打印出“文件加锁失败”信息。然后调用 signal 函数为 SIGINT 信号注册了一个信号处理函数,当进程接收到 SIGINT 信号后会执行 sigint_handler()函数,在信号处理函数中对文件进行解锁,然后终止进程。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
#include <signal.h>

static int fd = -1; //文件描述符

/* 信号处理函数 */
static void sigint_handler(int sig){
    if (SIGINT != sig)
    return;
    /* 解锁 */
    flock(fd, LOCK_UN);
    close(fd);
    printf("进程 1: 文件已解锁!\n");
}    

int main(int argc, char *argv[])    {
    if (2 != argc) {
        fprintf(stderr, "usage: %s <file>\n", argv[0]);
        exit(-1);
    }
    /* 打开文件 */
    fd = open(argv[1], O_WRONLY);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    /* 以非阻塞方式对文件加锁(排它锁) */
    if (-1 == flock(fd, LOCK_EX | LOCK_NB)) {
        perror("进程 1: 文件加锁失败");
        exit(-1);
    }
    printf("进程 1: 文件加锁成功!\n");

    /* 为 SIGINT 信号注册处理函数 */
    signal(SIGINT, sigint_handler);
    for ( ; ; )
        sleep(1);
}

        加锁成功之后,程序进入了 for 死循环,一直持有锁;此时我们可以执行另一个程序,如下所示,该程序首先也会打开文件,文件路径通过传参的方式传递进来,同样在程序中也会调用 flock()函数对文件加锁(排它锁、非阻塞方式),不管加锁成功与否都会执行下面的 I/O 操作,将数据写入文件、在读取出来并打印。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char buf[100] = "Hello World!";
    int fd;
    int len;
    if (2 != argc) {
        fprintf(stderr, "usage: %s <file>\n", argv[0]);
        exit(-1);
    }
    
    /* 打开文件 */
    fd = open(argv[1], O_RDWR);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }

    /* 以非阻塞方式对文件加锁(排它锁) */
    if (-1 == flock(fd, LOCK_EX | LOCK_NB))
        perror("进程 2: 文件加锁失败");
    else
        printf("进程 2: 文件加锁成功!\n");

    /* 写文件 */
    len = strlen(buf);
    if (0 > write(fd, buf, len)) {
        perror("write error");
        exit(-1);
    }
    printf("进程 2: 写入到文件的字符串<%s>\n", buf);

    /* 将文件读写位置移动到文件头 */
    if (0 > lseek(fd, 0x0, SEEK_SET)) {
        perror("lseek error");
        exit(-1);
    }
    /* 读文件 */
    memset(buf, 0x0, sizeof(buf)); //清理 buf
    if (0 > read(fd, buf, len)) {
        perror("read error");
        exit(-1);
    }
    printf("进程 2: 从文件读取的字符串<%s>\n", buf);

    /* 解锁、退出 */
    flock(fd, LOCK_UN);
    close(fd);
    exit(0);
}

        把上文代码分别作为应用程序 1和应用程序 2,将它们分别编译成不同的可执行文件 testApp1 和 testApp2,如下所示:

         在进行测试之前,创建一个测试用的文件 infile,直接使用 touch 命令创建即可,首先执行 testApp1 应用程序,将 infile 文件作为输入文件,并将其放置在后台运行:

         testApp1 会在后台运行,由 ps 命令可查看到其 pid 为 20710。接着执行 testApp2 应用程序,传入相同的文件 infile,如下所示:

         从打印信息可知,testApp2 进程对 infile 文件加锁失败,原因在于锁已经被 testApp1 进程所持有,所以 testApp2 加锁自然会失败;但是可以发现虽然加锁失败,但是 testApp2 对文件的读写操作是没有问题的,是成功的,这就是建议性锁(非强制)的特点;正确的使用方式是,在加锁失败之后不要再对文件进行 I/O 操作了,遵循这个协议。

        接着我们向 testApp1 进程发送一个 SIGIO 信号,让其对文件 infile 解锁,接着再执行一次 testApp2,如下所示:

         使用 kill 命令向 testApp1 进程发送编号为 2 的信号,也就是 SIGIO 信号,testApp1 接收到信号之后, 对 infile 文件进行解锁、然后退出;接着再次执行 testApp2 程序,从打印信息可知,这次能够成功对 infile 文件加锁了,读写也是没有问题的。

        关于 flock()的几条规则

  • 同一进程对文件多次加锁不会导致死锁。当进程调用 flock()对文件加锁成功,再次调用 flock()对文件(同一文件描述符)加锁,这样不会导致死锁,新加的锁会替换旧的锁。譬如调用 flock()对文件加共享锁,再次调用 flock()对文件加排它锁,最终文件锁会由共享锁替换为排它锁。
  • 文件关闭的时候,会自动解锁。进程调用 flock()对文件加锁,如果在未解锁之前将文件关闭,则会导致文件锁自动解锁,也就是说,文件锁会在相应的文件描述符被关闭之后自动释放。同理,当一个进程终止时,它所建立的锁将全部释放。
  • 一个进程不可以对另一个进程持有的文件锁进行解锁。
  • 由 fork()创建的子进程不会继承父进程所创建的锁。这意味着,若一个进程对文件加锁成功,然后该进程调用 fork()创建了子进程,那么对父进程创建的锁而言,子进程被视为另一个进程,虽然子进程从父进程继承了其文件描述符,但不能继承文件锁。这个约束是有道理的,因为锁的作用就是阻止多个进程同时写同一个文件,如果子进程通过 fork()继承了父进程的锁,则父进程和子进程就可以同时写同一个文件了。

        除此之外,当一个文件描述符被复制时(譬如使用 dup()、dup2()或 fcntl()F_DUPFD操作),这些通过复制得到的文件描述符和源文件描述符都会引用同一个文件锁,使用这些文件描述符中的任何一个进行解锁都可以,如下所示:

flock(fd, LOCK_EX); //加锁
new_fd = dup(fd);
flock(new_fd, LOCK_UN); //解锁

        这段代码先在 fd 上设置一个排它锁,然后使用 dup()对 fd 进行复制得到新文件描述符 new_fd,最后通过 new_fd 来解锁,这样可以解锁成功。但是,如果不显示的调用一个解锁操作,只有当所有文件描述符都被关闭之后锁才会被释放。譬如上面的例子中,如果不调用 flock(new_fd, LOCK_UN)进行解锁,只有当 fd 和 new_fd 都被关闭之后锁才会自动释放。

        关于flock()内容就暂时到这里为止,后面我们将学习使用 fcntl()对文件上锁。

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

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

相关文章

【UE】制作追踪导弹

效果 步骤 1. 首先在虚幻商城下载所需素材 2. 打开“BP_West_Missile_M26” 勾选模拟物理 添加一个变量&#xff0c;命名为“Target” 该变量用来表示导弹追踪的目标&#xff0c;变量类型为actor的对象引用&#xff0c;勾选可编辑实例和生成时公开 在事件图表中添加如下节点 3…

Swin Transformer 论文精读

Swin Transformer 论文精读 https://www.bilibili.com/video/BV13L4y1475U Swin 几乎涵盖了 CV 的下游任务&#xff08;下游任务指骨干网后面的 head 解决的任务&#xff0c;如&#xff1a;分类、检测、语义分割&#xff09;&#xff0c;并且曾经刷新多个数据集的榜单。 题目…

记一次符合Google Coding Style的Bash脚本重构

最近我在思考这样一个问题&#xff0c;顺便看一下gpt对这个问题的解释。搜索发现&#xff1a; 团队写代码&#xff0c;为什么要遵循coding guideline&#xff1f; 一致性&#xff1a;编码准则确保整个团队的代码风格和格式是一致的&#xff0c;这使得团队成员之间更易于交流和…

人工智能(Pytorch)搭建模型6-使用Pytorch搭建卷积神经网络ResNet模型

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(Pytorch)搭建模型6-使用Pytorch搭建卷积神经网络ResNet模型&#xff0c;在本文中&#xff0c;我们将学习如何使用PyTorch搭建卷积神经网络ResNet模型&#xff0c;并在生成的假数据上进行训练和测试。本文将…

Linux---vi/vim编辑器、查阅命令

1. vi\vim编辑器三种模式 vim 是 vi 的加强版本&#xff0c;兼容 vi 的所有指令&#xff0c;不仅能编辑文本&#xff0c;而且还具有 shell 程序编辑的功能&#xff0c; 可以不同颜色的字体来辨别语法的正确性&#xff0c;极大方便了程序的设计和编辑性。 命令模式&#xff08…

整数智能重磅推出集成SAM的智能标注工具2.0

前言 图像语义分割一直是数据标注中最繁琐、最耗时的标注任务之一&#xff0c;利用钢笔工具手动描边的标注方式所带来的时间成本和低准确率都将影响模型的生产速度和模型性能。整数智能ABAVA数据工程平台最新发布了基于SAM&#xff08;Segement Anything Model&#xff09;改进…

【面试题】面试题总结

加油加油 文章目录 1. TCP与UDP的区别2. TCP为什么是四次挥手机制3. HTTP与HTTPS的区别4. HTTPS加密机制5. 简要介绍SSL/TSL协议6. GET与POST的区别7. cookie与session的区别8. JVM内存区域划分9. 程序运行时候内存不足&#xff0c;会出现什么状况10. 显式调用GC会立即执行吗11…

【Python json】零基础也能轻松掌握的学习路线与参考资料

Python中的JSON模块主要用于将Python对象序列化成JSON数据或解析包含JSON数据的字符串。JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成。由于JSON在Web应用中的广泛使用…

SpringCloud Sentinel实战限流熔断降级应用

目录 1 Sentinel核心库1.1 Sentinel介绍1.2 Sentinel核心功能1.2.1 流量控制1.2.2 熔断降级 2 Sentinel 限流熔断降级2.1 SentinelResource定义资源2.2 Sentinel的规则2.2.1 流量控制规则 (FlowRule)2.2.2 熔断降级规则 (DegradeRule)2.2.3 系统保护规则 (SystemRule)2.2.4 访问…

Tomcat配置https协议证书-阿里云,Nginx配置https协议证书-阿里云,Tomcat配置https证书pfx转jks

Tomcat/Nginx配置https协议证书 前言Tomcat配置https协议证书-阿里云方式一 pfx配置证书重启即可 方式二 jkspfx生成jks配置证书重启即可 Nginx配置https协议证书-阿里云实现方式重启即可 其他Tomcat相关配置例子如下nginx配置相关例子如下 前言 阿里云官网&#xff1a;https:…

探索Java面向对象编程的奇妙世界(三)

⭐ 垃圾回收机制(Garbage Collection)⭐ JVM 调优和 Full GC⭐ this 关键字⭐ static 关键字 ⭐ 垃圾回收机制(Garbage Collection) Java 引入了垃圾回收机制&#xff0c;令 C程序员最头疼的内存管理问题迎刃而解。Java 程序员可以将更多的精力放到业务逻辑上而不是内存管理工…

网安面试只要掌握这十点技巧,绝对轻轻松松吊打面试官

结合工作经验&#xff0c;在这里笔者给企业网管员提供一些保障企业网络安全的建议&#xff0c;帮助他们用以抵御网络入侵、恶意软件和垃圾邮件。 定义用户完成相关任务的恰当权限 拥有管理员权限的用户也就拥有执行破坏系统的活动能力&#xff0c;例如&#xff1a; ・偶然对系…

挂耳式耳机品牌排行榜,良心推荐这四款蓝牙耳机

在蓝牙耳机越来越普及的同时&#xff0c;大家开始重视佩戴耳机时的舒适度&#xff0c;市面上的耳机形式也逐步迭代&#xff0c;目前流行的开放式耳机不仅很好的避免长期佩戴耳机产生的酸痛感&#xff0c;而且对耳道健康问题的处理也具有极佳的效果。那么&#xff0c;面对市面上…

VB显示“shell32.dll”中的图标

在Form上添加一个ListBox列表控件 代码如下&#xff1a; Option Explicit Private Declare Function ExtractIconEx Lib “shell32.dll” Alias “ExtractIconExA” (ByVal lpszFile As String, ByVal nIconIndex As Long, phiconLarge As Long, phiconSmall As Long, ByVal nI…

奇偶分频电路

目录 偶数分频 寄存器级联法 计数器法 奇数分频 不满足50%占空比 50%占空比 偶数分频 寄存器级联法 寄存器级联法能实现2^N的偶数分频&#xff0c;具体做法是采用寄存器结构的电路&#xff0c;每当时钟上升沿到来的时候对输出结果进行翻转&#xff0c;以此来实现偶数分…

拥有自我意识的AI:AutoGPT | 得物技术

1.引言 ChatGPT在当下已经风靡一时&#xff0c;作为自然语言处理模型的佼佼者&#xff0c;ChatGPT的优势在于其能够生成流畅、连贯的对话&#xff0c;同时还能够理解上下文并根据上下文进行回答。针对不同的应用场景可以进行快速定制&#xff0c;例如&#xff0c;在客服、教育…

【Unity-UGUI控件全面解析】| TextMeshPro 控件详解

🎬【Unity-UGUI控件全面解析】| TextMeshPro控件详解一、组件介绍二、组件属性面板三、代码操作组件四、组件常用方法示例4.1 Font Asset Creator 面板介绍4.2 制作中文字体库五、组件相关扩展使用5.1 软化/扩张 效果5.2 描边效果5.3 投影效果5.4 光照效果5.5 外发光效果💯…

5.25 费解的开关

思路&#xff0c;枚举 开关按下两次就复原&#xff0c;所以一个开关只有两种情况&#xff0c;按下和不按下&#xff0c;5*5的开关&#xff0c;一共25个开关&#xff0c;一共有2^25种情况&#xff0c;for (int i 0;i < 2^25;i)进行操作&#xff0c;计算按下开关次数&#x…

mac 安装 MongoDB

一.官网下载安装包 1.1 下载安装包 Download MongoDB Community Server | MongoDB 1.2 将下载好的 MongoDB 安装包解压缩&#xff0c;并将文件夹名改为 mongodb&#xff08;可改成自己想要的任何名字&#xff09;。 1.3 按快捷键 Command Shift G 打开前往文件夹弹窗&#…

没有经验能做产品经理吗?

没有经验能做产品经理吗&#xff1f;这是一个经常被讨论的问题&#xff0c;因为很多人想转行成为产品经理&#xff0c;但他们没有相关的工作经验。这里我也给出一些解答。 一、产品经理的职责和技能 首先&#xff0c;让我们看一下产品经理的职责和技能。产品经理是负责产品开…