linux文件I/O之 fcntl() 函数用法:设置文件的 flags、设置文件锁(记录锁)
头文件和函数声明
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
函数功能
获取、设置已打开文件的属性
返回值
成功时返回根据 cmd 传递的命令类型的执行结,失败时返回 -1,并设置 errno 为相对应的错误标志。
参数
fd:文件描述符
cmd:需要操作的命令类型(例如:F_GETFL、F_SETFL 等)
arg:表示要传递的参数,具体的含义和 cmd 传递的命令类型有关
1. 用于获取、设置文件的 flags
- cmd = F_GETFL 时,获取打开文件的 flags(即调用 open() 函数打开文件,传递的 flags 参数)。
- cmd = F_SETFL 时,设置打开文件的 flags。
在这种情况下,fcntl 函数的原型如下所示:
int fcntl(int fd, int cmd); // 在获取打开文件的 flags 时
int fcntl(int fd, int cmd, int flags); // 在设置打开文件的 flags 时
例子:对于一个 socket,创建时默认是阻塞I/O,将它设置为非阻塞(O_NONBLOCK),又设置回阻塞状态。
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>void err_quit(const char *msg) {perror(msg);exit(EXIT_FAILURE);
}
void close_fd(const int fd, char is_exit) { // is_exit: 0 (false),1(true)if (-1 == close(fd)) {perror("close error");if (is_exit)exit(EXIT_FAILURE);}
}int main(int argc, char *argv[]) {// 创建socketint fd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == fd)err_quit("socket error");printf("socket success, fd = %d\n", fd);int flags = fcntl(fd, F_GETFL);if (-1 == flags) {close_fd(fd, 0);err_quit("fcntl error");}printf("before set O_NONBLOCK, flags = %d\n", flags);flags |= O_NONBLOCK;if (-1 == fcntl(fd, F_SETFL, flags)) {close_fd(fd, 0);err_quit("fcntl error");}printf("after set O_NONBLOCK, flags = %d\n", flags);// 设置回阻塞状态flags &= ~O_NONBLOCK;if (-1 == fcntl(fd, F_SETFL, flags)) {close_fd(fd, 0);err_quit("fcntl error");}printf("after set ~O_NONBLOCK, flags = %d\n", flags);close_fd(fd, 1);return 0;
}

2. 设置文件锁
在 linux 操作系统中,当多个进程同时操作同一个文件时,该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序,如数据库,进程有时需要确保它在一个时刻只能被一个进程/线程写,这时候就要用到文件锁。文件锁也被称为记录锁(record lock)
文件锁的作用:当一个进程正在读写文件的某一区域时,其他进程就不能对文件的这个区域进行修改操作。
提供文件锁操作的函数有两个:flock() 和 fcntl(),其中,flock() 函数是对文件锁操作的早期版本,它只能对整个文件加锁,不能对文件中的某一个区域加锁。fcntl() 函数是在 flock() 函数的基础上构造出来的,它允许对文件中任意字节区域加锁,短至一个字节,长至整个文件。
在这种情况下,fcntl 函数的原型如下所示:
int fcntl(int fd, int cmd, struct flock *lock);
flock 结构体定义如下:
struct flock {short l_type;short l_whence;off_t l_start;off_t l_len;pid_t l_pid;
};
- l_type:锁类型,F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或 F_UNLCK(解锁)
- l_pid:持有锁的进程ID。(仅由于 cmd = F_GETLK 时返回)
- l_len:区域字节长度。
-
l_start:参数 l_start 的含义跟参数 l_whence 的值有关
l_whence = SEEK_SET 时,则将文件的偏移量设置为文件开始处 l_start 个字节,参数 l_start 必须为非负数。
l_whence = SEEK_CUR 时,则将文件的偏移量设置为 当前文件的偏移量 + l_start 个字节,参数 l_start 可以为正数,也可以为负数,只要最终得到的文件偏移量不会小于文件的起始位置(字节0)即可。
l_whence = SEEK_END 时,则将文件的偏移量设置为 文件长度 + l_start 个字节,参数 l_start 可以为正数,也可以为负数,只要最终得到的文件偏移量不会小于文件的起始位置(字节0)即可。 -
l_start、l_whence 和 l_len 这三个参数一起指定了待加锁的字节范围。如果 l_len = 0,则表示锁的范围会被扩展到最大的可能偏移量。这意味着对从由 l_start 和 l_whence 确定的起始位置开始,不管向该文件中追加写了多少数据,它们都处于锁的范围内。
-
为了对整个文件加锁,可以设置 l_start = 0, l_whence = SEEK_SET, l_len = 0;
-
锁可以在当前文件尾端处开始或越过文件尾端处开始,但不能在文件起始位置之前开始。
-
一般来说,应用程序应该只对所需的最小字节范围进行加锁,这样其他进程就能够同时对同一个文件的不同区域进行加锁,进而取得更大的并发性。
(1)cmd = F_GETLK 时,检测能否获取 lock 指针指定的文件区域的锁(lock指针指向的结构中的 l_type 字段的值必须是 F_RDLCK 或 F_WRLCK)。如果允许加锁(即在指定的文件区域上不存在不兼容的锁),那么 l_type 字段会返回 F_UNLCK,剩余的字段保持不变。如果在指定的文件区域上存在不兼容的锁(即不能加锁),那么 lock 会返回不兼容的锁的所有相关信息(如果有多把不兼容的锁的话,会返回其中一把不兼容的锁的所有相关信息,但不确定会返回哪一把) 。
(2)cmd = F_SETLK 时,给 fd 引用的文件加锁(lock 指针指定的文件区域的锁)。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁,fcntl() 就会失败并返回 -1,并设置 errno 为 EAGAIN,有的系统也可能设置 errno 为 EACCES。
(3)cmd = F_SETLKW 时,这个命令参数是 F_SETLK 命令参数的阻塞版本(命令名中的 W 表示等待 wait)。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁,那么调用进程会设置为休眠。如果请求创建的锁已经可用,或者休眠由信号中断,该进程将会被唤醒。
需要注意的是,用 F_GETLK 测试能否建立一把锁,然后用 F_SETLK 或 F_SETLKW 企图建立那把锁,这两者不是一个原子操作,因此不能保证在这两次 fcntl() 调用之间会不会有另一个进程插入并建立了一把相同的锁。如果不希望在等待锁变成可用时产生阻塞,就必须处理 F_SETLK 返回的可能出错。
例子
以下程序功能:用 fcntl() 函数对一个对文件进行读写时,先加锁,为了测试需要,读写完后不解锁,需要输入 u 命令进行解锁。
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/stat.h>#define BUF_SIZE 4096/*
is_errno: 是否要打印系统的出错信息,0:否 1:是
is_exit: 打印出错信息后,是否立马终止进程,0:否 1:是
*/
void err_msg(const char is_errno, const char is_exit, const char *format, ...);
void close_fd(const int fd, char is_exit);
void cmd_r(const int fd, char* const ibuf, const char *filename);
void cmd_w(const int fd, char* const ibuf, const char *filename);int main(int argc, char *argv[])
{if (argc != 2)err_msg(0, 1, "%s filename", argv[0]);const char *filename = argv[1];int fd = open(argv[1], O_RDWR | O_APPEND);if (fd == -1)err_msg(1, 1, "open error");printf("open %s success\n", filename);char ibuf[BUF_SIZE] = ""; // stdin bufwhile (1) {printf("please input r or w or quit\n", filename);fgets(ibuf, BUF_SIZE, stdin); // 从 stdin 中读入一行if (ibuf[strlen(ibuf) - 1] == '\n') ibuf[strlen(ibuf) - 1] = 0;if (strcmp(ibuf, "r") == 0) {cmd_r(fd, ibuf, filename);} else if (strcmp(ibuf, "w") == 0) {cmd_w(fd, ibuf, filename);} else if (strcmp(ibuf, "quit") == 0) {break;}}close_fd(fd, 1);return 0;
}void err_msg(const char is_errno, const char is_exit, const char *format, ...) {char ibuf[BUF_SIZE] = "";va_list ap;va_start(ap, format);vsnprintf(ibuf, BUF_SIZE - 1, format, ap); // 因为最后一个字节放换行符'\n',所以 BUF_SIZE-1if (is_errno)snprintf(ibuf + strlen(ibuf), BUF_SIZE - strlen(ibuf) - 1, ": %s", strerror(errno));strcat(ibuf, "\n");fflush(stdout); // 刷新stdoutfputs(ibuf, stderr); // 错误信息写到stderrfflush(NULL); // 参数为 NULL, fflush() 会刷新所有 stdiova_end(ap);if (is_exit)exit(EXIT_FAILURE);
}void close_fd(const int fd, char is_exit) { // is_exit: 0 (false),1(true)if (-1 == close(fd)) {perror("close error");if (is_exit)exit(EXIT_FAILURE);}
}void cmd_r(const int fd, char* const ibuf, const char *filename)
{struct flock lock;/* 对整个文件加锁 */lock.l_start = 0;lock.l_whence = SEEK_SET;lock.l_len = 0; lock.l_type = F_RDLCK;int res = fcntl(fd, F_GETLK, &lock);printf("res = %d\n", res);if (lock.l_type == F_UNLCK) {lock.l_type = F_RDLCK;if (fcntl(fd, F_SETLK, &lock) == -1) {err_msg(0, 0, "lock %s fail, please try again later", filename);} else {lseek(fd, 0, SEEK_SET); // 将文件偏移量设置到文件开始处printf("******* 文件内容 *******\n");ssize_t nread = 0;char rbuf[BUF_SIZE] = "";while ((nread = read(fd, rbuf, BUF_SIZE)) > 0) {if (write(STDOUT_FILENO, rbuf, nread) != nread)err_msg(1, 1, "write error");}printf("******* 文件内容 *******\n");while (1){printf("%s 已经被该进程共享写锁锁住,请输入 u 进行解锁\n", filename);fgets(ibuf, BUF_SIZE, stdin);if (ibuf[strlen(ibuf) - 1] == '\n')ibuf[strlen(ibuf) - 1] = 0;if (strcmp(ibuf, "u") == 0) {lock.l_type = F_UNLCK;if (fcntl(fd, F_SETLK, &lock) == -1) {err_msg(1, 0, "unlock %s fail, please try again later.", filename);} else {break;}}}}} else {err_msg(0, 0, "getlock %s fail, please try again later", filename);}
}void cmd_w(const int fd, char* const ibuf, const char *filename) {struct flock lock;/* 对整个文件加锁 */lock.l_start = 0;lock.l_whence = SEEK_SET;lock.l_len = 0; lock.l_type = F_WRLCK;int res = fcntl(fd, F_GETLK, &lock);printf("res = %d\n", res);if (lock.l_type == F_UNLCK) {lock.l_type = F_WRLCK;if (fcntl(fd, F_SETLK, &lock) == -1) {err_msg(0, 0, "lock %s fail, please try again later", filename);} else {lseek(fd, 0, SEEK_END); // 将文件偏移量设置到文件末尾char buf[] = "wrlck\n";size_t len = strlen(buf);if (write(fd, buf, len) != len)err_msg(1, 1, "write error");buf[strlen(buf) - 1] = 0;printf("%s 已写入文件\n", buf);while (1){printf("%s 已经被该进程独占写锁锁住,请输入 u 进行解锁\n", filename);fgets(ibuf, BUF_SIZE, stdin);if (ibuf[strlen(ibuf) - 1] == '\n')ibuf[strlen(ibuf) - 1] = 0;if (strcmp(ibuf, "u") == 0) {lock.l_type = F_UNLCK;if (fcntl(fd, F_SETLK, &lock) == -1) {err_msg(1, 0, "unlock %s fail, please try again later.", filename);} else {break;}}}}} else {err_msg(0, 0, "getlock %s fail, please try again later", filename);}
}
同时开2个进程测试
(1)共享读锁(进程1读取后,未解锁,进程2请求读取,成功,说明读时共享)


(2)独占性写锁(进程1写后,未解锁,进程2请求写,请求读都失败,说明写时独占)


(3)在文件被多个进程上了共享读锁后,需要对这个文件进行读时的所有进程都释放了这把读锁后,请求上写锁才会成功。
总结
利用 fcntl() 函数获取、设置文件的 flags 和 设置文件锁是在现实开发中比较常用到的功能,fcntl() 函数功能强大,简单一言两语比较难比较全面的对这个函数的功能进行解析,篇幅较长,还有一些其他的功能放在后面的另一篇博文进行更新吧。
参考:
《UNIX环境高级编程》(第3版)
《Linux-UNIX系统编程手册》
相关文章:
linux文件I/O之 fcntl() 函数用法:设置文件的 flags、设置文件锁(记录锁)
头文件和函数声明 #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ ); 函数功能 获取、设置已打开文件的属性 返回值 成功时返回根据 cmd 传递的命令类型的执行结,失败时返回 -1,并设置 errno 为相…...
黑马项目一完结后阶段面试45题 JavaSE基础部分20题(一)
一、Java数据类型 基本数据类型——四类八种 整数型 byte short int long 浮点型 float double 字符型 char 布尔型 boolean 引用数据类型 String字符串 类(对象) 接口类型 数组类型 枚举类型 二、面向对象的三大特性 1.封装 把同一类事物…...
(一)创建型设计模式:3、建造者模式(Builder Pattern)
目录 1、建造者模式含义 2、建造者模式的讲解 3、使用C实现建造者模式的实例 4、建造者模式的优缺点 5、建造者模式VS工厂模式 1、建造者模式含义 The intent of the Builder design pattern is to separate the construction of a complex object from its representatio…...
指针进阶大冒险:解锁C语言中的奇妙世界!
目录 引言 第一阶段:🔍 独特的字符指针 什么是字符指针? 字符指针的用途 演示:使用字符指针拷贝字符串 字符指针与字符串常量 小试牛刀 第二阶段:🎯 玩转指针数组 指针数组是什么? 指针…...
2.0 Maven基础
1. Maven概述 Maven概念 Apache Maven是一个软件项目管理工具,将项目开发和管理过程抽象程一个项目对象模型(POM,Project Object Model)。 Maven作用 项目构建 提供标准的、跨平台的自动化项目构建方式。 依赖管理 方便快捷…...
在Linux虚拟机内配置nginx以及docker
目录 1、nginx源码包编译以及安装依赖 1、配置安装所需的编译环境 2、安装函数库(pcre、zlib、openssl) 2、安装nginx 1、获取源码包 2、解压编译 3、启动nginx服务 1、关闭防火墙 2、运行nginx 3、使用本地浏览器进行验证 3、安装docker 1、…...
数据结构-带头双向循环链表的实现
前言 带头双向循环链表是一种重要的数据结构,它的结构是很完美的,它弥补了单链表的许多不足,让我们一起来了解一下它是如何实现的吧! 1.节点的结构 它的节点中存储着数据和两个指针,一个指针_prev用来记录前一个节点…...
android Ndk Jni动态注册方式以及静态注册
目录 一.静态注册方式 二.动态注册方式 三.源代码 一.静态注册方式 1.项目名\app\src\main下新建一个jni目录 2.在jni目录下,再新建一个Android.mk文件 写入以下配置 LOCAL_PATH := $(call my-dir)//获取当前Android.mk所在目录 inclu...
MySQL中的索引
1.2.MySQL中的索引 InnoDB存储引擎支持以下几种常见的索引:B树索引、全文索引、哈希索引,其中比较关键的是B树索引 1.2.1.B树索引 InnoDB中的索引自然也是按照B树来组织的,前面我们说过B树的叶子节点用来放数据的,但是放什么数…...
idea中如何处理飘红提示
idea中如何处理飘红提示 在写sql时,总是会提示各种错误 查找资料,大部分都是说关提示,这里把错误提示选择为None即可 关掉以后,也确实不显示任何提示了,但总有一种掩耳盗铃的感觉 这个sms表明明存在,但是还…...
Elasticsearch使用中出现的错误
Elasticsearch使用中出现的错误 1、分页查询异常 在分页的过程中出现了一个问题是当查询的数据超过10000条的时候报了异常: from size must be less than or equal to: [10000]这个问题最快捷的解决方式是增大窗口大小: curl -XPUT http://127.0.0.…...
【IMX6ULL驱动开发学习】01.编写第一个hello驱动+自动创建设备节点(不涉及硬件操作)
目录 一、驱动程序编写流程 二、代码编写 2.1 驱动程序hello_drv.c 2.2 测试程序 2.3 编写驱动程序的Makefile 三、上机实验 3.1 NFS 挂载 3.2 测试示例 一、驱动程序编写流程 构造file_operations结构体 在里面填充open/read/write/ioctl成员 注册file_operations结…...
决策规划仿真平台搭建
决策规划仿真平台搭建 自动驾驶决策规划算法第二章第一节 决策规划仿真平台搭建 这部分的主要难点在于多个软件的连通与适配,环境的搭建总是折磨人的,主要是 4 个软件,各软件版本如下 Visual Studio2017PreScan8.5.0CarSim2019.0MATLAB2019b…...
计算图像哈希SHA-512
1、MATLAB实现 计算图像哈希值SHA-512,在文献[1]提到的算法如下: % Example Code: Create an MD5 crypto-hash of an arbitrary string, "str" % Main class of interest: System.Security.Cryptography.HashAlgorithm% Example String to hash with MD5 %…...
Android之消除APP图标的白色边框
有问题的效果: 解决方案: 第一步:app右键—>new—>Image Asset 第二步:上传Logo图标,选择每种分辨率,预览看效果,选择Resize,可以微调 第三步:点击 Nextÿ…...
java线程的优先级、守护线程的概念
1.线程的调度 抢占式调度 非抢占式调度 1.1 抢占式调度 优先级越高,抢到cpu的概率越高 1.2 守护线程 守护线程,非守护线程。当其他的非守护线程执行完毕以后,守护线程会陆续结束。 守护线程的应用场景...
asp.net core 6.0 efcore +sqlserver增删改查的demo
asp.net core 6.0 efcore sqlserver增删改查的demo 下面是一个使用ASP.NET Core 5.0和Entity Framework Core进行增删改查操作的示例。 首先,创建一个空的ASP.NET Core 6.0 Web应用程序项目。 然后,安装以下NuGet包: Microsoft.EntityFra…...
HC32L110B6芯片测试
到货之后,直观上感觉的确很小,小包装盒里面还装了说明书。 下载器单独在一个盒里面,但是这个T-U2T没用上,还是用的STLINK。 开发之前先去网上找了一些别人遇到的坑,的确不少。 涉及的方面也是挺全的,供电、…...
关于我乱删注册表导致电脑没有声音这件事
之前因为想彻底删除迅雷,照着网上进入注册表一顿乱删,也忘记删了啥,反正把一顿xmp的文件,和搜索出来迅雷的全删了。结果迅雷确实没了,被带走的还有电脑的声音。 很离谱,就试过了所有方法都没用,…...
Linux 命令 su 和 sudo 的区别
之前一直对 su 和 sudo 这两个命令犯迷糊,最近专门搜了这方面的资料,总算是把两者的关系以及用法搞清楚了,这篇文章来系统总结一下。 1. 准备工作 因为本篇博客中涉及到用户切换,所以我需要提前准备好几个测试用户,方…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...
ArcGIS Pro+ArcGIS给你的地图加上北回归线!
今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等,设置经线、纬线都以10间隔显示。 2、需要插入背会归线…...
十二、【ESP32全栈开发指南: IDF开发环境下cJSON使用】
一、JSON简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有以下核心特性: 完全独立于编程语言的文本格式易于人阅读和编写易于机器解析和生成基于ECMAScript标准子集 1.1 JSON语法规则 {"name"…...
