【Linux高级 I/O(7)】初识文件锁——fcntl()方法及其独占性、共享性实验(附全文代码)

news2024/10/6 14:30:13

        fcntl()函数在前面系列内容中已经多次用到了,它是一个多功能文件描述符管理工具箱,通过配合不同的 cmd 操作命令来实现不同的功能。为了方便述说,这里再重申一次:

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

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

        与锁相关的 cmd 为 F_SETLK、F_SETLKW、F_GETLK,第三个参数 flockptr 是一个 struct flock 结构体指针。使用 fcntl()实现文件锁功能与 flock()有两个比较大的区别:

  • flock()仅支持对整个文件进行加锁/解锁;而 fcntl()可以对文件的某个区域(某部分内容)进行加锁 /解锁,可以精确到某一个字节数据。
  • flock()仅支持建议性锁类型;而 fcntl()可支持建议性锁和强制性锁两种类型。

        我们先来看看 struct flock 结构体,如下所示:

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(set by F_GETLK and F_OFD_GETLK) */
 ...
};

        对 struct flock 结构体说明如下:

  •  l_type:所希望的锁类型,可以设置为 F_RDLCK、F_WRLCK 和 F_UNLCK 三种类型之一,F_RDLCK 表示共享性质的读锁,F_WRLCK 表示独占性质的写锁,F_UNLCK 表示解锁一个区域。
  • l_whence 和 l_start:这两个变量用于指定要加锁或解锁区域的起始字节偏移量,与lseek()函数中的 offset 和 whence 参数相同,这里不再重述。
  •  l_len:需要加锁或解锁区域的字节长度。
  •  l_pid:一个 pid,指向一个进程,表示该进程持有的锁能阻塞当前进程,当 cmd=F_GETLK 时有效。

        以上便是对 struct flock 结构体各成员变量的简单介绍,对于加锁和解锁区域的说明,还需要注意以下几项规则:

  • 锁区域可以在当前文件末尾处开始或者越过末尾处开始,但是不能在文件起始位置之前开始。
  • 若参数 l_len 设置为 0,表示将锁区域扩大到最大范围,也就是说从锁区域的起始位置开始,到文 件的最大偏移量处(也就是文件末尾)都处于锁区域范围内。而且是动态的,这意味着不管向该文件追加写了多少数据,它们都处于锁区域范围,起始位置可以是文件的任意位置。
  • 如果我们需要对整个文件加锁,可以将 l_whence 和 l_start 设置为指向文件的起始位置,并且指定参数 l_len 等于 0。

        两种类型的锁:F_RDLCK 和 F_WRLCK

        上面我们提到了两种类型的锁,分别为共享性读锁(F_RDLCK)和独占性写锁(F_WRLCK)。基本的规则与 Linux线程同步(6)——更高并行性的读写锁中所介绍的线程同步读写锁很相似,任意多个进程在一个给定的字节上可以有一把共享的读 锁,但是在一个给定的字节上只能有一个进程有一把独占写锁,进一步而言,如果在一个给定的字节上已经 有一把或多把读锁,则不能在该字节上加写锁;如果在一个字节上已经有一把独占性写锁,则不能再对它加任何锁(包括读锁和写锁),下图显示了这些兼容性规则:

         如果一个进程对文件的某个区域已经上了一把锁,后来该进程又试图在该区域再加一把锁,那么通常新加的锁将替换旧的锁。譬如,若某一进程在文件的 100~200 字节区间有一把写锁,然后又试图在 100~200 字 节区间再加一把读锁,那么该请求将会成功执行,原来的写锁会替换为读锁。 还需要注意另外一个问题,当对文件的某一区域加读锁时,调用进程必须对该文件有读权限,譬如 open() 时 flags 参数指定了 O_RDONLY 或 O_RDWR;当对文件的某一区域加写锁时,调用进程必须对该文件有写 权限,譬如 open()时 flags 参数指定了 O_WRONLY 或 O_RDWR。         F_SETLK、F_SETLKW 和 F_GETLK

        我们来看看与文件锁相关的三个 cmd 它们的作用:

⚫ F_GETLK:这种用法一般用于测试,测试调用进程对文件加一把由参数 flockptr 指向的 struct flock 对象所描述的锁是否会加锁成功。如果加锁不成功,意味着该文件的这部分区域已经存在一把锁, 并且由另一进程所持有,并且调用进程加的锁与现有锁之间存在排斥关系,现有锁会阻止调用进程想要加的锁,并且现有锁的信息将会重写参数 flockptr 指向的对象信息。如果不存在这种情况,也就是说 flockptr 指向的 struct flock 对象所描述的锁会加锁成功,则除了将 struct flock 对象的 l_type 修改为 F_UNLCK 之外,结构体中的其它信息保持不变。

⚫ F_SETLK:对文件添加由 flockptr 指向的 struct flock 对象所描述的锁。譬如试图对文件的某一区域加读锁(l_type 等于 F_RDLCK)或写锁(l_type 等于 F_WRLCK),如果加锁失败,那么 fcntl() 将立即出错返回,此时将 errno 设置为 EACCES 或 EAGAIN。也可用于清除由 flockptr 指向的 struct flock 对象所描述的锁(l_type 等于 F_UNLCK)。

⚫ F_SETLKW:此命令是F_SETLK 的阻塞版本(命令名中的 W 表示等待 wait),如果所请求的读锁或写锁因另一个进程当前已经对所请求区域的某部分进行了加锁,而导致请求失败,那么调用进程将会进入阻塞状态。只有当请求的锁可用时,进程才会被唤醒。

        F_GETLK 命令一般很少用,事先用 F_GETLK 命令测试是否能够对文件加锁,然后再用 F_SETLK或F_SETLKW 命令对文件加锁,但这两者并不是原子操作,所以即使测试结果表明可以加锁成功,但是在使 用 F_SETLK 或 F_SETLKW 命令对文件加锁之前也有可能被其它进程锁住。

        使用示例与测试

        示例代码演示了使用 fcntl()对文件加锁和解锁的操作。需要加锁的文件通过外部传参传入,先 调用 open()函数以只写方式打开文件;接着对 struct flock 类型对象 lock 进行填充,l_type 设置为 F_WRLCK 表示加一个写锁,通过 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>
#include <string.h>

int main(int argc, char *argv[])
{
    struct flock lock = {0};
    int fd = -1;
    char buf[] = "Hello World!";
    /* 校验传参 */
    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);
    }
    /* 对文件加锁 */
    lock.l_type = F_WRLCK; //独占性写锁
    lock.l_whence = SEEK_SET; //文件头部
    lock.l_start = 0; //偏移量为 0
    lock.l_len = 0;
    if (-1 == fcntl(fd, F_SETLK, &lock)) {
        perror("加锁失败");
        exit(-1);
    }
    printf("对文件加锁成功!\n");

    /* 对文件进行写操作 */
    if (0 > write(fd, buf, strlen(buf))) {
        perror("write error");
        exit(-1);
    }
    /* 解锁 */
    lock.l_type = F_UNLCK; //解锁
    fcntl(fd, F_SETLK, &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 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");
    /* 对文件进行 I/O 操作 */
    // ......
    // ......
    /* 解锁 */
    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 字节区间和 150~250 字节区间,150~200 就是它们的重叠部分,一个进程对同一文件的相同区域不可能同时加两把锁,新加的锁会把旧的锁替换掉,譬如先对 100~200 字节区间加写锁、再对 150~250 字节区间加读锁,那么 150~200 字节区间最终是读锁控制的,关于这个问题,大家可以自己去验证、测试。         

        接下来对读锁和写锁彼此之间的兼容性进行测试,使用下示例代码测试读锁的共享性。

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

        首先运行上述示例代码,程序加读锁之后会进入死循环,进程一直在运行着、持有读锁。接着多次运行上述示例代码,启动多个进程加读锁,测试结果如下所示:

        从打印信息可以发现,多个进程对同一文件的相同区域都可以加读锁,说明读锁是共享性的。由于程序是放置在后台运行的,测试完毕之后,可以使用 kill 命令将这些进程杀死,或者直接关闭当前终端,重新启动新的终端。 

        几条规则

        关于使用 fcntl()创建锁的几条规则与 flock()相似,如下所示:

        ⚫ 文件关闭的时候,会自动解锁。

        ⚫ 一个进程不可以对另一个进程持有的文件锁进行解锁。

        ⚫ 由 fork()创建的子进程不会继承父进程所创建的锁

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

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);//解锁

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

Linux高级 I/O总结

        本系列向大家介绍了几种高级 I/O 功能,非阻塞 I/O、I/O 多路复用、异步 I/O、存储映射 I/O、以及文件锁,其中有许多的功能,我们将会在后面的提高篇和进阶篇章节实例中使用到。

  • 非阻塞 I/O:进程向文件发起 I/O 操作,使其不会被阻塞。
  • I/O 多路复用:select()和 poll()函数。
  • 异步 I/O:当文件描述符上可以执行 I/O 操作时,内核会向进程发送信号通知它。
  • 存储映射 I/O:mmap()函数。
  • 文件锁:flock()、fcntl()以及 lockf()函数。

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

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

相关文章

大模型对世界的改变,从一时一地,到无处不在、无时不有

作者 | 曾响铃 文 | 响铃说 大模型正在中国遍地开花&#xff0c;做过的没做过的都要过来参合一下。 汹涌浪潮中&#xff0c;不免有更多人开始关注那个最先发布的文心一言。 全球科技大厂中第一个发布GPT大模型产品的百度&#xff0c;在刚刚的中关村论坛上透露了一些文心一言…

nodejs连接mysql

npm i express #node后端框架npm i corsnpm i mysqlconst app require(express)(); const cors require(cors); const port 5000; const mysql require(mysql) //引入mysql 模块app.use(cors({}))const conn mysql.createConnection({user: root,password: qwertyuiop…

普通人想自学软件测试?我还是劝你算了吧。。。

本人7年测试经验&#xff0c;在学测试之前对电脑的认知也就只限于上个网&#xff0c;玩个办公软件。这里不能跑题&#xff0c;我为啥说&#xff1a;自学软件测试&#xff0c;一般人我还是劝你算了吧&#xff1f;因为我就是那个一般人&#xff01; 软件测试基础真的很简单&…

gtest单元测试

gtest单元测试 1. gtest是什么&#xff1f;简答&#xff1a;做测试用的2. gtest的优点3. 搭建测试框架4. gtest_范例演示 1. gtest是什么&#xff1f;简答&#xff1a;做测试用的 gtest是Google的一套用于编写C测试的框架&#xff0c;可以运行在很多平台上&#xff08;包括Lin…

【JavaSE】Java基础语法(十四):Static

文章目录 概述特点与应用注意事项为什么一个静态方法中只能访问用static修饰的成员? 概述 Java中的static是一个修饰符&#xff08;也可称关键字&#xff09;&#xff0c;可以用于修饰变量、方法和代码块。 特点与应用 static修饰的成员具有以下特点&#xff1a; 被类的所有对…

如何在Mac上抓取安卓设备的日志

要在 Mac 上抓取 Android 设备的日志&#xff0c;您可以使用 Android SDK 中的 adb 工具。以下是一个简单的步骤&#xff1a; 1.您需要在 Mac 上安装 Android SDK。您可以从 Android 开发者网站上下载最新版本的 Android SDK&#xff0c;并按照说明进行安装。 2.将您的 Andro…

重学 Symbol

重学 Symbol 之前在写基础类型的笔记时暂时性的先跳过了 symbol&#xff0c;现在也有了一些项目的使用经验后&#xff0c;觉得还是需要重新回滚并且学习一下&#xff0c;温故而知新。 首先依旧回顾一下 symbol 的特点&#xff1a; 是原始值 唯一 不可变 可以提供私有属性&…

javaWeb ssh沙发销售系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh沙发销售系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Mye…

【SUMO】SUMO运行自带的OSM入门教程

文章目录 一、运行CMD命令行二、进入OSM选择地图位置 首先给出官网教程&#xff1a; https://sumo.dlr.de/docs/Tutorials/OSMWebWizard.html 一、运行CMD命令行 代码&#xff1a; 先进入osmWebWizard.py文件地址 cd /d D:\SUMO\sumo-1.17.0\tools&#xff08;替换成自己的…

智慧PG(pgting),一款拖拽式智能页面搭建系统

目录 前言 一、介绍 二、设计理念 1&#xff0c;资源整合&#xff0c;开箱即用 2&#xff0c;降低系统颗粒度 3&#xff0c;组件共享 4&#xff0c;简化配置 三、系统功能 1&#xff0c;可视化大屏搭建&#xff1a; 四、技术架构 1&#xff0c;技术栈 2&#xff0c;整体架构 五…

LeetCode 1091. Shortest Path in Binary Matrix【BFS,A星】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

C++ 代码整洁之道

NOTICE: 这篇文章的框架条目来自《C代码整洁之道&#xff1a;C17可持续软件开发模式实践》&#xff0c;作者: [德] 斯提芬罗特。书籍原名"Clean C: Sustainable Software Development Patterns and Best Practices with C 17"。 文章目录 编码基本原则保持简单和直接…

Unity | HDRP高清渲染管线学习笔记:示例场景解析

目录 一、HDRP入门 1.HDRP设置 1.1 HDRP配置文件中的全部设置项 1.1.1 Rendering下的Lit Shader Mode 1.1.2 Lighting 下的Volumetrics&#xff08;体积光&#xff09;和Screen Space Reflection&#xff08;屏幕空间反射&#xff09; 2.离线渲染VS实时渲染 3.Volume组件 …

文字gif闪图怎么做?高效的gif闪图制作方法

相信不少新媒体行业的小伙伴&#xff0c;一定都见过那种闪动文字效果的gif动图吧。效果非常的炫酷还很吸引人们的眼球&#xff0c;但是作为设计小白这种闪烁gif图要怎么制作呢&#xff1f;有没有那种小白也能轻松上手的工具呢&#xff1f; 一、什么样的工具能够在线生成gif动态…

《Spring Guides系列学习》guide35 - guide40

要想全面快速学习Spring的内容&#xff0c;最好的方法肯定是先去Spring官网去查阅文档&#xff0c;在Spring官网中找到了适合新手了解的官网Guides&#xff0c;一共68篇&#xff0c;打算全部过一遍&#xff0c;能尽量全面的了解Spring框架的每个特性和功能。 接着上篇看过的gu…

项目管理自动化 工作效率顶呱呱

项目管理&#xff0c;是职场人进阶发展的必备高阶能力&#xff0c;需要在复杂的环境中有效整合资源、高效助力团队实现整体的项目目标。 一个好的项目管理者&#xff0c;需要合理规划项目进展&#xff0c;实时同步需求、及时沟通进展&#xff0c;合理判断项目风险&预警&am…

记一次用户反馈app在后台收不到push问题跟踪

我们的应该大范围推广后&#xff0c;今日用户群好多用户反馈安卓手机app在后台时收不到app的push消息&#xff0c;只有app处于前台时才能收到push消息。但是ios手机可以正常接收push消息。 拿到问题&#xff0c;首先想到从下面几个方便尝试定位&#xff1a; 1.用户手机app通知权…

财报解读:毛利持续改善,金山云正在“弯道超车”?

一季度&#xff0c;云巨头们的表现持续稳健&#xff0c;依旧稳坐前排&#xff0c;而作为中小云代表的金山云也在5月23日发布了2023年一季度财报&#xff0c;盈利能力持续改善成为通篇最亮眼的一笔。 随着AI大模型打开了新的“潘多拉魔盒”&#xff0c;云市场也在发生着巨变。 …

picoctf_2018_rop chain

小白垃圾笔记&#xff0c;不建议阅读。 这道题目其实我是瞎做的. 本地调试需要写一个文件名为flag.txt的文件。 先检查下保护&#xff1a;&#xff08;我把文件名改成pwn了&#xff09;&#xff0c;32位仅仅开启了nx 然后放到32位ida里&#xff1a; main函数如下&#xff1a…

〖Web全栈开发⑤〗— CSS基础

〖Web全栈开发⑤〗— CSS基础 (一)CSS基础1.1CSS介绍1.2CSS样式1.3CSS 格式 &#xff08;二&#xff09;CSS 选择器2.1标签选择器2.2类选择器2.3层级选择器2.4id选择器2.5组选择器2.6伪类选择器2.7通配符选择器 &#xff08;三&#xff09;样式表引入3.1外部样式表3.2内部样式表…