Linux文件锁的使用

news2024/12/22 20:29:02

        文件是一种共享资源,多个进程对同一文件进行操作的时候,必然涉及到竞争状态,因此引入了文件锁实现对共享资源的访问进行保护的机制,通过对文件上锁, 来避免访问共享资源产生竞争
状态。

一、文件锁的分类

        1.建议性锁

        建议性锁本质上是一种协议,程序访问文件之前,先对文件上锁, 上锁成功之后再访问文件,这是建议性锁的一种用法;但是如果你的程序不管三七二十一,在没有对文件上锁的情况下直接访问文件,也是可以访问的,并非无法访问文件。

        2.强制性锁

        如果进程对文件上了强制性锁,其它的进程在没有获取到文件锁的情况下是无法对文件进行访问的。

Linux 系统中,可以调用 flock()、 fcntl()函数对文件上锁

二、flock()函数进行上锁

      函数原型:flock()函数只能产生建议性锁

#include <sys/file.h>
int flock(int fd, int operation);

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,

使用实例:

        代码1

#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;  //文件描述符

/* 信号处理函数 */
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(argc != 2) {
        fprintf(stderr, "Usage: error!\n");
        exit(1);
    }

    fd = open(argv[1], O_WRONLY);   /*只写的方式打开*/
    if(fd < 0) {
        perror("open()");
        exit(1);
    }

    if(flock(fd, LOCK_EX | LOCK_NB) == -1) {    //以非阻塞的方式加锁
        perror("pid 1 lock failed");
        exit(1);
    }

    printf("pid 1 lock successed!\n");

    signal(SIGINT, sigint_handler);

    for(;;)
        sleep(1);

    return 0;

}

①首先利用open函数获取文件描述符fd
②对文件描述符使用非阻塞的方式加一个互斥锁
③注册SIGINT信号处理函数:信号处理函数实现对文件进行解锁
④死循环进行睡眠

代码2:

#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);
}

①首先利用open函数获取文件描述符fd
②对文件描述符使用非阻塞的方式加一个互斥锁
③不管加锁成功还是失败都将buf总的内容写入该文件
④最后从文件中读取写入的内容,并进行打印

运行效果:

运行代码1

运行代码2

从打印信息可以看出,代码2对文件加锁失败了,是因为锁已经被代码1持有,尽管加锁失败,但是代码2对文件的读写仍然是成功的,这就是建议性锁

对代码1发送SIGINT信号,会触发信号处理函数对文件进行解锁,再次运行代码2可以成功对文件进行加锁。 

关于 flock()规则

①同一进程对文件多次加锁不会导致死锁。
②文件关闭的时候,会自动解锁。
③一个进程不可以对另一个进程持有的文件锁进行解锁。
④由 fork()创建的子进程不会继承父进程所创建的锁

三、fcntl()函数进行上锁

        fcntl函数是一个多功能文件描述符管理工具箱,通过配合不同的 cmd 操作命令来实现不同的功能。

#include <unistd.h>
#include <fcntl.h>


int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );

fd:要加锁的文件描述符

cmd: F_SETLK、 F_SETLKW、 F_GETLK
第三个参数 flockptr 是一个 struct flock 结构体指针 

struct flock
  {
    short int l_type;	/* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK.	*/
    short int l_whence;	/* Where `l_start' is relative to (like `lseek').  */
#ifndef __USE_FILE_OFFSET64
    __off_t l_start;	/* Offset where the lock begins.  */
    __off_t l_len;	/* Size of the locked area; zero means until EOF.  */
#else
    __off64_t l_start;	/* Offset where the lock begins.  */
    __off64_t l_len;	/* Size of the locked area; zero means until EOF.  */
#endif
    __pid_t l_pid;	/* Process holding the lock.  */
  };

l_type: 所希望的锁类型,可以设置为 F_RDLCK、F_WRLCK 和 F_UNLCK 三种类型之一, F_RDLCK
表示共享读锁, F_WRLCK 表示独占写锁, F_UNLCK 表示解锁一个区域。
    l_whence 和 l_start: 这两个变量用于指定要加锁或解锁区域的起始字节偏移量。
    l_len: 需要加锁或解锁区域的字节长度。
    l_pid: 一个 pid,指向一个进程,表示该进程持有的锁能阻塞当前进程,当 cmd=F_GETLK 时有效。
以上便是对 struct flock 结构体各成员变量的简单介绍,对于加锁和解锁区域的说明,还需要注意以下
几项规则:
    锁区域可以在当前文件末尾处开始或者越过末尾处开始,但是不能在文件起始位置之前开始。
    若参数 l_len 设置为 0,表示将锁区域扩大到最大范围,也就是说从锁区域的起始位置开始, 到文
件的最大偏移量处(也就是文件末尾)都处于锁区域范围内。而且是动态的, 这意味着不管向该文
件追加写了多少数据,它们都处于锁区域范围,起始位置可以是文件的任意位置。
    如果我们需要对整个文件加锁,可以将 l_whence 和 l_start 设置为指向文件的起始位置, 并且指定
参数 l_len 等于 0。

测试代码: 

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


int main(int argc, char *argv[])
{
    struct flock wr_lock = {0};
    struct flock rd_lock = {0};
    int fd = -1;
    /* 校验传参 */
    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);
    }
    /* 将文件大小截断为 1024 字节 */
    ftruncate(fd, 1024);
    /* 对 100~200 字节区间加写锁 */
    wr_lock.l_type = F_WRLCK;
    wr_lock.l_whence = SEEK_SET;
    wr_lock.l_start = 100;
    wr_lock.l_len = 100;
    if (-1 == fcntl(fd, F_SETLK, &wr_lock)) {
        perror("加写锁失败");
        exit(-1);
    }
    printf("加写锁成功!\n");
    /* 对 400~500 字节区间加读锁 */
    rd_lock.l_type = F_RDLCK;
    rd_lock.l_whence = SEEK_SET;
    rd_lock.l_start = 400;
    rd_lock.l_len = 100;
    if (-1 == fcntl(fd, F_SETLK, &rd_lock)) {
        perror("加读锁失败");
        exit(-1);
    }
    printf("加读锁成功!\n");

    /* 解锁 */
    wr_lock.l_type = F_UNLCK; //写锁解锁
    fcntl(fd, F_SETLK, &wr_lock);
    rd_lock.l_type = F_UNLCK; //读锁解锁
    fcntl(fd, F_SETLK, &rd_lock);
    /* 退出 */
    close(fd);
    exit(0);
}

一个进程可以对同一个文件的不同区域进行加锁,该程序对文件的 100~200 字节区间加了一个写锁,对文件的 400~500 字节区间加了一个读锁。

读锁的共享性测试: 

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


int main(int argc, char *argv[])
{
    struct flock lock = {0};
    int fd = -1;
    /* 校验传参 */
    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);
    }
    /* 将文件大小截断为 1024 字节 */
    ftruncate(fd, 1024);
    /* 对 400~500 字节区间加读锁 */
    lock.l_type = F_RDLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 400;
    lock.l_len = 100;
    if (-1 == fcntl(fd, F_SETLK, &lock)) {
        perror("加读锁失败");
        exit(-1);
    }
    printf("加读锁成功!\n");
    for ( ; ; )
    sleep(1);
}

从打印信息可以发现,多个进程对同一文件的相同区域都可以加读锁,说明读锁是共享性的。

写锁的独占性测试:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    struct flock lock = {0};
    int fd = -1;
    /* 校验传参 */
    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);
    }
    /* 将文件大小截断为 1024 字节 */
    ftruncate(fd, 1024);
    /* 对 400~500 字节区间加写锁 */
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 400;
    lock.l_len = 100;
    if (-1 == fcntl(fd, F_SETLK, &lock)) {
        perror("加写锁失败");
        exit(-1);
    }
    printf("加写锁成功!\n");
    for ( ; ; )
    sleep(1);
}

        由打印信息可知,但第一次启动的进程对文件加写锁之后,后面再启动进程对同一文件的相同区域加写锁发现都会失败,所以由此可知,写锁是独占性的。

关于fcntl函数的规则:
        ①文件关闭的时候,会自动解锁。
        ②一个进程不可以对另一个进程持有的文件锁进行解锁。
        ③由 fork()创建的子进程不会继承父进程所创建的锁

注意:

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

例如:

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



lock.l_type = F_RDLCK;
fcntl(fd, F_SETLK, &lock);//加锁
new_fd = dup(fd);
lock.l_type = F_UNLCK;
fcntl(new_fd, F_SETLK, &lock);//解锁

对于flock函数上的锁,如果不进行主动解锁的话,只有文件描述符fd和new_fd都关闭才会自动解锁。而fcntl函数上的锁,fd和new_fd任意一个关闭都会自动解锁。

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

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

相关文章

Java并发编程——Threadlocal源码解析

Threadlocal源码解析一、基本结构二、ThreadLocal操作set操作get操作remove操作三、内存泄露&#xff1f;四、ThreadLocalMap核心变量数组下标计算方式阈值计算扩容下标冲突&#xff08;hash冲突&#xff09;从名称上来看可以理解为线程本地变量&#xff0c;也可以认为是线程局…

(JAVA)认识Java中的数据类型和变量

文章目录前言1.字面常量2. 数据类型3.变量3.1 变量概念3.2 语法格式3.3 整形变量3.4 浮点型变量3.5 字符型变量3.6布尔类型变量3.7 类型转换3.7.1 隐式转换&#xff08;自动类型转换&#xff09;3.7.2 显示转换 &#xff08;强制类型转换&#xff09;3.8 类型提升4. 字符串类型…

驱动开发:内核层InlineHook挂钩函数

在上一章《驱动开发&#xff1a;内核LDE64引擎计算汇编长度》中&#xff0c;LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度&#xff0c;本章将在此基础之上实现内联函数挂钩&#xff0c;内核中的InlineHook函数挂钩其实与应用层一致&#xff0c;都是使用劫持执行流并跳…

三类基于贪心思想的区间覆盖问题【配套资源详解】

博主主页&#xff1a;Yu仙笙 配套资源&#xff1a;三类基于贪心算法覆盖问题-C文档类资源-CSDN下载 目录 三类基于贪心思想的区间覆盖问题 情形1&#xff1a;区间完全覆盖问题 描述&#xff1a; 样例&#xff1a; 解题过程: 例题&#xff1a; 题意&#xff1a; 例题&#xff1a…

深入理解Kafka服务端之索引文件及mmap内存映射

深入理解Kafka服务端之索引文件及mmap内存映射 - 墨天轮 一、场景分析 Kafka在滚动生成新日志段的时候&#xff0c;除了生成日志文件(.log)&#xff0c;会同时生成一个偏移量索引文件(.index)、一个时间戳索引文件(.timeindex)和一个已中止事务索引文件(.txnindex)。 由于索引写…

JVM面试高频问题

一、进程与线程 在谈JVM的这些问题前&#xff0c;我们先来复习一下有关线程和进程的关系 进程可以看作是程序的执行过程。一个程序的运行需要CPU时间、内存空间、文件以及I/O等资源。操作系统就是以进程为单位来分配这些资源的&#xff0c;所以说进程是分配资源的基本单位。线…

C语言函数章--该如何学习函数?阿斗看了都说会学习了

前言 &#x1f47b;作者&#xff1a;龟龟不断向前 &#x1f47b;简介&#xff1a;宁愿做一只不停跑的慢乌龟&#xff0c;也不想当一只三分钟热度的兔子。 &#x1f47b;专栏&#xff1a;C初阶知识点 &#x1f47b;工具分享&#xff1a; 刷题&#xff1a; 牛客网 leetcode笔记软…

【Python入门指北】 发邮件与正则表达式

文章目录邮件发送一、群发邮件二、指定用户发邮件正则表达式一、预备知识正则1. 正则介绍2. 陷阱3. 特殊的字符二、 re 模块的方法1 常用方法2. 正则分组总结邮件发送 #第三方模块 yagmail #pip3 install yagmailimport yagmail""" 项目需求 yag yagmail.SMTP(u…

MyBatis Plus实现动态字段排序

利用周末时间&#xff0c;对已有的项目进行了升级&#xff0c;原来使用的是tkmybatis&#xff0c;改为mybatis plus。但是由于修改了返回数据的格式&#xff0c;前端页面字段排序失效了&#xff0c;需要刷新表格才会排序。页面效果如下 easyui的数据表格datagrid支持多字段排序…

【仿牛客网笔记】Spring Boot实践,开发社区登录模块-账号设置,检查登录

首先访问账号设置的页面。 新建一个Controller,用过RequestMapping生成访问路径 上传头像 首先打开配置文件&#xff0c;配置一下将文件配置到哪里。 直接在Controller存了&#xff0c; 更新的时候掉Map&#xff0c;参数为id和路径。 注入日志对象后&#xff0c;通过Val…

SpringBoot项目启动执行任务的几种方式

经过整理后得到以下几种常用方式&#xff0c;供大家参考。 1. 使用过滤器 init() &#xff1a;该方法在tomcat容器启动初始化过滤器时被调用&#xff0c;它在 Filter 的整个生命周期只会被调用一次。可以在这个方法中补充想要执行的内容。 Component public class MyFilter …

CTF竞赛网络安全大赛(网鼎杯 )Web|sql注入java反序列化

CTF竞赛网络安全大赛题目考点 sql注入 java反序列化 网鼎杯解题思路 题目一打开是这样的界面 下载题目的附件,并用jd-gui.exe打开 核心代码如下 Test代码 `` package 部分class;import cn.abc.common.bean.ResponseCode; import cn.abc.common.bean.ResponseResult; impor…

持续交付中流水线构建完成后就大功告成了吗?别忘了质量保障

上期文章我结合自己的实践经验&#xff0c;介绍了持续交付中流水线模式的软件构建&#xff0c;以及在构建过程中的3个关键问题。我们可以看出&#xff0c;流水线的软件构建过程相对精简、独立&#xff0c;只做编译 和打包两个动作。 但需要明确的是&#xff0c;在持续交付过程…

网课查题接口使用方法

网课查题接口使用方法 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点…

Hadoop面试题汇总-20221031

Hadoop面试题汇总 HDFS部分 1、请描述HDFS的写流程。 答&#xff1a; 首先由客户端向 NameNode 发起文件上传请求&#xff0c;NameNode 检查文件要上传的目录&#xff0c;并鉴权。如果上传用户对此目录有权限&#xff0c;则允许客户端进行上传操作。客户端接收到允许指令后&…

本科毕业论文内容必须有国内外文献综述吗?

不知不觉间整个暑假变过去了&#xff0c;现在大部分的大学生都已经开学了。2023届毕业的学生现在也开始借鉴毕业论文的选题工作。但是无论是现在正在选题的大四的同学们还是还在上大一大&#xff0c;二大三的同学们都对毕业论文这4个字有着天生的恐惧感。因为对于大多数人来说&…

阿里为何禁止在对象中使用基本数据类型

大家好&#xff0c;我是一航&#xff01; 前两天&#xff0c;因为一个接口的参数问题&#xff0c;和一位前端工程师产生了一些分歧&#xff0c;需求很简单&#xff1a; 根据一个数值类型&#xff08;type 取值范围1&#xff0c;2&#xff0c;3&#xff09;来查询数据&#xff…

HTML+CSS+JavaScript七夕情人节表白网页【樱花雨3D相册】超好看

这是程序员表白系列中的100款网站表白之一&#xff0c;旨在让任何人都能使用并创建自己的表白网站给心爱的人看。 此波共有100个表白网站&#xff0c;可以任意修改和使用&#xff0c;很多人会希望向心爱的男孩女孩告白&#xff0c;生性腼腆的人即使那个TA站在眼前都不敢向前表白…

pandas 基本数据

目录 1. pandas 简介 2. pandas 基本数据结构 2.1 Series 类型 2.1.1 索引-数据的行标签 2.1.2 值 2.1.3 切片 2.1.4 索引赋值 2.2 DataFrame 类型 1. pandas 简介 一般导入的形式&#xff1a;import pandas as pd 2. pandas 基本数据结构 python 的数据结构&#xff1a…

python爬虫之Scrapy框架,基本介绍使用以及用框架下载图片案例

一、Scrapy框架简介 Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据&#xff0c;只需要实现少量的代码&#xff0c;就能够快速的抓取。 Scrapy使用了Twisted异步网络框架来处理网络通信&#xf…